# Auth Providers API

> An auth provider is one way to connect to a service, identified by its slug (google, google-sa, my-crm): how it signs in or which keys it takes. Its auth methods (specs) follow the OpenAPI security sc...

**URL:** https://inference.sh/docs/api/rest/auth-providers
**Last updated:** 2026-10-15

---

An **auth provider** is one way to connect to a service, identified by its slug (`google`, `google-sa`, `my-crm`): how it signs in or which keys it takes. Its **auth methods** (`specs`) follow the OpenAPI security scheme kinds: `oauth2` (authorization code or client credentials), `api_key` and `http`. The credential service resolves a slug by looking up a **team** row first, then a **platform** row.

Every auth provider belongs to a **company**, the organization behind the service (one catalog row with name, logo and legal links). Google's five slugs all carry `provider: "google"`. The `provider` field on an auth provider row is the company slug; the row's own `slug` is what `Credential.provider` stores.

> Renamed from **auth schemes** (INF-1079). The `/auth-schemes…` paths answer identically for one more release and are then removed; use `/auth-providers…`. In the SDKs `AuthSchemeDTO` is now `AuthProviderDTO` and `AuthSchemeSpec` is `AuthMethod`.

- **Platform auth providers** — OAuth providers in the catalog (Google, Slack, GitHub, Linear, and 35 others). Every catalog provider is a platform row that the platform maintains from its built-in definitions. List them with `GET /auth-providers?scope=platform`. Until a platform operator sets the shared OAuth app with [`PUT /credentials/{provider}/app`](/docs/api/rest/credentials#configure-oauth-app), teams connect through **their own** OAuth app on the provider page, the same way Microsoft works today. Operators edit seeded rows with `PUT /auth-providers/{id}` (workspace **Admin → providers**) and can still register additional platform slugs with `POST /auth-providers` and `scope: platform` when a service is not in the catalog.
- **Team auth providers** — Your own OAuth app for a slug that is not already a platform row. Register with `POST /auth-providers` (default `scope: team`); client id and secret are written to the team vault. Teammates connect through [`POST /credentials`](/docs/api/rest/credentials#connect-a-credential) like any other OAuth provider.

Auth providers store **endpoints and OAuth settings** in the database. The OAuth **client id and secret** live in the vault (`{SLUG}_CLIENT_ID`, `{SLUG}_CLIENT_SECRET`). After a user connects, access tokens are stored under `{SLUG}_ACCESS_TOKEN` (slug `my-crm` → env prefix `MY_CRM`).

→ [Credentials API](/docs/api/rest/credentials) — connect, disconnect, and `GET /credentials/configs`  
→ [Secrets API](/docs/api/rest/secrets) — vault keys for client credentials and tokens

---

## Scopes

Uses the same API key scopes as credentials:

| Scope | Endpoints |
|-------|-----------|
| `credentials:read` | `GET /auth-providers`, `GET /auth-providers/hooks`, `GET /auth-providers/{id}` |
| `credentials:write` | `POST /auth-providers`, `PUT /auth-providers/{id}`, `DELETE /auth-providers/{id}` |

**Team auth providers:** create, update, and delete require the **team admin** role (or owner) in addition to `credentials:write`. List and get are available to any team member with `credentials:read`. Callers without the role receive `403` with code `team_role_required`.

**Platform auth providers:** register, update, or delete with `scope: platform` requires a **platform operator** API key from the platform team (`403` `permission_denied` otherwise). Listing platform rows (`?scope=platform`) only needs `credentials:read`.

---

## List auth providers

`GET /auth-providers`

### Team auth providers (default)

Returns every auth provider registered by the API key's team, sorted by `slug` ascending.

```bash
curl https://api.inference.sh/auth-providers \
  -H "Authorization: Bearer inf_your_key"
```

### Platform auth providers

`GET /auth-providers?scope=platform`

Returns every **platform** auth provider row — 38 seeded catalog providers plus any operator-defined platform rows. Sorted by `slug`. Any team member with `credentials:read` can call this; use it to inspect endpoints, default scopes, capabilities, and vault key names before connecting or declaring requirements in `inf.yml`.

The first eight slugs (`google`, `slack`, `discord`, `x`, `microsoft`, `notion`, `salesforce`, `reddit`) shipped as Go OAuth providers; the other thirty were added from each service's public OAuth documentation. None of the thirty had a live platform OAuth app when they were written, so treat the first connect as the practical test.

**Seeded catalog slugs** (38): `airtable`, `asana`, `atlassian`, `bitbucket`, `box`, `calendly`, `canva`, `clickup`, `discord`, `dropbox`, `figma`, `github`, `gitlab`, `google`, `hubspot`, `intercom`, `klaviyo`, `linear`, `linkedin`, `mailchimp`, `microsoft`, `miro`, `monday`, `notion`, `pipedrive`, `quickbooks`, `reddit`, `salesforce`, `slack`, `spotify`, `stripe`, `tiktok`, `twitch`, `typeform`, `webflow`, `x`, `xero`, `zoom`.

```bash
curl "https://api.inference.sh/auth-providers?scope=platform" \
  -H "Authorization: Bearer inf_your_key"
```

**Response** (`AuthProviderDTO[]`):

| Field | Description |
|-------|-------------|
| `id` | Auth provider ID |
| `slug` | Credential slug, used in `/credentials/{slug}`, `Credential.provider`, `credentials:` in inf.yml, and secret key prefixes |
| `provider` | Company slug the auth provider belongs to (`google` for `google-sa`). Defaults to `slug` |
| `display_name` | Label shown in the vault and connect UI |
| `icon_url` | Logo URL shown on credential cards and the provider page (see [Icons and logos](#icons-and-logos)) |
| `website` | Service home domain or URL (for example `github.com`) — used to resolve `icon_url` when it is empty |
| `description`, `docs_url`, `how_it_works` | Catalogue copy for the provider page |
| `scope` | `team` or `platform` — who owns the row |
| `connection_scope` | Default connection scope for new logins (`team` or `user`) when set on the row |
| `specs` | Auth methods (see [Auth method](#auth-method)) |
| `client_id_key` | Vault secret name for the OAuth client id (for example `SLACK_CLIENT_ID`) |
| `client_secret_key` | Vault secret name for the OAuth client secret |

---

## Icons and logos

Each auth provider row can carry an **`icon_url`** for the workspace and API catalog UI. You can set it explicitly on create or update; when you omit it but set **`website`**, the API resolves a logo for you.

**Resolution order** (best effort — unknown brands or lookup failures leave `icon_url` empty):

1. **Company catalog** — If a [company](/docs/api/rest/credentials#company-catalog) row exists for the auth provider's `slug` and has an icon, that URL is used.
2. **Logo lookup by website** — Otherwise the API fetches a logo for the domain in `website` (accepts `acme.com` or a full `https://` URL; `www.` is stripped). The same lookup path is used for companies in the credential service.

**Platform seeded rows** — All 38 catalog slugs include a `website` in the built-in definitions (for example `linear.app`, `zoom.us`). When the platform writes these rows, it looks up a logo for each row that still has no `icon_url` (company icon first, then logo lookup by `website`). A logo already stored on a row is kept.

**Platform operator rows** — Registering or updating a platform auth provider (`scope: platform`) with `website` and no `icon_url` runs the same lookup synchronously on write. When the slug is not yet in the company catalog, a company row is created (OAuth type) so other surfaces can reuse the name and logo.

**Team auth providers** — Registering your own with `website` and no `icon_url` borrows a logo for the card only; it does not create a platform catalog entry. Updating `website` on `PUT /auth-providers/{id}` re-runs lookup when the request leaves `icon_url` empty.

To use a fixed asset, set `icon_url` on create or update; the API does not overwrite a non-empty stored icon when you change other fields.

---

## List auth provider hooks

`GET /auth-providers/hooks`

Returns the hook names an auth method may reference when declarative fields are not enough. Hooks are implemented in the API; custom team auth providers should only use names from this list (unknown names fail validation on create/update).

**Response** (`AuthProviderHooksDTO`):

| Field | Description |
|-------|-------------|
| `token_response` | Non-standard token response parsers (for example `slack_v2`) |
| `identity` | Derive account id and display name when templates are insufficient (for example `discord`) |
| `vars` | Substitute URL variables from extra app keys (for example `microsoft_tenant`, `salesforce_host`) |
| `auth_server_profiles` | Authorization server profiles for **outbound MCP OAuth** (no auth provider row). Names registered in the API (today `microsoft_entra` for Microsoft Entra ID). The MCP connect flow picks one profile from discovered OAuth metadata (issuer host, then authorize endpoint, then token endpoint) and records it on the credential. |

```bash
curl https://api.inference.sh/auth-providers/hooks \
  -H "Authorization: Bearer inf_your_key"
```

---

## MCP resource auth providers

An MCP server that needs a credential signs in through an auth provider of its own, which the API creates when the server is registered or connected. The server's registration declares that slug in `credentials`, and calls to the server send the caller's credential for it.

| | |
|--|--|
| **Slug** | `{name}-mcp`. `name` is the server's slug (directory or workspace registration), else the distinctive label of its host (`mcp.linear.app` → `linear-mcp`). When another auth provider already holds the slug, the API numbers it: `linear-2-mcp`, `linear-3-mcp`, … |
| **Scope** | `platform` when the public MCP directory lists the URL, so every workspace resolves the same slug. Otherwise `team`, so a private server's URL is never shown to another workspace. |
| **OAuth servers** | `kind: "oauth2"` from discovery: `resource` is the resource the server's [RFC 9728](https://www.rfc-editor.org/rfc/rfc9728) metadata names when it names this server, else the server URL; `issuer`, `pkce: true`, `callback: "mcp"`, and a `profile` when the authorization server needs one. A public client is registered for the auth provider (dynamic client registration or the Client ID Metadata Document) with `client_auth: "none"`, unless the authorization server needs an [OAuth app set up by hand](/docs/api/rest/credentials#configure-mcp-oauth-app). |
| **API-key servers** | `kind: "api_key"` with one `API_KEY` field, sent as `Authorization: Bearer <key>`. |
| **Company** | `provider` on the row is the catalog company for the server's slug or host domain (`mcp.asana.com` belongs to `asana`). |

---

## Get auth provider

`GET /auth-providers/{id}`

Returns one auth provider **owned by the team**. Platform rows respond `403` `permission_denied` for non-operators. Responds `404` when the id is missing or belongs to another team.

---

## Register auth provider

`POST /auth-providers`

Creates an auth provider. For **team** rows (default), `client_id` and `client_secret` are required and written to the team vault. For **platform** rows (`scope: platform`), omit client credentials — set the OAuth app later with [`PUT /credentials/{provider}/app`](/docs/api/rest/credentials#configure-oauth-app).

The slug must be unique and must not collide with a built-in or catalog slug (otherwise `409` `conflict`).

**Body** (`AuthProviderCreateRequest`):

| Field | Required | Description |
|-------|----------|-------------|
| `slug` | Yes | Lowercase letters, digits, and single hyphens (for example `my-crm`), max 64 characters |
| `display_name` | Yes | Human-readable name |
| `specs` | Yes | At least one auth method (see [Auth method](#auth-method)) |
| `provider` | No | Company slug, when the auth provider is one of several for one company. Defaults to `slug` |
| `client_id` | Team rows | OAuth client id — stored in the vault, not returned on later reads |
| `client_secret` | Team rows | OAuth client secret — stored in the vault |
| `icon_url`, `website`, `description`, `docs_url`, `how_it_works` | No | Catalogue and UI metadata; see [Icons and logos](#icons-and-logos) |
| `connection_scope` | No | `team` or `user` — default scope for credentials through this auth provider |
| `scope` | No | `team` (default) or `platform` (platform operators only) |

**Example** (team authorization code + PKCE):

```json
{
  "slug": "my-crm",
  "display_name": "My CRM",
  "specs": [
    {
      "kind": "oauth2",
      "flow": "authorization_code",
      "authorization_endpoint": "https://crm.example.com/oauth/authorize",
      "token_endpoint": "https://crm.example.com/oauth/token",
      "scopes": ["read", "write"],
      "pkce": true,
      "client_auth": "basic"
    }
  ],
  "client_id": "your-client-id",
  "client_secret": "your-client-secret"
}
```

Set `provider` to the company slug when the auth provider is one of several for one company (`"provider": "acme"` on `acme-mcp`); it defaults to the slug.

Register this redirect URI in the third-party OAuth app before connecting:

```
https://app.inference.sh/settings/secrets/oauth/{slug}
```

Use your staging or local app origin when testing outside production (same path pattern as [Slack BYOK](/docs/credentials/slack)).

After registration, start OAuth with [`POST /credentials`](/docs/api/rest/credentials#connect-a-credential) (or `POST /integrations`) using `provider` set to the auth provider's `slug`.

---

## Update auth provider

`PUT /auth-providers/{id}`

Updates display fields, `provider` (moves the row under another company), `connection_scope`, and `specs` (`AuthProviderUpdateRequest`). The **slug cannot change** — it is the stable provider key on credentials and secrets.

Optional body fields include `icon_url`, `website`, `description`, `docs_url`, `how_it_works`, and `connection_scope`. Setting `website` without `icon_url` triggers the same logo resolution as on create (see [Icons and logos](#icons-and-logos)).

Optional `client_id` / `client_secret` rotate vault secrets when provided (team rows). Omit them to leave stored values unchanged. Platform updates require a platform operator; team updates require team admin.

---

## Delete auth provider

`DELETE /auth-providers/{id}`

Removes the auth provider and deletes its OAuth app secrets from the vault. Responds `400` `validation_error` when a credential for this slug is still connected — disconnect first with [`DELETE /credentials/{provider}`](/docs/api/rest/credentials#disconnect).

**Response:**

```json
{ "data": { "deleted": true }, "messages": [] }
```

---

## Auth method

Each entry in `specs` is an **auth method** (`AuthMethod`): one way to sign in to the slug. The kinds are the OpenAPI security scheme kinds; OAuth endpoints use the RFC 8414 names.

| `kind` | Means | Fields |
|--------|-------|--------|
| `oauth2` | A sign-in or a server-to-server grant | `flow`: `authorization_code` (default) or `client_credentials`; `authorization_endpoint` (authorization code only), `token_endpoint`, optional `revocation_endpoint`, and everything in the tables below |
| `api_key` | One or more keys a person pastes | `fields` (each `key`, `label`, `placeholder`, `sensitive`, `optional`); optional `in` (`header` or `query`), `param_name`, `value_prefix` (for example `Bearer `) |
| `http` | A pasted secret sent as an HTTP auth scheme | `scheme`: `bearer` or `basic`; optional `bearer_format`; `fields` |

A `client_credentials` method has no redirect: `POST /credentials` exchanges the slug's client id and secret at the token endpoint and returns a connected credential at once (see [Credentials API](/docs/api/rest/credentials#oauth-providers)). An `api_key` or `http` method connects by saving its fields with `POST /secrets` and `provider` set to the slug, the same way catalogd API-key providers do.

**Older names.** Rows written before this change used `kind: "oauth2_authorization_code"`, `authorize_url` and `token_url`. They are still accepted on read (and rewritten by a migration) but new requests must use the names below.

**Validation** (`400` `validation_error`): at least one auth method; `authorization_endpoint`, `token_endpoint`, `revocation_endpoint` and `userinfo_url` must be `https` URLs; `flow` must be `authorization_code` or `client_credentials`; `api_key` and `http` need at least one field, with keys like `ACME_API_KEY` (uppercase letters, digits, underscores) and no duplicates; `in` needs `param_name`; hook names must be registered.

### Authorize and token (`oauth2`)

| Field | Description |
|-------|-------------|
| `kind` | `oauth2` |
| `flow` | `authorization_code` (default) or `client_credentials` |
| `authorization_endpoint` | HTTPS authorization endpoint (authorization code) |
| `token_endpoint` | HTTPS token endpoint |
| `revocation_endpoint` | Optional HTTPS revocation endpoint, used on disconnect when `revoke` is not set |
| `scopes` | Default scopes requested on connect (connect may override) |
| `required_scopes` | Scopes always sent on connect and when adding scopes, even when the caller passes a narrower `scopes` list on [`POST /credentials`](/docs/api/rest/credentials#connect-a-credential). Use the auth provider's own scope names (same namespace as `scopes`). Typical values are the permissions the identity lookup (`userinfo_url` or a `lookups` entry) needs to resolve `identifier` and `name`, plus `offline_access` (or the service's refresh scope) when the defaults include it. Omit when identity comes from the token response or an endpoint any access token can call, or when the service sends no scope parameter (`omit_scope_param`). Built-in catalog providers set these on their platform row. |
| `scope_aliases` | Map friendly scope names to wire values (credentials store the alias) |
| `scope_param` | Query parameter name for scopes (default `scope`; Slack uses `user_scope`) |
| `scope_separator` | Separator between scopes (default space; Slack uses comma) |
| `omit_scope_param` | Omit the scope parameter entirely (Notion) |
| `pkce` | Use PKCE on the authorize request (recommended) |
| `resource` | [RFC 8707](https://www.rfc-editor.org/rfc/rfc8707) resource indicator: the protected resource the token is minted for (an MCP server's URL). Sent on the authorize, code, and refresh requests when the profile takes one |
| `issuer` | The authorization server's [RFC 8414](https://www.rfc-editor.org/rfc/rfc8414) issuer. A callback whose `iss` names another issuer is refused |
| `authorization_response_iss_parameter_supported` | With `issuer`, the callback must carry an `iss` that names it ([RFC 9207](https://www.rfc-editor.org/rfc/rfc9207)) |
| `callback` | Name of the redirect URI the OAuth app was registered with (`<app>/settings/secrets/oauth/<callback>`). Empty means the slug. [MCP resource auth providers](#mcp-resource-auth-providers) use `mcp` |
| `profile` | An authorization server profile from `auth_server_profiles` in [hooks](#list-auth-provider-hooks), for servers that deviate from the specs. Empty means none |
| `client_id_param` | Query/body field name for the OAuth client id (default `client_id`; TikTok uses `client_key`) |
| `extra_authorize_params` | Query parameters appended to the authorize URL (for example Google `access_type=offline`) |
| `add_scopes_params` | Parameters used when re-authorizing for more scopes |
| `account_hint_param` | Authorize query parameter that names the connected account on add-scopes flows (Google and Microsoft use `login_hint`). The API sets it to the credential's `account_identifier`. Omitted on first connect and when the identifier is empty. |
| `client_auth` | `basic` (default), `body`, or `none` — how client id/secret are sent to the token endpoint. `none` is a public client that sends no secret |
| `token_request` | `form` (default) or `json` — token request body encoding |
| `headers` | Extra HTTP headers on requests to the service; values are templates (for example Twitch `Client-Id: {{client_id}}`) |

### Identity and disconnect

| Field | Description |
|-------|-------------|
| `userinfo_url` | HTTPS userinfo endpoint fetched with the access token (shorthand for a lookup named `user`) |
| `userinfo_url_path` | JSON path inside the token response to a userinfo URL (Salesforce) |
| `lookups` | Additional HTTP calls after the token exchange, each with `name`, `url`, optional `method` (`GET` default or `POST`), `body`, `content_type`, and `headers`. The decoded response becomes a document root named after `name` (for example `user`, `resources`). Lookups run after userinfo, in order. |
| `identifier` | Template for the connected account id (for example `{{user.email}}`) |
| `name` | Template for display name |
| `metadata` | Key → template map stored on the credential |
| `token_response_hook` | Named hook when the token response is not RFC 6749 shape |
| `identity_hook` | Named hook when identifier/name cannot be templated |
| `vars_hook` | Named hook to build URL variables from extra app keys |
| `refresh` | `always` refreshes on every read when the service hides expiry; default refreshes near expiry |
| `revoke` | Optional revoke endpoint on disconnect (`url`, `style`: `bearer`, `query`, or `form`) |

### Template placeholders

URLs, headers, lookup bodies, `identifier`, `name`, `metadata`, and `env` values are strings with **`{{path}}`** placeholders (double braces). That lets JSON or GraphQL bodies contain literal `{` / `}` without being parsed as templates.

The document grows as the OAuth flow runs:

| Root | When available |
|------|----------------|
| `client_id` | After authorize (and on token/lookup requests) |
| `vars.*` and each bare var name | From the vars hook (for example `{{tenant}}` in Microsoft URLs) |
| `access_token`, `token.*` | After the token exchange (`token.*` is the decoded token response) |
| `callback.*` | Query parameters from the redirect back to inference.sh (for example QuickBooks `realmId`, Shopify `shop`) — forwarded by the app callback page into `CompleteOAuth` |
| `<lookup>.*` | Each lookup response by `name`; `user.*` is the userinfo lookup |
| `expires_at`, `metadata.*`, `app.*` | Env only: token expiry (RFC 3339), stored metadata, extra OAuth app keys |

Array elements use numeric indexes: `{{resources.0.id}}` (Atlassian cloud id, Xero tenant). `{{a|b}}` picks the first path that resolves. Lookup URLs and headers may include `{{access_token}}` and `{{client_id}}` (HubSpot token-in-URL, Twitch `Client-Id`).

If every placeholder in a template is empty, env/metadata entries are omitted and identifier/name render blank.

When the auth method sets `identifier` or `identity_hook`, [`POST /credentials/oauth/complete`](/docs/api/rest/credentials#account-identity-on-oauth-complete) **must** produce a non-empty account from the token exchange and lookups. Otherwise the connect fails and nothing is saved (the dashboard could not show whose account it is, and incremental auth could not target the same user). A lookup that returns **401** or **403**, or **200** with no matching field, counts as the service refusing identity: fix scopes or the OAuth app and reconnect. A timeout, unreachable host, **408**, **429**, or **5xx** is temporary when the lookup URL is the auth method's own (`userinfo_url` or a lookup, not a URL named by the token response): the login is saved without an account, and the account is learned when the login is next used.

**Examples:**

- **Linear** — POST GraphQL lookup with a JSON body; identity from nested fields: `identifier: "{{user.data.viewer.email}}"`.
- **Dropbox** — POST lookup with body `null` to the account endpoint.
- **QuickBooks** — `metadata.realm_id: "{{callback.realmId}}"` with no extra userinfo call.
- **Mailchimp** — lookup `headers` override the default bearer (OAuth `Authorization` header).

### Callback query parameters on OAuth complete

Some services return extra query parameters on the redirect (QuickBooks `realmId`, Shopify `shop`, and so on). Auth method templates read them as `{{callback.*}}` after OAuth completes.

When you call [`POST /credentials/oauth/complete`](/docs/api/rest/credentials#oauth-providers) (alias `POST /integrations/oauth/complete`), pass those parameters in optional **`params`**: a string map of every callback query key except `code` and `state`, which are top-level fields on the request body. The workspace OAuth callback page forwards redirect query params this way automatically.

```json
{
  "provider": "quickbooks",
  "type": "oauth",
  "code": "authorization_code",
  "state": "state_abc123",
  "params": {
    "realmId": "1234567890"
  }
}
```

Omit `params` when the redirect carries only `code` and `state`. In `@inferencesh/sdk`, the generated type is `CredentialCompleteOAuthRequest` with optional `params?: Record<string, string>`.

### Capabilities, app keys, and runtime env

| Field | Description |
|-------|-------------|
| `capabilities` | Credential capabilities (`key`, `scopes`, `satisfies`, `display_name`, `description`) — used by `GET /credentials/configs` and requirement checks |
| `app_keys` | Extra OAuth app fields beyond client id/secret (labels and vault keys) |
| `env` | Environment variables injected on run (defaults to `{PREFIX}_ACCESS_TOKEN` and expiry) |

Validation errors use `400` with code `validation_error` and a descriptive `detail` (for example non-HTTPS URLs, an unknown `kind` or `flow`, or unknown hook names).

### Declared from inf.yml

An app can carry an auth method inline under `credentials.<slug>.auth` in its `inf.yml`. It is validated at deploy with the same rules and seeds the auth provider when the slug is free. See [Declaring credentials](/docs/extend/credentials#properties).

---

## Credential configs

Auth providers appear in `GET /credentials/configs` (and `GET /integrations/configs`) like every other credential slug. When the row is one the team defined, `CredentialConfigDTO` includes `auth_scheme_id` (the auth provider's id; the field keeps its name) so the workspace UI can edit or remove it.

Resolution for connect and runtime uses the registry (non-OAuth slugs such as `google-sa`, `gcp`, and MCP) then **team auth provider → platform auth provider** (platform rows are cached briefly on each API instance).

Connected accounts use the same `CredentialDTO` fields as other OAuth credentials. Use the auth provider's `slug` as `provider` in apps, agents, and [`POST /credentials/check`](/docs/api/rest/credentials#check-requirements-pre-flight).

---

## Related vault keys

For slug `my-crm`:

| Secret key | Purpose |
|------------|---------|
| `MY_CRM_CLIENT_ID` | OAuth client id (written at registration) |
| `MY_CRM_CLIENT_SECRET` | OAuth client secret |
| `MY_CRM_ACCESS_TOKEN` | Access token after a user connects (managed by the platform) |

Apps and agents read the access token from the environment using the `{PREFIX}_ACCESS_TOKEN` name when the credential is declared in `inf.yml`.
