Essentials
Errors
One envelope, every code, and which are worth retrying.
The envelope
Every failure answers with the same shape, so a client can branch on error.code without parsing prose. The message is written for a person and can change; the code will not.
402 Payment Required
{
"error": {
"code": "plan_required",
"message": "API and MCP access is included on the Grow, Scale and Custom plans."
}
}The same codes reach MCP clients as tool errors, formatted code: message. See Using the tools.
Every code
| Status | Code | When |
|---|---|---|
| 401 | unauthorized | No key was sent, or the key is unknown, revoked or expired, or the member who created it has left the workspace or been deactivated. |
| 402 | plan_required | The workspace's plan doesn't include API access. It's included on Grow, Scale and Custom. |
| 402 | credits_exhausted | This month's content credits are used up. Retrying won't help until the credits reset on the 1st (UTC). |
| 402 | payment_required | The plan doesn't include this capability for the project, or the workspace has reached its daily usage limit. Drafting a content opportunity is the call that can return it. |
| 403 | workspace_not_approved | The workspace is still waiting for approval, or wasn't approved. |
| 403 | write_not_allowed | The key is read-only. Writes need a read-and-write key, and read-and-write keys need Scale or Custom. |
| 403 | plan_feature_unavailable | The feature is on a higher plan. Bulk exports and webhooks are Custom-only. |
| 403 | forbidden | The workspace or the member the key acts as can't do this in the dashboard either — for example a project with competitive analysis switched off. |
| 404 | not_found | The project or item doesn't exist in this workspace. A project from another workspace also returns 404, never 403. |
| 409 | conflict | The item isn't in a state that allows the change — for example marking an opportunity published before it has a draft, or drafting one that is already being drafted. |
| 422 | invalid_request | A parameter or body field is missing or has the wrong type or value. The message names the field. Also returned when a write would pass the plan's prompt limit. |
| 422 | confirmation_required | A draft spends a content credit, and the call didn't confirm it with `confirm_credit: true`. |
| 422 | limit_reached | The workspace already has the maximum number of webhooks (10). |
| 429 | rate_limited | Too many requests this minute. Wait for the number of seconds in the `Retry-After` header, then retry. |
Two of these are easy to confuse and must never be handled the same way.
rate_limited means slow down and retry; credits_exhausted means this month is over — the reason it answers 402 rather than 429is precisely so a retrying client doesn’t loop until the 1st.Server errors
An unexpected failure answers 500with a different body — the one response that isn’t in the envelope:
{
"detail": "Internal server error",
"request_id": "5f9c2b14-7a3d-4e21-b0c8-91d2e4f6a7b3"
}Retry with backoff, and quote the request_id if you report it — it is how we find the request in our logs.
What to branch on
- Retry:
429 rate_limitedafterRetry-After, and5xxwith backoff. - Stop and tell a human:
401,402,403— a credential, a plan or an approval has to change. - Fix the call:
404,409,422— the id, the state or a parameter is wrong. The message names the field.