Credentials give your app keys and connections that belong to an external service: an API key for Acme, an OpenAI key, a Google sign-in. Each user supplies their own, and the platform passes them to your app at run time.
A value that belongs to no service, such as a webhook signing token, is a plain secret instead.
→ Concepts: providers, credentials, secrets and vaults
Two words
- A company is the organization behind a service: one catalog row with its name, logo, docs and legal links. Google is a company; so is Aikido.
- An auth provider is one way to connect to a company's service, named by its credential slug, the key every credential is stored and looked up by. Google has several:
google(an account sign-in),google-sa(a service account),google-gemini(a Gemini API key). Each is an auth provider that belongs to its company; how it signs in (oauth2,api_key,http) is its auth method.
A token is minted for one resource, so a service and its MCP server are two slugs even when the same company runs both: abc for the API, abc-mcp for the MCP server. An abc login never satisfies abc-mcp.
credentials: entries are keyed by the credential slug. credential.provider in the API still carries that slug.
Declaring credentials
List them under credentials: in inf.yml, keyed by the credential slug:
1credentials:2 openai: # a slug the platform lists: nothing else needed3 secrets: [OPENAI_API_KEY]45 google:6 capabilities: [google.sheets] # the OAuth access the app needs7 description: Read and write your sales sheets89 acme: # a slug the platform doesn't list yet10 secrets: [ACME_API_KEY]11 description: Syncs contacts from your Acme workspace12 company: # seeds the company row if missing; never overwrites it13 slug: acme14 name: Acme CRM15 website: acme.com16 logo_uri: https://acme.com/logo.png17 docs_uri: https://docs.acme.com18 tos_uri: https://acme.com/terms19 policy_uri: https://acme.com/privacy2021 acme-mcp: # the same company's MCP server: its own slug22 scopes: [contacts:read]23 auth: # seeds the auth provider if the slug is free24 kind: oauth225 flow: authorization_code26 authorization_endpoint: https://acme.com/oauth/authorize27 token_endpoint: https://acme.com/oauth/tokenThe list form still works and is read into the same shape: provider: acme (a string) becomes the slug acme; key: google.sheets becomes the slug google with capabilities: [google.sheets]; name and website become the company block. A provider: object (the name of the company block before INF-1079) is read as company. Existing apps need no change.
API keys from a provider
Name the slug and the keys your app reads:
1credentials:2 acme:3 secrets: [ACME_API_KEY]4 description: Syncs contacts from your Acme workspace5 company:6 name: Acme CRM7 website: acme.comWhen a user runs the app without an Acme key, the run stops and asks for ACME_API_KEY. The key they paste is saved as their workspace's Acme CRM credential. Its logo comes from acme.com, and it shows in the vault like any other credential. Later runs, and any other app that asks for acme, find it there.
The slug doesn't have to be one the platform lists. The company block describes the company for users who haven't added it yet; company.slug defaults to the credential slug. For a slug the platform lists, such as openai, leave the block out; the platform's name and logo win.
Who can list a company for everyone
The platform's company catalog is shared by every workspace, so only platform apps can add to it:
- A platform app (owned by a platform admin) that declares a company the catalog doesn't have adds it on deploy, with the given
nameand the logo fromwebsite. Every workspace then sees it in the vault, ready to connect. This happens the same way platform apps are approved in the store automatically. - Any other app doesn't change the catalog. The slug still works for each workspace that runs the app: the key a user saves becomes their workspace's credential for it, under the app's
nameand logo. It just isn't listed for workspaces that haven't used it.
This keeps slugs from being claimed by whoever deploys first. To get a company listed for everyone, ask a platform admin.
Describing the auth provider
An entry can also describe how people connect: the name they see, who a saved key belongs to, how to get the key and how long it lasts.
1credentials:2 claude-code:3 secrets: [CLAUDE_CODE_OAUTH_TOKEN]4 company: {slug: anthropic}5 display_name: Claude Code6 connection_scope: user7 instructions: |8 1. Run `claude setup-token` in a terminal and sign in with your Claude account.9 2. Paste the token it prints. It starts with `sk-ant-oat01-`.10 docs_url: https://inference.sh/docs/integrations/claude-code-subscription11 expires_after_days: 365| Field | Effect |
|---|---|
display_name | The name shown on the credential page, the setup dialog and requirement errors. Without it, the slug. |
connection_scope | team (default): a saved key serves the whole workspace. user: each person saves their own. Use user for personal tokens, so one member's token is not shared with everyone. |
instructions | Markdown, links allowed, up to 4000 characters. Shown wherever someone connects: the credential page, the add-key dialog, the app's setup flow and the agent chat's setup card. |
docs_url | A page with the full setup guide, linked beside the instructions. |
expires_after_days | A saved key counts as expired this many days after it is saved; saving a new key restarts the count. The credential page shows the expiry date; after it, runs stop and ask for a new key. 0 or absent: never. |
What they do depends on who deploys the app:
- A platform app: the fields complete the platform's auth provider for the slug. On the first deploy they set it up; on later deploys they fill only fields that are still empty, so a value a platform admin set is never overwritten.
- Any other app: the fields are validated and otherwise ignored, like
company. Ask a platform admin to set them for a listed slug.
A deploy fails when connection_scope is not team or user, instructions are over 4000 characters, display_name is over 100, or expires_after_days is outside 0–3650.
Deploy wires your workspace credential
Each time you deploy an app, the platform registers every api_key entry in credentials: for your workspace.
If you already saved the declared keys as plain team secrets in the vault (not linked to any credential), deploy creates the provider's login if needed, links those secrets to it and marks it connected — you do not need to paste the keys again in the setup dialog. Only secrets in the deploying workspace are considered, and only keys that are still unlinked; a key attached to another credential stays where it is.
If the keys are missing, deploy creates no credential row. The next run (or the vault UI) asks for exactly the keys listed under secrets, and saving them creates the login.
Leave out secrets to receive every key the provider's credential holds.
OAuth capabilities
For a service users sign in to, name the capabilities. They decide the access (the OAuth scopes) your app gets:
1credentials:2 google:3 capabilities: [google.sheets, google.drive]4 description: Read and write your sales sheets, attach exported filesA capability belongs to its own slug: google.drive goes under google, never under another entry. An entry whose only need is optional can say optional: true.
Users without a Google connection are asked to connect one. Users who have one without Sheets access are asked to grant it.
A connect goes through an OAuth client: the user's workspace's own, else the platform's. The platform's client for a provider may carry a list of allowed scopes (the scopes it is approved for — the platform's Google client is held to the scopes Google verified it for); a workspace's own client is unrestricted by default. A connect asking the platform's client for a scope outside its list is refused with scope <scope> is not approved for the platform's <Provider> client; connect through your own OAuth app or ask an admin. Deploy warns when your inf.yml asks for such a scope (see Deploy checks); a platform admin edits the list as data. Details in Allowed scopes.
Properties
The entry's key is the credential slug: lowercase letters, digits and single hyphens (acme, google-sa). It is what credential.provider stores and what @function(credentials=["acme"]) names.
| Property | Type | Description |
|---|---|---|
capabilities | string[] | Capabilities of this slug the platform defines (google.sheets, x.tweet.write). When set, they decide how the entry is checked. Each must start with the entry's slug. |
scopes | string[] | OAuth scopes to require from the credential, for an entry without capabilities. At connect time the API also requests any required_scopes on the slug's auth method (identity and refresh); see Required scopes on connect. |
secrets | string[] | Keys to pass to the app from the credential. They are also the fields users fill in when the key is missing. |
description | string | Shown to users when the credential is missing |
optional | boolean | If true, the app runs without it |
company | object | The company, for a slug the platform doesn't list (CompanySeed): slug (defaults to the entry's), name, description, website, logo_uri, docs_uri, tos_uri, policy_uri. Seeds the company row when none exists; an existing row is never overwritten. Called provider before INF-1079; the object form of provider is still read, and the API's CredentialRequirement.provider still carries it. |
display_name | string | The auth provider's name. See Describing the auth provider. |
connection_scope | string | team (default) or user: who a saved key belongs to |
instructions | string | Markdown shown wherever someone connects (≤ 4000 characters) |
docs_url | string | Setup guide linked beside the instructions |
expires_after_days | integer | Days a saved key lasts (0–3650; 0 = never) |
auth | object | An auth method for a slug no auth provider defines yet: kind (oauth2, api_key, http), flow, authorization_endpoint, token_endpoint, scopes, fields, and the rest. Validated at deploy like POST /auth-providers; seeds the auth provider when the slug is free. |
Legacy list-form keys (provider as a string, key, name, website) are still read. A deploy with an invalid slug, a capability under the wrong slug, or an invalid auth block fails with a validation error.
The section was called integrations: before 2026-09-23. Current belt refuses that name and asks you to rename it.
Per-function credentials
In a multi-function app, credentials: in inf.yml lists everything the app may use. Each function can also declare the subset it uses:
- A function that declares
credentialsrequires exactly those when it runs. - A function that declares none requires the whole
inf.ymllist.
A Google Calendar app whose event functions need only event access, and whose calendar-level functions need full calendar access:
1# inf.yml2credentials:3 google:4 capabilities: [google.calendar.events, google.calendar]5 description: Read and edit events; create, share and delete calendars1from inferencesh import BaseApp, function23class App(BaseApp):4 @function(5 credentials=[{"slug": "google", "capabilities": ["google.calendar.events"]}],6 annotations={"readOnlyHint": False, "destructiveHint": False},7 )8 async def create_event(self, input_data: CreateEventInput) -> CreateEventOutput:9 ...1011 @function(12 credentials=[{"slug": "google", "capabilities": ["google.calendar"]}],13 annotations={"readOnlyHint": False, "destructiveHint": True},14 )15 async def delete_calendar(self, input_data: DeleteCalendarInput) -> DeleteCalendarOutput:16 ...A function entry takes the same properties as an inf.yml entry (Properties), with the slug as slug. The legacy {"key": "google.calendar.events"} and {"provider": "acme"} forms are still read. annotations are covered in Function metadata.
What users are asked for
When a user runs create_event without a Google connection, the connect prompt asks for event access only (google.calendar.events, Google's https://www.googleapis.com/auth/calendar.events scope). Full calendar access is requested only when they run a function that declares google.calendar. A connection that already holds a broader capability satisfies a narrower one: google.calendar or google.calendar.events covers google.calendar.events.readonly, and google.calendar.readonly covers google.calendar.calendarlist.readonly and google.calendar.events.readonly. See Google OAuth capabilities.
Deploy checks
Each function entry is validated like an inf.yml entry and must be covered by inf.yml:
| Function entry | Covered when inf.yml has |
|---|---|
with capabilities | An entry for the same slug whose capabilities include each of the function's |
| without | An entry for the same slug whose secrets include all of the function's secrets and whose scopes include all of the function's scopes |
Coverage is by exact capability: in the example above, inf.yml lists google.calendar.events even though google.calendar grants more. A function entry that isn't covered fails the deploy.
Deploy also warns, without failing, when a function marked readOnlyHint: true requires a capability that has a narrower read-only counterpart, such as google.gmail.modify where google.gmail.readonly would do, or google.calendar.events where google.calendar.events.readonly would do. Declare the read-only capability for that function instead.
It also warns (unapproved_platform_scope) when a credentials: entry asks for a scope — through its capabilities or its scopes list — that the platform's OAuth client for that provider is not approved for (allowed scopes): credentials: google asks for https://www.googleapis.com/auth/youtube.readonly, which the platform's google client is not approved for; users connect through their own OAuth app, or an admin approves the scope. The deploy goes through; users without their own OAuth app are refused at connect. Pick an approved capability, have users set up their own client, or ask a platform admin to approve the scope.
Available capabilities
List the capability keys the platform defines:
1belt app credentials listCommon capabilities
| Key | Description |
|---|---|
google.sheets | Read/write Google Sheets |
google.sheets.readonly | Read-only Sheets access |
google.drive | Google Drive files |
google-sa.files | Service account access (Sheets, Docs, Drive) |
microsoft.mail.read | Read Outlook email |
notion.read | Read shared Notion pages |
salesforce.api | Salesforce REST API access |
reddit.read | Read Reddit posts and comments |
→ Credentials overview for setup guides (Google, Slack, Microsoft, Notion, Salesforce, Reddit, and more)
Accessing credentials
API keys
Each key listed under secrets arrives as an environment variable with the same name:
1import os23class App(BaseApp):4 async def setup(self, config):5 self.acme_key = os.environ["ACME_API_KEY"]OAuth Credentials
1import os23class App(BaseApp):4 async def setup(self, config):5 # The platform refreshes the token before each run; the app never sees a refresh token.6 self.token = os.environ["GOOGLE_OAUTH_ACCESS_TOKEN"]7 self.expires_at = os.environ.get("GOOGLE_OAUTH_TOKEN_EXPIRES_AT") # RFC 3339Service Account
1from google.oauth2 import service_account23sa_json = os.environ.get("GOOGLE_SA_CREDENTIALS")4if sa_json:5 self.credentials = service_account.Credentials.from_service_account_info(6 json.loads(sa_json)7 )Secrets vs credentials
secrets: | credentials: | |
|---|---|---|
| The value belongs to | No service | A provider |
| Users see | A key name to fill in | The provider, with its name and logo |
| Stored as | A plain secret | That provider's credential, with a scope and a status |
| Reused by | Apps that ask for the same key name | Every app that asks for the provider |
| Best for | Webhook tokens, feature flags | API keys, OAuth sign-ins |
Check before run
Validate that your workspace has the secrets and credentials an app requires before calling POST /run:
POST /credentials/check
Requires credentials:read scope. Send the same secrets and credentials arrays from your app's inf.yml:
1curl -X POST https://api.inference.sh/credentials/check \2 -H "Authorization: Bearer inf_your_key" \3 -H "Content-Type: application/json" \4 -d '{5 "secrets": [{ "key": "WEBHOOK_SECRET" }],6 "credentials": [7 { "slug": "acme", "secrets": ["ACME_API_KEY"] },8 { "slug": "google", "capabilities": ["google.sheets"] }9 ]10 }'When requirements are missing, the response matches the 412 body from POST /run (satisfied: false and an errors array). See Check requirements.
Connect from the CLI
Connect native OAuth, API-key, and service-account providers without the dashboard:
1belt credentials connect google2belt credentials connect slack3belt credentials connect grafana # api_key: prompts for declared keys4belt credentials disconnect googleFor API key providers, the CLI saves each required key with POST /secrets (not POST /credentials, which is OAuth-only). That is the same as belt secrets set <KEY> --provider <provider> for each field. Providers with no declared fields print belt secrets set <KEY> --provider … instead. Non-interactive runs print belt secrets set <KEY> --value-stdin --provider <provider> < value-file lines for you to run in CI.
For MCP connector servers, use belt mcp connect <slug> instead. List connected services with belt credentials list (belt creds is an alias).
When a provider has several credential slugs (Google has an account sign-in at google and a separate service account at google-sa), use the slug that matches the connection you need. Connect a Google service account from vaults → credentials (google-sa slug) or with belt credentials connect google-sa.
When you run an app or agent and requirements are still missing, the CLI prints the same errors[] categories with actionable commands — for example belt secrets set <KEY> (prompts for the value) or belt credentials connect google.
→ CLI setup: Credentials · Credentials API
Best Practices
- Request minimal scopes - use
readonlyif you only read, and declare per-function credentials so each function asks only for what it uses - Clear descriptions - explain why access is needed
- Handle missing gracefully - check if optional credentials exist
- Test without - ensure app works without optional credentials
REST API
Programmatic connect, disconnect, and requirements checks:
→ Credentials API — POST /credentials, POST /credentials/check, MCP tool proxy
Next
→ Troubleshooting - Common issues and solutions