Manage workspace-scoped API keys and credentials used when apps run.
Secret values are never included in list or create/update responses — only masked_value. Use GET /secrets/reveal/{key} when you need the plaintext value (audit-logged).
→ Secrets overview — security model and how apps declare requirements
→ Environment secrets — user-facing setup
→ Extend: declaring secrets — inf.yml schema for app authors
→ Vaults API — group secrets and credentials into named vaults
Scopes
| Scope | Endpoints |
|---|---|
secrets:read | List, get |
secrets:read + workspace admin | Reveal (GET /secrets/reveal/{key}) |
secrets:write + workspace admin | Create, update, delete |
Create, update, delete, and reveal require the workspace admin role (or owner) in addition to the API key scope. Platform admins bypass the role check. List and get are open to any workspace member with secrets:read. A caller without the role gets 403 with code team_role_required.
Create API keys with these scopes at settings → workspace → api keys. See REST overview.
List secrets
GET /secrets or POST /secrets/list
Returns workspace-scoped secrets for the API key's workspace. Credential-managed secrets (scope: internal) and system settings (scope: system) are omitted from list results. When the request has no scope filter, the API applies scope=team. An explicit scope filter overrides it.
Query / body parameters
Uses cursor pagination (cursor, limit, filters). POST /secrets/list accepts the same fields in the JSON body.
Response
| Field | Type | Description |
|---|---|---|
items | array | Secret objects |
next_cursor | string | Cursor for the next page |
has_next | boolean | More results available |
total_items | number | Total matching secrets |
Each item:
| Field | Type | Description |
|---|---|---|
id | string | Secret ID |
key | string | Environment variable name (e.g. OPENAI_API_KEY) |
masked_value | string | Masked preview (never the full value) |
description | string | User-visible description |
scope | string | team for user-managed secrets |
credential_id | string | The credential this secret belongs to. Absent on a plain secret. |
created_at | string | ISO timestamp |
Example:
1curl -X POST https://api.inference.sh/secrets/list \2 -H "Authorization: Bearer inf_your_key" \3 -H "Content-Type: application/json" \4 -d '{"limit": 50}'1{2 "items": [3 {4 "id": "sec_abc123",5 "key": "OPENAI_API_KEY",6 "masked_value": "sk-…xxxx",7 "description": "OpenAI API key for GPT models",8 "scope": "team",9 "created_at": "2026-01-15T10:30:00Z"10 }11 ],12 "next_cursor": "",13 "has_next": false,14 "has_previous": false,15 "items_per_page": 1,16 "total_items": 117}Get secret
GET /secrets/{key}
Requires secrets:read. Returns one secret's metadata for your workspace, with the same fields as a list item, including masked_value but not the plaintext value. Use Reveal secret when you need the decrypted value (audit-logged).
Returns 404 not_found if the key does not exist for your workspace.
Example:
1curl https://api.inference.sh/secrets/OPENAI_API_KEY \2 -H "Authorization: Bearer inf_your_key"1{2 "id": "sec_abc123",3 "key": "OPENAI_API_KEY",4 "masked_value": "sk-…xxxx",5 "description": "OpenAI API key for GPT models",6 "scope": "team",7 "created_at": "2026-01-15T10:30:00Z"8}belt secrets get calls this endpoint. On deployments that predate the route, the CLI falls back to listing secrets and filtering locally.
Create secret
POST /secrets
Requires secrets:write.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
key | string | Yes | Environment variable name |
value | string | Yes | Secret value (stored encrypted) |
description | string | No | Shown in the UI and list responses |
provider | string | No | Slug of the provider this key belongs to (for example openai, acme). The secret becomes that provider's credential. The provider does not have to be one the platform lists. |
provider_name | string | No | Display name of a provider the platform doesn't list. Ignored for a listed provider. |
provider_website | string | No | Website of a provider the platform doesn't list (acme.com). Its logo is looked up from the domain. Ignored for a listed provider. |
connection_scope | string | No | Who owns the credential this key activates: user, team, org, or platform. Empty uses the provider default. Requires the matching admin role. Same rules as connection_scope on connect. |
When provider is set, the API links the secret to that provider's credential, creating it if none exists. For a provider the platform doesn't list, the credential is an api_key one named provider_name. An api_key credential becomes connected once a key is saved.
When provider is omitted, the API still links the key to a provider's credential if its name is a listed provider's standard API key (for example ARK_API_KEY belongs to ByteDance). Saving such a key from the vault's secrets tab, belt secrets set, or an app's secrets: block makes it that provider's credential all the same. Names that match an OAuth provider's BYOK app fields (such as X_CLIENT_ID) are not inferred this way.
A provider that is not a valid slug (lowercase letters, digits and single hyphens) fails with validation_error, so a key is never saved as a plain secret by mistake. If connection_scope is set, a validation or permission error fails the whole request. If it is omitted and the credential lookup fails, the secret is still created without a link. Omit provider for a plain secret that belongs to no service.
New workspace-scoped secrets are linked to the workspace's default vault.
Example:
1curl -X POST https://api.inference.sh/secrets \2 -H "Authorization: Bearer inf_your_key" \3 -H "Content-Type: application/json" \4 -d '{5 "key": "OPENAI_API_KEY",6 "value": "sk-your-key-here",7 "description": "OpenAI API key"8 }'Returns the created SecretDTO with masked_value only.
Reserved platform keys
You cannot create team secrets whose key matches an environment variable name reserved for platform settings (SSO, SMTP, object storage, billing, and other deployment configuration). The API returns 400 with code validation_error and a detail such as 'STRIPE_SECRET_KEY' is a reserved platform key. Pick a different name for your app (for example MY_STRIPE_SECRET_KEY) and declare that same name in inf.yml.
Examples of reserved names include STRIPE_SECRET_KEY, SMTP_PASSWORD, TURNSTILE_SECRET_KEY, and STORAGE_ACCESS_KEY_SECRET. The full set matches the legacy environment variable names used for platform settings.
BYOK example with connection scope:
1curl -X POST https://api.inference.sh/secrets \2 -H "Authorization: Bearer inf_your_key" \3 -H "Content-Type: application/json" \4 -d '{5 "key": "OPENAI_API_KEY",6 "value": "sk-your-key-here",7 "provider": "openai",8 "connection_scope": "team",9 "description": "Team OpenAI BYOK key"10 }'Key for a provider the platform doesn't list:
1curl -X POST https://api.inference.sh/secrets \2 -H "Authorization: Bearer inf_your_key" \3 -H "Content-Type: application/json" \4 -d '{5 "key": "ACME_API_KEY",6 "value": "your-acme-key",7 "provider": "acme",8 "provider_name": "Acme CRM",9 "provider_website": "acme.com",10 "connection_scope": "team"11 }'Errors:
| Code | HTTP | When |
|---|---|---|
already_exists | 409 | A secret with this key already exists for the workspace. To make an existing secret a provider's credential, attach it. |
invalid_request | 400 | Missing key or malformed body |
team_role_required | 403 | Caller is not a workspace admin |
team_role_required | 403 | connection_scope: "org" without org admin |
validation_error | 400 | An unknown connection_scope, org when the workspace is not part of an organization, or org / platform requested from a workspace other than the organization or platform workspace. detail says which. |
forbidden | 403 | connection_scope: "platform" without platform admin |
Attach a secret to a provider
PUT /secrets/{key}/provider
Requires secrets:write and workspace admin.
Makes an existing secret a provider's credential, with the same effect as creating it with provider. The value and key name stay the same. An empty provider detaches the secret back to a plain secret. The credentials it joins and leaves have their status updated.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
provider | string | Yes | Provider slug. Empty string detaches. |
provider_name | string | No | As on create |
provider_website | string | No | As on create |
connection_scope | string | No | As on create |
1curl -X PUT https://api.inference.sh/secrets/ACME_API_KEY/provider \2 -H "Authorization: Bearer inf_your_key" \3 -H "Content-Type: application/json" \4 -d '{ "provider": "acme", "provider_name": "Acme CRM", "provider_website": "acme.com" }'Returns the SecretDTO. credential_id is set while the secret is attached and absent on a plain secret.
| Code | HTTP | When |
|---|---|---|
not_found | 404 | No workspace secret with this key |
validation_error | 400 | An invalid provider slug, or a secret that is not workspace-scoped |
Update secret
PUT /secrets/{key}
Requires secrets:write. Updates the value and/or description for an existing workspace secret.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
value | string | Yes | New secret value |
description | string | No | New description (omit to leave unchanged) |
Example:
1curl -X PUT https://api.inference.sh/secrets/OPENAI_API_KEY \2 -H "Authorization: Bearer inf_your_key" \3 -H "Content-Type: application/json" \4 -d '{"value": "sk-rotated-key"}'Returns 404 not_found if the key does not exist for your workspace.
Reveal secret
GET /secrets/reveal/{key}
Requires secrets:read. Returns the decrypted value. Access is audit-logged (secret.revealed).
Example:
1curl https://api.inference.sh/secrets/reveal/OPENAI_API_KEY \2 -H "Authorization: Bearer inf_your_key"1{2 "key": "OPENAI_API_KEY",3 "value": "sk-your-key-here"4}Use sparingly in production automations. Prefer injecting secrets at task runtime (the platform resolves requirements when you POST /run) rather than pulling values into your own systems.
Delete secret
DELETE /secrets/{key}
Requires secrets:write.
Example:
1curl -X DELETE https://api.inference.sh/secrets/OPENAI_API_KEY \2 -H "Authorization: Bearer inf_your_key"1{2 "success": true3}JavaScript SDK
1import { inference } from '@inferencesh/sdk';23const client = inference({ apiKey: 'inf_your_key' });45const { items } = await client.secrets.list({ limit: 50 });6await client.secrets.create({7 key: 'OPENAI_API_KEY',8 value: 'sk-...',9 description: 'OpenAI',10});11const revealed = await client.secrets.reveal('OPENAI_API_KEY');12await client.secrets.update('OPENAI_API_KEY', { value: 'sk-rotated' });13await client.secrets.delete('OPENAI_API_KEY');client.secrets.create also accepts provider, provider_name, provider_website and connection_scope. client.secrets.setProvider(key, { provider }) attaches an existing secret.
→ JavaScript SDK · SDK overview
Python SDK
The Python SDK has no client.secrets namespace. Call the REST endpoints directly. inferencesh.types has SecretCreateRequest and CredentialScope for typing the body:
1import httpx2from inferencesh.types import CredentialScope, SecretCreateRequest34body: SecretCreateRequest = {5 "key": "OPENAI_API_KEY",6 "value": "sk-...",7 "provider": "openai",8 "connection_scope": CredentialScope.TEAM,9 "description": "Team OpenAI BYOK key",10}11response = httpx.post(12 "https://api.inference.sh/secrets",13 headers={"Authorization": "Bearer inf_your_key"},14 json=body,15).json()Related
- Credentials: OAuth and managed service connections (separate from workspace secrets)
- Extend: secrets — declare
secrets:ininf.yml - Tasks API — run apps that consume injected secrets
- Entitlements API — plan limits (API keys, storage, and other resources)