Start
Authentication
Bearer keys, the eight scopes, expiry, and the status codes a bad credential produces.
The header
Every request carries the key as a bearer token. There is no other way to authenticate on /public/v1.
Authorization: Bearer sk_live_YOUR_KEYKeys are created in Settings > API Keys & MCP. Each key is bound to the workspace it was created in; the API never accepts a workspace parameter to move a key elsewhere.
Scopes
A key carries a list of scopes chosen when it is created. Each route declares the scope it needs; a call without it answers 403 with code SCOPE_MISSING. Scopes are all-or-nothing per route: a route that lists two needs both.
| Scope | Unlocks |
|---|---|
posts:read | Read posts and their per-account delivery log |
posts:write | Create, edit, schedule, cancel and publish posts |
connections:read | List connected social accounts and their token health |
media:write | Upload media and ingest media by URL |
analytics:read | Read ad accounts, metrics and the landing-page crawl |
workflows:run | Trigger workflows and read their runs |
webhooks:manage | Register and manage outbound webhooks |
workspaces:manage | Create client workspaces, mint their keys and hosted connect links |
On the MCP server the same scopes decide which tools tools/list returns. A tool outside the key's scopes is not listed and cannot be called.
Keys created before scopes were enforced carry an empty scope list and keep full access. A key created in the app today always carries an explicit list.
Expiry and revocation
A key may carry an expiresAt. Past that instant it answers 401 like a key that never existed. Revoking a key in the app has the same effect immediately. There is no refresh flow: make a new key.
Status codes
| Status | Body | Meaning |
|---|---|---|
401 | { "statusCode": 401, "message": "Missing API key" } | No Authorization: Bearer sk_live_... header. |
401 | { "statusCode": 401, "message": "Invalid API key" } | Unknown, revoked or expired key. |
402 | flat body | The workspace's plan does not include API access. Keys work on Growth and Scale. |
403 | { "error": { "code": "SCOPE_MISSING" } } | The key lacks the scope this route needs. |
429 | flat body with retryAfterSeconds | Rate limit, see Rate limits. |
Managed workspaces
An agency key may act for a client workspace it created over POST /workspaces by sending x-overads-workspace: <workspace id> on any public route or MCP call. The swap happens before any other check, so scopes, rate limits and idempotency all apply to the client workspace. Only a direct child counts: a managed workspace cannot itself manage others.
Authorization: Bearer sk_live_AGENCY_KEY
x-overads-workspace: c0ff3e00-1a2b-4c3d-8e4f-5a6b7c8d9e0f| Case | Answer |
|---|---|
| Header names a workspace the key's workspace manages | The call acts on that workspace. |
| Header names the key's own workspace | Same as no header. |
| Header names any other workspace | 403 with WORKSPACE_NOT_MANAGED. |
| Header is not a single uuid | 400 with WORKSPACE_NOT_MANAGED and field: "x-overads-workspace". |
Header on /workspaces/* routes | 400 with WORKSPACE_HEADER_NOT_ALLOWED; those routes always act on the agency. |
The alternative is a key that belongs to the client workspace, minted with POST /workspaces/:id/api-keys; it needs no header. The whole flow is on Managed workspaces.