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 SDKsAuthSchemeDTOis nowAuthProviderDTOandAuthSchemeSpecisAuthMethod.
- 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 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-providers/{id}(workspace Admin → providers) and can still register additional platform slugs withPOST /auth-providersandscope: platformwhen 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(defaultscope: team); client id and secret are written to the team vault. Teammates connect throughPOST /credentialslike 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 — 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-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.
1curl https://api.inference.sh/auth-providers \2 -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.
1curl "https://api.inference.sh/auth-providers?scope=platform" \2 -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) |
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) |
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):
- Company catalog — If a company row exists for the auth provider'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 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. |
1curl https://api.inference.sh/auth-providers/hooks \2 -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 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. |
| 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.
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) |
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 |
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):
1{2 "slug": "my-crm",3 "display_name": "My CRM",4 "specs": [5 {6 "kind": "oauth2",7 "flow": "authorization_code",8 "authorization_endpoint": "https://crm.example.com/oauth/authorize",9 "token_endpoint": "https://crm.example.com/oauth/token",10 "scopes": ["read", "write"],11 "pkce": true,12 "client_auth": "basic"13 }14 ],15 "client_id": "your-client-id",16 "client_secret": "your-client-secret"17}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:
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 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).
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}.
Response:
1{ "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). 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. 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 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 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) |
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 use mcp |
profile | An authorization server profile from auth_server_profiles in 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 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
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 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 (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 | 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.
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.
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.