Mint an API key, and read its secret the only time it is shown
Create a key for a programmatic caller and return its secret once.
/v1/api-keysAdmin- Operation id
- create_api_key
- Access
- An editor or a viewer is refused.
/v1/api-keysCreate a key for a programmatic caller and return its secret once.
Read secret out of this response and store it now. It is the only
time any route returns it. Nothing can recover it afterwards, and a lost
secret means minting a replacement and revoking this key.
The secret starts with sk_zr_ and is presented as
Authorization: Bearer sk_zr_..., on the same header a user's access token
travels on. The prefix is what tells the two apart, so a caller never picks
a header, only a credential.
Scopes decide what the key reaches, and a key with none reaches
nothing. Send scopes or the key is minted with an empty set and is
refused at every operation. A scope is <tag>:read, <tag>:write,
<tag>:* or *, where the tag is the one the operation is grouped under
in this document: tickets:read covers the ticket reads, tickets:write
covers those and the ticket writes, * covers everything including
operations added later. Scopes are checked here against what this
deployment can actually grant, so a value like tickets:delete, which no
operation could ever require, is refused with 422 rather than stored as a
key that fails everywhere for a reason nobody can see.
Scopes narrow, they never widen. The key acts as the admin who created it and cannot do more than that member's current role allows, so changing or removing their membership changes what the key can do with no key administration at all. The key is also minted into the workspace this call was made against and is bound to it: it cannot be pointed at another workspace.
agent_id pins the key to one agent by adding an agent:<uuid> scope. It
names a resource and no action, so it grants nothing by itself, it only
narrows a key that also carries action scopes.
expires_in_days is optional and absent means the key never expires on its
own. Set one. An expiry is the only thing that limits a leaked secret
without someone noticing and revoking it.
Tenancy: the request is scoped to one workspace, and the credential presented is what selects it. A caller with no membership in that workspace is refused with 404, which is indistinguishable from a workspace that does not exist.
Authorisation: admin or owner. An editor or viewer receives 403 insufficient_role.
Access: Authorization: Bearer carrying either a realm access token or an API key. A key must hold the admin-resources:write scope, and the workspace role of the person who owns it must meet the floor above.
A realm-issued access token, presented as Authorization: Bearer <token>. It acts as the person who signed in, and every operation resolves their workspace role live from the membership record.
In: header
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
curl -X POST "https://example.com/v1/api-keys" \ -H "Content-Type: application/json" \ -d '{ "name": "Billing sync", "expires_in_days": 90, "scopes": [ "tickets:read", "customers:read" ] }'{ "id": "8f14e45f-ceea-4f4e-9c1f-2b0b2b7d4a11", "name": "Billing sync", "prefix": "sk_zr_7Qd2K", "scopes": [ "tickets:read", "customers:read" ], "expires_at": "2026-11-12T09:15:00+00:00", "created_at": "2026-08-14T09:15:00+00:00", "secret": "sk_zr_7Qd2KcW1nJqR0sYb4tVx9mAe6ZpH3uLgN8fD5kSc2rT"}