An auth scheme is the data that defines how OAuth works for a provider slug: authorize and token URLs, scopes, PKCE, identity templates, capabilities, and vault key layout. The credential service resolves a provider by looking up a team row first, then a platform row.
- Platform schemes — OAuth providers in the catalog (Google, Slack, GitHub, Linear, and 35 others). Every catalog provider is a seeded platform row written when a platform operator runs
POST /admin/auth-schemes/seed— the same primitive as the original eight built-ins. The API does not seed these rows on startup; run seed after a deploy that changes the built-in definitions. List them withGET /auth-schemes?scope=platform. Until a platform operator sets the shared OAuth app withPUT /credentials/{provider}/app, teams connect through their own OAuth app on the provider page, the same way Microsoft works today. Operators edit seeded rows withPUT /auth-schemes/{id}(workspace Admin → OAuth providers) and can still register additional platform slugs withPOST /auth-schemesandscope: platformwhen a service is not in the catalog. - Team schemes — Your own OAuth app for a slug that is not already a platform row. Register with
POST /auth-schemes(defaultscope: team); client id and secret are written to the team vault. Teammates connect throughPOST /credentialslike any other OAuth provider.
Auth schemes 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 — connect, disconnect, and GET /credentials/configs
→ Secrets API — vault keys for client credentials and tokens
Scopes
Uses the same API key scopes as credentials:
| Scope | Endpoints |
|---|---|
credentials:read | GET /auth-schemes, GET /auth-schemes/hooks, GET /auth-schemes/{id} |
credentials:write | POST /auth-schemes, PUT /auth-schemes/{id}, DELETE /auth-schemes/{id} |
Team schemes: 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 schemes: register, update, or delete with scope: platform requires a platform operator API key from the platform team (403 permission_denied otherwise). Listing platform schemes (?scope=platform) only needs credentials:read.
List auth schemes
GET /auth-schemes
Team schemes (default)
Returns every auth scheme registered by the API key's team, sorted by slug ascending.
1curl https://api.inference.sh/auth-schemes \2 -H "Authorization: Bearer inf_your_key"Platform schemes
GET /auth-schemes?scope=platform
Returns every platform auth scheme 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 and are updated when an operator runs seed after deploy, like the originals. None of the thirty had a live platform OAuth app when they were written — treat the first connect as the practical test, and fix the row with PUT /auth-schemes/{id} (or re-run seed after correcting the built-in definition) if a path is wrong.
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.
1curl "https://api.inference.sh/auth-schemes?scope=platform" \2 -H "Authorization: Bearer inf_your_key"Response (AuthSchemeDTO[]):
| Field | Description |
|---|---|
id | Auth scheme ID |
slug | Provider key — used in /credentials/{slug}, Credential.provider, and secret key prefixes |
display_name | Label shown in the vault and connect UI |
icon_url | Provider logo URL shown on integration cards and the provider page (see 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 | Catalog copy for the provider page |
scope | team or platform — who owns the scheme row |
connection_scope | Default connection scope for new logins (team or user) when set on the scheme |
specs | Authentication specs (see Auth scheme spec) |
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 scheme 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):
- Providers catalog — If a catalog row exists for the scheme's
slugand has an icon, that URL is used. - Logo lookup by website — Otherwise the API fetches a logo for the domain in
website(acceptsacme.comor a fullhttps://URL;www.is stripped). The same lookup path is used for integration providers in the credential service.
Platform seeded schemes — All 38 catalog slugs include a website in the built-in definitions (for example linear.app, zoom.us). When an operator runs POST /admin/auth-schemes/seed, the API looks up a logo synchronously for each row that still has no icon_url (catalog icon first, then logo lookup by website). The response lists any slugs that stayed unbranded and why. Re-running seed keeps logos already stored on a row unless you clear icon_url. Once a logo is stored, later seeds keep it unless you set icon_url explicitly.
Platform operator rows — Registering or updating a platform scheme (scope: platform) with website and no icon_url runs the same lookup synchronously on write. When the slug is not yet in the providers catalog, a catalog row is created (OAuth type) so other surfaces can reuse the name and logo.
Team schemes — Registering your own scheme with website and no icon_url borrows a logo for the scheme card only; it does not create a platform catalog entry. Updating website on PUT /auth-schemes/{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.
Seed platform catalog (operators)
POST /admin/auth-schemes/seed
Writes the API's built-in OAuth provider definitions as platform auth scheme rows and resolves logos for rows without one. Requires platform admin credentials (same /admin router as other operator endpoints).
Run this after a deploy that changes built-in provider definitions — startup no longer seeds auth schemes. In the workspace, Admin → OAuth providers (/admin/auth-schemes) exposes the same operation as Seed built-ins (toast summarizes created, updated, branded, and any slugs still without a logo). The call is idempotent for known slugs: existing platform rows with the same slug are updated from the built-in definition (operator-added platform slugs that are not in the built-in list are left alone). Logos found on a previous seed or set by an operator are preserved when the built-in row leaves icon_url empty.
Response (AuthSchemeSeedResult):
| Field | Description |
|---|---|
created | New platform rows inserted |
updated | Existing built-in slugs refreshed |
branded | Rows that received an icon_url during this call |
unbranded | Map of slug → reason when a row still has no logo (for example no website to look a logo up by, logo service not configured, no logo found for example.com) |
1curl -X POST https://api.inference.sh/admin/auth-schemes/seed \2 -H "Authorization: Bearer $ADMIN_API_KEY"Example response:
1{2 "data": {3 "created": 0,4 "updated": 38,5 "branded": 2,6 "unbranded": {7 "custom-saas": "no logo found for custom-saas.example"8 }9 },10 "messages": []11}List auth scheme hooks
GET /auth-schemes/hooks
Returns the hook names a scheme spec may reference when declarative fields are not enough. Hooks are implemented in the API; custom team schemes should only use names from this list (unknown names fail validation on create/update).
Response (AuthSchemeHooksDTO):
| 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) |
1curl https://api.inference.sh/auth-schemes/hooks \2 -H "Authorization: Bearer inf_your_key"Get auth scheme
GET /auth-schemes/{id}
Returns one scheme 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 scheme
POST /auth-schemes
Creates a scheme. For team schemes (default), client_id and client_secret are required and written to the team vault. For platform schemes (scope: platform), omit client credentials — set the OAuth app later with PUT /credentials/{provider}/app.
The slug must be unique and must not collide with a built-in or catalog provider slug (otherwise 409 conflict).
Body (AuthSchemeCreateRequest):
| 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 spec (see Auth scheme spec) |
client_id | Team schemes | OAuth client id — stored in the vault, not returned on later reads |
client_secret | Team schemes | OAuth client secret — stored in the vault |
icon_url, website, description, docs_url, how_it_works | No | Catalog and UI metadata; see Icons and logos |
connection_scope | No | team or user — default scope for connections through this scheme |
scope | No | team (default) or platform (platform operators only) |
Example (team authorization code + PKCE):
1{2 "slug": "my-crm",3 "display_name": "My CRM",4 "specs": [5 {6 "kind": "oauth2_authorization_code",7 "authorize_url": "https://crm.example.com/oauth/authorize",8 "token_url": "https://crm.example.com/oauth/token",9 "scopes": ["read", "write"],10 "pkce": true,11 "client_auth": "basic"12 }13 ],14 "client_id": "your-client-id",15 "client_secret": "your-client-secret"16}Register this redirect URI in the third-party OAuth app before connecting:
1https://app.inference.sh/settings/secrets/oauth/{slug}Use your staging or local app origin when testing outside production (same path pattern as Slack BYOK).
After registration, start OAuth with POST /credentials (or POST /integrations) using provider set to the scheme's slug.
Update auth scheme
PUT /auth-schemes/{id}
Updates display fields, connection_scope, and specs. 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).
Optional client_id / client_secret rotate vault secrets when provided (team schemes). Omit them to leave stored values unchanged. Platform scheme updates require a platform operator; team scheme updates require team admin.
Delete auth scheme
DELETE /auth-schemes/{id}
Removes the scheme 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}.
Response:
1{ "data": { "deleted": true }, "messages": [] }Auth scheme spec
Today only oauth2_authorization_code is supported. Each spec describes one OAuth flow for the provider slug.
Authorize and token
| Field | Description |
|---|---|
kind | oauth2_authorization_code |
authorize_url | HTTPS authorization endpoint |
token_url | HTTPS token endpoint |
scopes | Default scopes requested on connect (connect may override) |
scope_aliases | Map friendly scope names to provider 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) |
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 |
client_auth | basic (default) or body — how client id/secret are sent to the token endpoint |
token_request | form (default) or json — token request body encoding |
headers | Extra HTTP headers on provider requests; 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 provider 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.
Examples:
- Linear — POST GraphQL lookup with a JSON body; identity from nested fields:
identifier: "{{user.data.viewer.email}}". - Dropbox — POST lookup with body
nullto the account endpoint. - QuickBooks —
metadata.realm_id: "{{callback.realmId}}"with no extra userinfo call. - Mailchimp — lookup
headersoverride the default bearer (OAuthAuthorizationheader).
Callback query parameters on OAuth complete
Some providers return extra query parameters on the redirect (QuickBooks realmId, Shopify shop, and so on). Auth scheme templates read them as {{callback.*}} after OAuth completes.
When you call POST /credentials/oauth/complete (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.
1{2 "provider": "quickbooks",3 "type": "oauth",4 "code": "authorization_code",5 "state": "state_abc123",6 "params": {7 "realmId": "1234567890"8 }9}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 | Integration 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, unsupported kind, or unknown hook names).
Integration configs
Auth scheme providers appear in GET /credentials/configs (and GET /integrations/configs) like other OAuth integrations. When the row comes from an auth scheme, CredentialConfigDTO includes auth_scheme_id so the workspace UI can edit or remove team-defined schemes.
Provider resolution for connect and runtime uses the registry (non-OAuth providers such as google-sa, gcp, and MCP) then team auth scheme → platform auth scheme (platform rows are cached briefly on each API instance).
Connected accounts use the same CredentialDTO fields as other OAuth integrations. Use the scheme slug as provider in apps, agents, and POST /credentials/check.
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 integration is declared in inf.yml.