Create, list, and revoke API keys programmatically. Use the scope catalog to build least-privilege keys for CI, engines, and automation.
→ API keys — personal and workspace keys, who manages them, offboarding
→ Authentication — how keys are sent on requests
→ Device authorization — CLI sign-in sessions (alternative to long-lived keys)
→ Entitlements — plan limits on API key count
Personal and workspace keys
A key's scope field says who it acts as. It is set at creation and never changes.
scope | Acts as | user_id | Created, listed and revoked by |
|---|---|---|---|
user (default) | The person who created it | That person | Its creator, and workspace owners and admins |
workspace | The workspace's service account | The service account | Workspace owners and admins only |
created_by is always the person who created the key. A personal key stops working, and is revoked, when its creator leaves the workspace; a workspace key keeps working. Workspace keys are not available on personal workspaces.
A workspace key never holds the user or apikeys scopes, whatever it was created with, and it cannot sign in, approve devices or OAuth apps, manage the workspace's members, vault or billing, or create keys. See API keys.
All four /apikeys routes also require the create_keys workspace capability, which every member who is a person holds and service accounts do not. A credential without it gets 403 team_role_required even when it carries apikeys:read or apikeys:write. manage_keys (workspace owners and admins) adds workspace keys and every member's keys.
Permission scopes
API keys carry a list of scopes. Every operation the API performs declares the scope it takes (for example reading an agent takes agents:read, storing a secret secrets:write), and the scope is checked on whatever reaches the operation: a REST route, an MCP tool, an agent's tool call, a flow step or a trigger. Each REST page documents the scopes its operations take.
What a credential without the scope gets:
- A read of a resource that can be public (agents, apps, conversations, files, flows, engines, credentials, knowledge, artifacts): the public rows, as a signed-out caller would. A private row answers
404, a list shows only public rows. - A sign-in-only scope (below):
403 requires_sign_in. - Any other scope:
403 insufficient_scope. The detail names the scope.
Runs act with the scopes that started them
An agent run, a flow run, a task or a trigger records the scopes of the credential that started or set it up, and everything it does later acts with those scopes: its tool calls, a step that runs after an approval, a cron or webhook fire. A run started with a key that holds only apps:execute cannot read your knowledge or use a secret's value, whoever approves its tool calls. Using a secret's value in a tool takes secrets:reveal. A trigger edited by a narrower key takes that key's scopes.
Hierarchy
- Resource-level scopes (for example
agents,apps) grant every action on that resource (agents:read,agents:write,agents:execute). - Action-level scopes grant a single capability (for example
flows:execute). *grants full access.- Empty
scopeson a key means every scope a key can hold (backwards compatibility for keys created before scoped permissions). It never includes a sign-in-only scope. - A
belt loginor web sign-in narrowed when it was approved holds exactly the scopes it was approved with, sign-in-only ones included only when listed.
Keys cannot grant scopes the creator does not hold. If you authenticate with a scoped credential (a limited API key, or a belt login session), POST /apikeys rejects any scope outside your own grant set, and a request with no scopes creates a key with your scopes, not full access. Only an unrestricted creator (a dashboard session, or a key with empty scopes) can create a full-access key.
Common scope strings
| Group | Scopes |
|---|---|
| Agents | agents:read, agents:write, agents:execute |
| Apps / tasks | apps:read, apps:write, apps:execute |
| Knowledge / skills | knowledge:read, knowledge:write |
| Flows | flows:read, flows:write, flows:execute |
| Secrets | secrets:read, secrets:write, secrets:reveal (a key with secrets:read holds it; an app's OAuth token never does) |
| Artifacts | artifacts:read, artifacts:write |
| Credentials | credentials:read, credentials:write |
| Workspaces | teams:read, teams:write |
| API keys | apikeys:read, apikeys:write (sign-in only) |
| Engines | engines:read, engines:write (admin catalog only) |
| Remotes | remotes:read, remotes:write |
| Billing | billing:read, billing:write (sign-in only) |
| Settings | settings:read, settings:write |
| User | user:read, user:write (sign-in only) |
| Approvals | approvals:write (sign-in only) |
| Admin | admin:read, admin:write (sign-in only) |
| Store | store:pricing, store:submit (the platform's workspace only) |
Sign-in-only scopes
Some scopes are held only by your own sign-in: the web app, or the CLI after belt login. No API key (personal or workspace), OAuth app token or workspace frame session can be granted them, and a key that lists one, or a full-access key, is still refused with 403 requires_sign_in:
approvals:write: answering a tool approval, gate, widget or MCP input request, "always allow", and widening what runs without asking (turning on "allow every tool" or skipping hooks on a chat, adding an allow rule or lifting an ask/deny rule on a chat, agent or remote, turning on an agent'spermissions.allow_all_tools)user:read,user:write: your profile, sessions, OAuth consents and connected apps, surveys, bounties, feedbackapikeys:read,apikeys:write: listing, creating and revoking keysbilling:write: subscriptions, checkout, payment methods, vouchers, invoices sentadmin:read,admin:write: platform administration. Held only while the sign-in carries platform power: a staff member's own browser session proved by their authenticator app, or a CLI login they elevated. Without it the refusal isadmin_session_required,admin_authenticator_requiredorotp_required, and a caller who is not staff gets403 forbidden. A sign-in approved withadmin:writealso holds the store scopes. A staff member who has platform power but whose login was approved without the scope an operation needs gets403 insufficient_scopenaming that scope: runbelt loginagain and approve it, no new elevation needed.
Staff viewing a workspace through an impersonation grant hold none of its sign-in-only scopes except admin.
POST /apikeys refuses a sign-in-only scope with 400. GET /scopes leaves them out of scopes and lists them under account_scopes (with their groups in account_groups), for the device approval page. The admin scopes appear there only for staff.
Store scopes
The store's pricing and submissions are run from the platform's own workspace, not by platform administration:
store:pricing: read and change a store version's pricing (draft, evaluate, publish, apply to the listing) and read the sample tasks pricing is checked against.store:submit: submit a resource version to the store on its publisher's behalf.
They are held, when the credential's scopes name them, only while it acts in the platform's workspace as a member: a member's sign-in (no elevation), their API keys, the workspace's own keys, and the agent runs, flows and triggers started there. A staff member with platform power holds them from any workspace. They end when the membership ends, for keys and runs already made too. No other workspace, and no app's OAuth token, ever holds them: naming one on a key elsewhere is refused with 403, and a full-access key carries none. GET /scopes lists them only to callers who hold them.
store:pricing replaces admin:pricing. A key that named admin:pricing no longer holds pricing: create a new one with store:pricing.
Use GET /scopes for the full catalog with labels, descriptions, and preset bundles.
Scope catalog
GET /scopes
Returns every scope a key can hold, UI group, and preset bundle, plus the sign-in-only scopes a login can be approved for (account_scopes, account_groups) and the preset the belt login approval page starts at (login_preset, with the scopes it grants in grants). Public: no credential needed. public_rows on a group says a credential without its read scope still reads the group's public rows. Non-admin callers do not see engine scopes or the engine preset, and only the platform's workspace sees the store scopes.
Example:
1curl https://api.inference.sh/scopes \2 -H "Authorization: Bearer inf_your_key"Response:
1{2 "scopes": [3 {4 "value": "agents:execute",5 "label": "execute",6 "description": "run agents and send messages",7 "group": "agents"8 }9 ],10 "groups": [11 {12 "id": "agents",13 "label": "agents",14 "description": "manage and run AI agents"15 }16 ],17 "presets": [18 {19 "id": "read",20 "label": "read-only",21 "description": "view resources without making changes",22 "summary": [23 "view your apps, agents, and flows",24 "view files, datastores, and projects",25 "view team information"26 ],27 "scopes": [28 "agents:read", "apps:read", "conversations:read", "files:read", "flows:read",29 "datastores:read", "projects:read", "teams:read", "credentials:read",30 "knowledge:read", "artifacts:read", "settings:read"31 ]32 }33 ]34}Preset bundles
| Preset ID | Label | Typical use |
|---|---|---|
standard | standard | Full developer access. The belt login approval page starts at login_preset: standard plus approvals:write. Every read, execute, and write scope a key can hold, except the engine scopes. Includes billing:read. |
run-only | run only | A server that runs apps and agent chats for its users, such as the server proxy: apps:read, apps:execute, agents:read, agents:execute, conversations:read, conversations:write, files:write. It cannot change apps, agents, secrets, keys or billing, or answer approvals. |
read | read-only | Dashboards and monitoring. Read scopes only, without secrets, API keys, or billing. |
engine | engine | Connect private engines: engines:read, engines:write, apps:execute. Returned to admins only. |
Presets are convenience lists — pass individual scope strings when creating a key.
A session or API key stores its scope list when it is created. Scopes added to a preset later are not added to existing credentials. Run belt login again to get a session with the current standard scopes.
List API keys
GET /apikeys or POST /apikeys/list
Requires apikeys:read (sign-in only: an API key cannot list keys) and create_keys. Workspace owners and admins get every key of the workspace; everyone else gets their own personal keys.
Uses cursor pagination (cursor, limit, filters). POST /apikeys/list accepts the same fields in the JSON body.
Listed keys are masked — only the last four characters of the key string are shown (1nfsh-****wxyz).
Example:
1curl -X POST https://api.inference.sh/apikeys/list \2 -H "Authorization: Bearer inf_your_key" \3 -H "Content-Type: application/json" \4 -d '{"limit": 50}'Response item fields:
| Field | Type | Description |
|---|---|---|
id | string | Key ID |
name | string | Display name |
key | string | Masked key (1nfsh-****wxyz) |
scope | string | user (personal key) or workspace |
user_id | string | Who the key acts as: its creator, or the workspace's service account |
created_by | string | The person who created the key |
creator | object | That person's id, name, full_name, email and avatar_url |
scopes | string[] | Granted permissions (empty = full access) |
last_used_at | string | ISO timestamp of the last authenticated request. Omitted when the key has never been used. Updates are debounced (about five minutes), so the value can lag behind actual use. |
expires_at | string | Expiration (optional) |
source | string | Origin — empty for manual keys; token for temporary keys; device-auth for legacy CLI keys; engine:<id> for cloud-engine credentials; oauth:<client_id> for OAuth-issued keys |
Create API key
POST /apikeys
Requires apikeys:write (sign-in only) and create_keys; a workspace key also needs manage_keys. Subject to API key entitlement limits for the workspace.
Request
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name |
scope | string | No | user (default) or workspace. A workspace key needs a workspace owner or admin, and fails with 403 otherwise |
scopes | string[] | No | Permission list. Omit or [] to get the creator's own scopes (full access only if the creator is unrestricted) |
expires_at | string | No | ISO timestamp; must be in the future. 24 hours or less makes it a temporary key |
source, user_id and created_by are set by the server; values in the request body are ignored. A workspace key cannot create keys.
A key created by another API key cannot outlive it: if the creating key expires, the new key's expires_at is capped at the creator's, and a missing expires_at is set to it.
1curl -X POST https://api.inference.sh/apikeys \2 -H "Authorization: Bearer inf_your_key" \3 -H "Content-Type: application/json" \4 -d '{5 "name": "ci-deploy",6 "scopes": ["apps:read", "apps:execute", "files:write"]7 }'A workspace key for CI:
1curl -X POST https://api.inference.sh/apikeys \2 -H "Authorization: Bearer inf_your_key" \3 -H "Content-Type: application/json" \4 -d '{"name": "ci", "scope": "workspace", "scopes": ["apps:execute"]}'The full key string is returned only in the create response. Store it immediately — list responses never include the plaintext value.
JavaScript SDK:
1const { data: key } = await client.apiKeys.create({2 name: 'ci-deploy',3 scopes: ['apps:read', 'apps:execute'],4});5console.log(key.key); // only time the full value is availableTemporary keys
A personal key created with expires_at 24 hours or less from now is a temporary key. The server marks it source: "token". A short-lived workspace key is not a temporary key. It works like any other key (same scopes, same workspace pinning) with these differences:
- It cannot create API keys (no key holds
apikeys:write, which is sign-in only), so a leaked one cannot renew itself.POST /apikeysreturns403. - It sends no expiry reminder emails.
- It is listed while live, where it can be revoked, and deleted once expired.
1curl -X POST https://api.inference.sh/apikeys \2 -H "Authorization: Bearer inf_your_key" \3 -H "Content-Type: application/json" \4 -d '{5 "name": "nightly-report",6 "scopes": ["apps:read", "apps:execute"],7 "expires_at": "'"$(date -u -d '+15 minutes' +%Y-%m-%dT%H:%M:%SZ)"'"8 }'This is what belt auth token does: it mints a 15-minute key for the active workspace so scripts never see the stored login.
Revoke API key
DELETE /apikeys/{id}
Requires apikeys:write (sign-in only) and create_keys. Members revoke their own personal keys; workspace owners and admins revoke any key of the workspace. Anyone else gets 403. Returns the deleted key DTO.
1curl -X DELETE https://api.inference.sh/apikeys/key_abc123 \2 -H "Authorization: Bearer inf_your_key"Revocation is immediate — in-flight requests may still succeed briefly while caches expire.
Workspace pinning
Keys created in settings → workspace → api keys or via POST /apikeys are pinned to the workspace active when they were created. They ignore X-Team-ID, so a leaked automation key cannot be retargeted at another workspace via a header.
This includes temporary keys from belt auth token: minted from a session that can switch workspaces, the key itself stays on the workspace that was active at mint time.
Device-auth and magic-link session tokens support switching workspaces. See Authentication: workspace context.
Next
- Authentication: header format and workspace context
- Device authorization — CLI sessions and
auth/sessionsmanagement - Secrets API — runtime credentials (distinct from API keys)