> ## Documentation Index
> Fetch the complete documentation index at: https://vetta.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Connections

> Connected third-party accounts for a persona — auth configs, the Connect Link, and revocation.

<Info>A connection lets an [identity](/docs/api/identities) act in a third-party account the organization has authorized. There is **no fixed list of supported apps** — search the catalogue.</Info>

Three calls, in this order:

1. **Find the app.** `GET /v1/connections/apps?search=…` searches the catalogue — over a thousand apps — and answers with the `slug` every later call takes as `connector`.
2. An **auth config** is org-level and says *how* that app authenticates. Created once, shared by every identity that connects it.
3. A **connection** is identity-level: the connected account itself, minted against an auth config.

A connected account is the **provider's** object, not a row held here — a read reconciles state against the provider before answering.

## Apps

### The app object

<ResponseField name="object" type="string">Always `connection_app`.</ResponseField>
<ResponseField name="slug" type="string">The app's id. This is what `connector` takes.</ResponseField>
<ResponseField name="name" type="string">The app's display name.</ResponseField>
<ResponseField name="description" type="string">One line about what the app is.</ResponseField>
<ResponseField name="categories" type="string[]">What the app is used for — e.g. `customer-support`, `crm`.</ResponseField>

<ResponseField name="auth" type="string[]">
  Which setup paths work for this app. `managed_oauth` means one call and no credentials of your own; `oauth` means registering an app with the vendor first; `api_key` means the person authorizing types a key on the hosted page.
</ResponseField>

<ResponseField name="tool_count" type="number">How many operations the app publishes in total.</ResponseField>

### Search apps

`GET /v1/connections/apps` — scope `agents:read`

<ParamField query="search" type="string">Matches an app's name, id or description. Omit it to list the most-used apps.</ParamField>
<ParamField query="limit" type="number">Page size, 1–100.</ParamField>
<ParamField query="after" type="string">The `next_cursor` of the previous page. Opaque — it is not a row id.</ParamField>

<CodeGroup>
  ```bash cURL theme={"system"}
  curl -fsSL "https://api.vetta.sh/v1/connections/apps?search=helpdesk" \
    -H "authorization: Bearer sk_live_..."
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={"system"}
  {
    "object": "list",
    "data": [
      {
        "object": "connection_app",
        "slug": "zendesk",
        "name": "Zendesk",
        "description": "Customer support software with ticketing, live chat and a knowledge base.",
        "categories": ["crm", "customer-support"],
        "auth": ["managed_oauth", "oauth"],
        "tool_count": 12
      }
    ],
    "has_more": true,
    "next_cursor": "Mi0z"
  }
  ```
</ResponseExample>

### Retrieve an app

`GET /v1/connections/apps/{slug}` — scope `agents:read`

Everything the two setup calls need before either is made. `not_found` (404) for an id the catalogue does not know — this is what replaced the old connector enum.

<ResponseField name="default_tools" type="string[]">The operations an auth config pins when `tools` is not given.</ResponseField>

<ResponseField name="connect_fields" type="object[]">
  What the person authorizing must still supply — an account subdomain, a region, a workspace. Each has `name`, `label`, `description` and `required`. Pass them to `POST /v1/connections` as `fields`, or leave them and the hosted page asks.
</ResponseField>

<ResponseExample>
  ```json Response theme={"system"}
  {
    "object": "connection_app",
    "slug": "zendesk",
    "name": "Zendesk",
    "auth": ["managed_oauth", "oauth"],
    "tool_count": 12,
    "default_tools": ["ZENDESK_LIST_ZENDESK_TICKETS", "ZENDESK_REPLY_ZENDESK_TICKET"],
    "connect_fields": [
      { "name": "subdomain", "label": "Subdomain", "description": "the part before .zendesk.com", "required": true }
    ]
  }
  ```
</ResponseExample>

## Auth configs

### The auth config object

<ResponseField name="id" type="string">Unique id (e.g. `ac_01H...`).</ResponseField>
<ResponseField name="object" type="string">Always `auth_config`.</ResponseField>
<ResponseField name="connector" type="string">The app this config authenticates — an app `slug`.</ResponseField>
<ResponseField name="auth" type="string">`managed_oauth`, `oauth`, or `api_key`.</ResponseField>
<ResponseField name="scopes" type="string[]">The scopes the config actually carries, read back after creation — not the ones requested.</ResponseField>
<ResponseField name="tools" type="string[]">The operations a connection made from this config may call. Everything else is refused.</ResponseField>
<ResponseField name="connect_fields" type="object[]">What `POST /v1/connections` will still be asked for. Same shape as on the app.</ResponseField>
<ResponseField name="created_at" type="string">Creation timestamp.</ResponseField>

### Create an auth config

`POST /v1/connections/auth_configs` — scope `agents:write`

<ParamField body="connector" type="string" required>An app `slug` from the catalogue.</ParamField>
<ParamField body="auth" type="string">`managed_oauth` (default), `oauth`, or `api_key`. Check the app's `auth` first — not every app offers every path.</ParamField>
<ParamField body="scopes" type="string[]">Scopes to request. Ignored where the app's managed OAuth app is registered with a fixed set.</ParamField>
<ParamField body="tools" type="string[]">Pin the callable operations. Omitted, the app's `default_tools` are pinned. This is a hard boundary — anything not pinned does not exist to the agent.</ParamField>
<ParamField body="client_id" type="string">Your own OAuth client id. Required when `auth` is `oauth`.</ParamField>

<CodeGroup>
  ```bash cURL theme={"system"}
  curl -fsSL https://api.vetta.sh/v1/connections/auth_configs \
    -H "authorization: Bearer sk_live_..." \
    -H "content-type: application/json" \
    -d '{ "connector": "zendesk", "auth": "managed_oauth" }'
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={"system"}
  {
    "id": "ac_01H9CN...",
    "object": "auth_config",
    "connector": "zendesk",
    "auth": "managed_oauth",
    "scopes": ["tickets:read", "tickets:write"],
    "tools": ["ZENDESK_LIST_ZENDESK_TICKETS", "ZENDESK_REPLY_ZENDESK_TICKET"],
    "connect_fields": [
      { "name": "subdomain", "label": "Subdomain", "description": "the part before .zendesk.com", "required": true }
    ],
    "created_at": "2026-08-20T17:10:00Z"
  }
  ```
</ResponseExample>

### List auth configs

`GET /v1/connections/auth_configs` — scope `agents:read`. Returns a [paginated list](/docs/api/pagination).

### Delete an auth config

`DELETE /v1/connections/auth_configs/{id}` — scope `agents:write`. Connections already minted against it stop refreshing.

## Connections

### The connection object

<ResponseField name="id" type="string">Unique id (e.g. `ca_01H...`).</ResponseField>
<ResponseField name="object" type="string">Always `connection`.</ResponseField>
<ResponseField name="auth_config_id" type="string">The auth config it was minted against.</ResponseField>
<ResponseField name="identity_id" type="string">The persona that acts in the account.</ResponseField>
<ResponseField name="connector" type="string">The app the account is in — an app `slug`.</ResponseField>

<ResponseField name="status" type="string">
  `initiated` — minted, waiting for someone to complete the Connect Link.
  `active` — authorized and usable.
  `failed` — authorization did not complete.
  `disconnected` — the stored credential was dropped.
</ResponseField>

<ResponseField name="status_reason" type="string | null">Why a `failed` or `disconnected` account got there.</ResponseField>

<ResponseField name="connect_link" type="string | null">
  The hosted authorization URL. Present only while `initiated`.

  <Warning>**This URL is not on a Vetta domain.** The authorization page is served by the connection provider, on the provider's own host, carrying the provider's name and a "Secured by" badge. Hand it to an end user; do not treat it as part of your product's surface. See [Connections](/docs/identity/connections) for what the provider's own white-labelling does and does not cover.</Warning>
</ResponseField>

<ResponseField name="connect_link_expires_at" type="string | null">When that link stops working.</ResponseField>
<ResponseField name="last_refreshed_at" type="string | null">Last successful credential refresh.</ResponseField>
<ResponseField name="created_at" type="string">Creation timestamp.</ResponseField>

### Connect an account

`POST /v1/connections` — scope `billing:write`

Mints the connected account and its Connect Link; the account starts `initiated`. This is `billing:write` on the wire and not `agents:write` because a managed account is a per-account charge at the provider.

<ParamField body="auth_config_id" type="string" required>The `ac_` config to mint against.</ParamField>
<ParamField body="identity" type="string" required>The `idn_` identity that will act in the account.</ParamField>
<ParamField body="fields" type="object">Values for the config's `connect_fields`, e.g. `{ "subdomain": "acme" }`. Omitted, the hosted page asks the person instead — so this is a convenience, never a gate.</ParamField>

<CodeGroup>
  ```bash cURL theme={"system"}
  curl -fsSL https://api.vetta.sh/v1/connections \
    -H "authorization: Bearer sk_live_..." \
    -H "content-type: application/json" \
    -d '{ "auth_config_id": "ac_01H9CN...", "identity": "idn_01H...", "fields": { "subdomain": "acme" } }'
  ```
</CodeGroup>

<ResponseExample>
  ```json Response theme={"system"}
  {
    "id": "ca_01H9CQ...",
    "object": "connection",
    "auth_config_id": "ac_01H9CN...",
    "identity_id": "idn_01H...",
    "connector": "zendesk",
    "status": "initiated",
    "status_reason": null,
    "connect_link": "https://<provider-host>/link/...",
    "connect_link_expires_at": "2026-08-20T18:10:00Z",
    "last_refreshed_at": null,
    "created_at": "2026-08-20T17:10:00Z"
  }
  ```
</ResponseExample>

**Errors** — `validation_failed` (400) when `identity` is not an `idn_` id or `auth_config_id` is not an `ac_` id; `not_found` (404) for an unknown auth config or identity; `insufficient_scope` (403) without `billing:write`; `insufficient_credits` (402).

### List connections

`GET /v1/connections` — scope `agents:read`

<ParamField query="identity" type="string">Filter to one persona.</ParamField>

Returns a [paginated list](/docs/api/pagination) of connection objects.

### Retrieve a connection

`GET /v1/connections/{id}` — scope `agents:read`

Reconciles status against the provider before answering — **this read is the refresh**. Poll it to watch an `initiated` account become `active`, rather than assuming the Connect Link was completed. There is no `connection.*` webhook yet, so polling this route is the only way to learn that a person finished authorizing.

### Disconnect

`DELETE /v1/connections/{id}` — scope `agents:write`

Revoke. The provider drops the stored credential and every later call through the connection is refused. Returns the standard deleted envelope.

<ResponseExample>
  ```json Response theme={"system"}
  { "id": "ca_01H9CQ...", "object": "connection", "deleted": true }
  ```
</ResponseExample>

<Card title="Identities" icon="arrow-left" href="/docs/api/identities">
  The persona a connection is bound to.
</Card>
