# App catalog

> The APIANT app catalog lists every app you can connect, and a public unauthenticated JSON feed at app.apiant.ai/catalog/v1 serves it with each app's triggers and actions.

The APIANT app catalog lists the apps you can connect, each with its triggers and actions. Browse it at [apiant.ai/apps](https://apiant.ai/apps), where each app has its own page (for example `https://apiant.ai/connections/slack`), or read it as JSON from the public catalog feed documented below.

Every app in the feed can be picked inside the product: the feed uses the same visibility rule as the app picker in APIANT, so an app you find here is one you can connect. To connect one, see [Connecting accounts](/docs/apps/connections). If an app or an operation you need is missing, see [Build a connector, action or trigger](/docs/apps/custom-connectors).

## What an app entry holds

| Field | Type | Meaning |
|---|---|---|
| `slug` | string | The app's public identifier, lowercase letters, digits and hyphens. The only handle the feed exposes. |
| `name` | string | Display name. |
| `description` | string | One-paragraph description of the app. |
| `category` | string or null | App category. `null` when no category has been set yet. |
| `auth_type` | string | How the app connects. Values seen on 2026-09-15: `api_key`, `none`, `oauth_v2`, `managed_auth`, `token_mint`. |
| `app_domain` | string | The vendor's domain, for example `slack.com`. |
| `logo_url` | string | URL of the app icon (`/catalog/v1/apps/{slug}/logo`). |
| `connect_url` | string | Sign-up link that starts on this app: `https://app.apiant.ai/signup?app={slug}`. |
| `last_updated` | string | RFC 3339 time the connector or one of its listed operations was last edited. Informational; use `etag` to detect change. |
| `etag` | string | Validator for this app's entry, including its operations. |
| `retired` | boolean | `true` when the app has been withdrawn. A retired app stays in the feed with this marker for a retirement window (180 days by default), then drops out. |
| `retired_at` | string | Present only when `retired` is `true`. |
| `tier` | string | Popularity band: `A`, `B`, `C`, or `D` for unranked. |
| `sort_order` | integer | Position 1 to N in the feed's popularity ordering. It shifts when apps enter or leave the feed, so treat it as a sort key, not an identifier. |
| `trigger_count` | integer | Number of triggers. |
| `action_count` | integer | Number of actions. |
| `last_op_modified` | string or null | When a trigger or action on this app was last added or edited. `null` when the app has none. |
| `triggers` | array | Detail and bulk responses only. Each item is `{name, description}`. |
| `actions` | array | Detail and bulk responses only. Each item is `{name, description}`. |

The feed publishes names and descriptions of operations only. It does not expose endpoints, request bodies, credentials or internal identifiers.

## Catalog feed endpoints

Base URL: `https://app.apiant.ai/catalog/v1`

| Method and path | Returns | Cache-Control | Default rate limit per IP |
|---|---|---|---|
| `GET /apps` | Every app, summary fields only | `public, max-age=300, stale-while-revalidate=3600` | 300 per 5 minutes, shared with `/apps/{slug}` |
| `GET /apps/{slug}` | One app with its `triggers` and `actions` | `public, max-age=300, stale-while-revalidate=3600` | 300 per 5 minutes, shared with `/apps` |
| `GET /apps/{slug}/logo` | The app icon bytes | `public, max-age=86400, immutable` | 1,000 per 5 minutes |
| `GET /bulk` | Every app with its `triggers` and `actions`, built once a day | `public, max-age=3600` | 10 per hour |

The rate limits are defaults and can change. IPv6 clients are counted per /64 network.

### Authentication

None. The feed is public, read-only, and sends `Access-Control-Allow-Origin: *`, so a browser page on any site can read it directly.

### Parameters

The routes take no query parameters and do not paginate. `GET /apps` returns the whole catalog in one response; `count` equals the length of `apps`. The only path parameter is `{slug}`.

`GET` routes also answer `HEAD`. Any other method returns `405` with `Allow: GET, HEAD`.

### Caching and conditional requests

Every JSON response carries an `ETag`. Send it back in `If-None-Match` and the feed answers `304 Not Modified` with no body when nothing changed.

`/bulk` is a stored artifact rebuilt daily at 04:00 UTC; its `generated_at` field shows when. It is sent gzip-compressed when your client sends `Accept-Encoding: gzip`, and uncompressed otherwise. Prefer `/bulk` over calling `/apps/{slug}` once per app.

### Errors

| Status | When | Body |
|---|---|---|
| `304` | `If-None-Match` matched the current `ETag` | none |
| `404` | Unknown slug, a slug with uppercase or other invalid characters, an app that is not in the feed, or an app with no logo | none |
| `405` | A method other than `GET` or `HEAD` | `method not allowed` |
| `429` | You passed the rate limit for that route | `rate limit exceeded`, with a `Retry-After` header in seconds |
| `503` | The feed cannot produce a body it trusts, or the daily bulk artifact is not built yet or lists an app the feed no longer serves | `{"error":"catalog feed temporarily unavailable"}`, with `Retry-After` (60 or 300 seconds) |

The feed never returns `200` with an empty app list. If the catalog cannot be read it serves the last good response or a `503`.

## Examples

### List every app

```bash
$ curl -s https://app.apiant.ai/catalog/v1/apps
```

Response, trimmed to one app (captured 2026-09-15; the count values are illustrative):

```json
{
  "generated_at": "2026-09-15T18:31:52Z",
  "count": 1,
  "apps": [
    {
      "slug": "slack",
      "name": "Slack",
      "description": "Slack is a messaging platform for teams. Post messages, manage channels, users, reminders, and react to events via the Slack Web API.",
      "category": "Communication",
      "auth_type": "oauth_v2",
      "app_domain": "slack.com",
      "logo_url": "https://app.apiant.ai/catalog/v1/apps/slack/logo",
      "connect_url": "https://app.apiant.ai/signup?app=slack",
      "last_updated": "2026-08-27T13:02:59Z",
      "etag": "\"80515b2820c0614d\"",
      "retired": false,
      "tier": "A",
      "sort_order": 3,
      "trigger_count": 2,
      "action_count": 2,
      "last_op_modified": "2026-08-27T13:02:59Z"
    }
  ]
}
```

### Get one app with its triggers and actions

```bash
$ curl -s https://app.apiant.ai/catalog/v1/apps/slack
```

Response, trimmed to two triggers and two actions (captured 2026-09-15; the count values are illustrative):

```json
{
  "slug": "slack",
  "name": "Slack",
  "auth_type": "oauth_v2",
  "trigger_count": 2,
  "action_count": 2,
  "triggers": [
    { "name": "New Channel", "description": "Triggers when a new channel is created in the workspace." },
    { "name": "New Channel (instant)", "description": "Triggers when a new channel is created. Events API event: channel_created." }
  ],
  "actions": [
    { "name": "Add Bookmark", "description": "Add a bookmark to a channel." },
    { "name": "Add Reaction", "description": "Add an emoji reaction to a message." }
  ]
}
```

The detail response also carries every summary field shown in the list example; they are omitted here for length.

The same call from JavaScript in a browser or Node 18+:

```javascript
// No API key: the feed is public and allows any origin.
const res = await fetch("https://app.apiant.ai/catalog/v1/apps/slack");
if (res.status === 404) throw new Error("slack is not in the catalog");
const app = await res.json();
console.log(app.name, app.triggers.length, "triggers,", app.actions.length, "actions");
```

### Skip the download when nothing changed

```bash
$ curl -s -o /dev/null -w "%{http_code}\n" \
    -H 'If-None-Match: "80515b2820c0614d"' \
    https://app.apiant.ai/catalog/v1/apps/slack
304
```

### Download the whole catalog once a day

```bash
$ curl -s --compressed https://app.apiant.ai/catalog/v1/bulk -o apiant-catalog.json
```

`--compressed` asks for gzip and decompresses it. The file has the shape `{generated_at, count, apps}`, where each app carries the summary fields plus `triggers` and `actions`.

## Next steps

- [Apps and connectors](/docs/apps)
- [Connecting accounts](/docs/apps/connections)
- [Build a connector, action or trigger](/docs/apps/custom-connectors)
- [Public URLs](/docs/reference/urls)
