API keys
Read, create, change, rotate and revoke keys, and read what they did.
Runs any of the 11 calls on this page against your workspace, with your own key.
Reading keys
GET /keys lists every key the caller can see, newest first and a page at a time, with its status, scopes, role, send scope, when it was last used and who made and last changed it. GET /keys/{id} reads one. No read ever returns a secret: maskedKey is enough to tell two keys apart. Both need keys:read.
{ "object": "api_key", "id": "4c1b257a66287fd113bd89d0", "name": "Billing sender", "maskedKey": "oe_live_4c1b…kX7a", "status": "active", "scopes": ["emails:send"], "roleId": null, "domainAllowlist": ["billing.acme.com"], "expiresAt": "2026-12-22T09:00:00.000Z", "lastUsedAt": "2026-09-23T08:14:02.000Z", "createdBy": { "kind": "apiKey", "name": "API key Provisioner", "label": "API key Provisioner" }}Creating and changing keys
POST /keysmints a key and returns its secret intoken, once. Left out, the scopes areemails:sendand the role, send scope and expiry are the caller’s own.PATCH /keys/{id}renames a key, replaces its scopes or its send scope, and switches it off and on withenabled. Switching off is the reversible choice: the key keeps everything and is refused withinactive_api_keyuntil it is switched on again.POST /keys/{id}/rotategives a key a new secret and returns it once. The old secret stops working the instant the call returns.POST /keys/{id}/revokeretires a key for good, with an optionalreason.DELETE /keys/{id}then removes it from the list and keeps its history.- Every one of them needs
keys:manage. Rotating the calling key also works withkeys:write, exactly likePOST /keys/self/rotate.
Never wider than the caller
Every change is checked against the key making it. A key that would end up outside the caller on any axis is refused with 403 beyond_caller_authority, and param names the axis:
- Scopes: only scopes the caller holds after its own role has narrowed them.
- Role: a caller capped by a role can only create and manage keys capped by the same role.
- Expiry: a caller that expires can only create and manage keys that expire no later.
- Mode: a test key can only reach test keys.
- Send scope: only domains and addresses inside the caller’s own, and holding one address never covers its whole domain.
A key narrowed to some domains or addresses only sees the keys whose send scope sits inside its own, so any other key is a 404. Over OAuth only the owner of the workspace reaches these calls, and a member’s token is refused with owner_only.
Before you grant keys:manage
The console asks you to verify yourself again before it mints or rotates a key. A call made with a key cannot be asked that, so keys:manage is a credential that makes credentials: a leaked key holding it can mint keys of its own, up to its own reach, that keep working after it is revoked.
- Give
keys:manageonly to automation whose job is provisioning keys, never to a key that sends mail. - Narrow that key: a role, a send scope and an expiry. Everything it creates inherits all three and can never exceed them.
- Watch
GET /keys/activity. Every key it creates, changes or revokes is recorded against it by name, so a leak shows up as keys you did not expect. keys:readexposes the request log, IP addresses and user agents included. Treat it as audit access.
Request log and activity
GET /keys/requests and GET /keys/{id}/requests read every authenticated call a key made, newest first: method, path, status, error code, duration, IP and user agent, never a body or a query string. keyIds, failedOnly, since and until are the filters the console offers. GET /keys/activity and GET /keys/{id}/activity read what happened to keys, with actor naming who did it, as @username or API key <name>. Nothing is pruned, and a deleted key keeps its history.