Credentials

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:

yaml
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/token

The 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:

yaml
1credentials:2  acme:3    secrets: [ACME_API_KEY]4    description: Syncs contacts from your Acme workspace5    company:6      name: Acme CRM7      website: acme.com

When 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 name and the logo from website. 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 name and 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.

yaml
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
FieldEffect
display_nameThe name shown on the credential page, the setup dialog and requirement errors. Without it, the slug.
connection_scopeteam (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.
instructionsMarkdown, 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_urlA page with the full setup guide, linked beside the instructions.
expires_after_daysA 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:

yaml
1credentials:2  google:3    capabilities: [google.sheets, google.drive]4    description: Read and write your sales sheets, attach exported files

A 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.

PropertyTypeDescription
capabilitiesstring[]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.
scopesstring[]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.
secretsstring[]Keys to pass to the app from the credential. They are also the fields users fill in when the key is missing.
descriptionstringShown to users when the credential is missing
optionalbooleanIf true, the app runs without it
companyobjectThe 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_namestringThe auth provider's name. See Describing the auth provider.
connection_scopestringteam (default) or user: who a saved key belongs to
instructionsstringMarkdown shown wherever someone connects (≤ 4000 characters)
docs_urlstringSetup guide linked beside the instructions
expires_after_daysintegerDays a saved key lasts (0–3650; 0 = never)
authobjectAn 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 credentials requires exactly those when it runs.
  • A function that declares none requires the whole inf.yml list.

A Google Calendar app whose event functions need only event access, and whose calendar-level functions need full calendar access:

yaml
1# inf.yml2credentials:3  google:4    capabilities: [google.calendar.events, google.calendar]5    description: Read and edit events; create, share and delete calendars
1from 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 entryCovered when inf.yml has
with capabilitiesAn entry for the same slug whose capabilities include each of the function's
withoutAn 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:

bash
1belt app credentials list

Common capabilities

KeyDescription
google.sheetsRead/write Google Sheets
google.sheets.readonlyRead-only Sheets access
google.driveGoogle Drive files
google-sa.filesService account access (Sheets, Docs, Drive)
microsoft.mail.readRead Outlook email
notion.readRead shared Notion pages
salesforce.apiSalesforce REST API access
reddit.readRead 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 3339

Service 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 toNo serviceA provider
Users seeA key name to fill inThe provider, with its name and logo
Stored asA plain secretThat provider's credential, with a scope and a status
Reused byApps that ask for the same key nameEvery app that asks for the provider
Best forWebhook tokens, feature flagsAPI 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:

bash
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:

bash
1belt credentials connect google2belt credentials connect slack3belt credentials connect grafana          # api_key: prompts for declared keys4belt credentials disconnect google

For 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

  1. Request minimal scopes - use readonly if you only read, and declare per-function credentials so each function asks only for what it uses
  2. Clear descriptions - explain why access is needed
  3. Handle missing gracefully - check if optional credentials exist
  4. 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

we use cookies

we use cookies to ensure you get the best experience on our website. for more information on how we use cookies, please see our cookie policy.

by clicking "accept", you agree to our use of cookies.
learn more.