Skip to the documentation
API

Authentication

One credential type, and the ways a request is refused.

Check a key works

GET /ping is the smoke test: it needs no scope and tells you what the key is.

curl
curl "$OE/ping" -H "$AUTH"
Response
{  "ok": true,  "keyId": "4c1b257a66287fd113bd89d0",  "mode": "live",  "scopes": ["emails:send", "emails:read"],  "roleId": null,  "grantedScopes": ["emails:send", "emails:read"],  "workspaceId": "10417196-e324-4283-af98-66ec62167c47"}

If this works and something else 401s, the problem is the scope, not the key.

scopes is the EFFECTIVE list and the only one that authorises anything. grantedScopes is what the key was issued with, and the two differ only when a role is capping the key. The Scopes page explains that intersection. A roleId of null means no ceiling, which is the widest a key gets.

See what a key may send as

GET /addresses is the answer to a 403 you did not expect.

curl
curl "$OE/addresses" -H "$AUTH"
Response
{  "object": "list",  "unrestricted": false,  "data": [    { "object": "address", "address": "[email protected]", "enabled": true, "canSend": true },    { "object": "address", "address": "[email protected]", "enabled": true, "canSend": false }  ],  "domains": [    { "domain": "acme.com", "receivingVerified": true, "sendingVerified": true, "catchAll": false }  ]}

canSend: false has three causes: the address is switched off, the key's send scope leaves it out (neither its domain nor the address itself is on the key), or the domain cannot sign yet. enabled on the address and sendingVerified on its domain tell those apart, and that is most of the debugging time this endpoint saves. A domain can be verified for receiving and still unable to send.

unrestricted: true means any local-part on a verified domain is accepted, including ones nobody has created yet.

How a key is refused

CodeMeans
missing_api_keyNo Authorization header at all.
invalid_credential_typeA cookie or a session token. Send an API key.
invalid_api_keyNot a key we issued, or the secret does not match.
revoked_api_keyIssued here, then revoked. Distinct on purpose. It is the difference between a five-minute fix and an afternoon.
expired_api_keyIssued here, then expired.
insufficient_scopeA real key, without the scope this endpoint needs.

Revoking takes effect on the next call. The row stays on the keys page afterwards, so you can still tell whether anything was using the key when you killed it. The most useful state on that screen is "never used", because it is how a leaked key is told from a live dependency.

Rotating is the other way to retire a secret. It mints a new secret for the same key, so the id, the name, the scopes, the role, the send scope and every request and activity row all carry on; only the secret changes. The old one stops working the instant the rotation completes, with no overlap window, and the replacement is shown once. From the console it is in the same menu as Revoke and asks you to re-verify first. A key holding keys:write can also rotate itself with POST /keys/self/rotate, which is how an integration rotates on a schedule without anyone opening the console.