Errors and limits
The error shape every surface returns, what each status class means, the codes worth branching on, and the plan and rate limits a call can meet.
The error shape
Every error from the REST API has the same body:
{
"error": {
"code": "unknown_approver",
"message": "A sentence for a person to read.",
"details": { "…": "whatever the refusal carries, when it carries something" }
}
}The `code` is the contract. Branch on it, never on the message, which is written for people and will be reworded. The SDKs raise it as DeliverdError.code, the CLI prints Error (code): message, and the MCP server returns most refusals as an error result carrying the same code.
Status classes
| Status | Means | What to do |
|---|---|---|
| 400 | The request itself is wrong. | Change it. Retrying the same request fails the same way. |
| 401 | The credential was not accepted. | Check the key or token. |
| 402 | Your plan does not include this, or a plan limit is reached. | Upgrade, or free something up. Not retryable as it is. |
| 403 | A refusal about you, or a rule about you. | Not retryable. Read the code: a missing scope, a role, or an organisation rule. |
| 404 | No such thing, or nothing this key may see. | Check the id. The two are deliberately indistinguishable. |
| 409 | The request was fine and the world moved. | Re-read and retry: someone published first, a question was answered, a version is awaiting approval. |
| 413, 415, 422 | The body is too large, the wrong type, or a scan blocked what was in it. | Change the content. |
| 429 | Too many requests. | Slow down and retry; the SDKs do this for you. |
| 500 | Ours. | Retry later. A named 500 code means a write failed for a reason you could not have avoided. |
Codes worth branching on
| Code | Status | When |
|---|---|---|
auth_error | 401 or 403 | The key or token is missing, malformed, revoked or expired (401), or its organisation is suspended or its agent disabled (403). |
insufficient_scope | 403 | The credential is fine and lacks the scope this call needs; the message names it. Reissue the key — scopes are fixed when a key is made. |
validation_error | 400 | The body does not match the schema; details lists the problems. |
unknown_approver | 400 | An approver, reviewer or respondent is not someone the organisation knows. |
ambiguous_approver | 400 | A name matches more than one person or team. Name it by id. |
self_approval | 403 | The requester named only themselves. |
refused_by_rule | 403 | An organisation rule or ethics rule refused the request. The message carries its explanation. |
action_not_permitted | 403 | This agent is not permitted to make this kind of request. |
agent_not_permitted | 403 | Agent keys may not do this: registering actions, writing policy. |
not_permitted | 403 | Your role does not allow this, whatever the key's scopes. |
agent_not_found | 404 | The agent this key acts as has been deleted. |
unknown_action | 404 | The gate was asked about an action nobody registered. |
invalid_input | 400 | The gate's input does not match the action's schema; problems names each fault. |
question_not_found | 404 | That question is not on this approval. |
already_answered | 409 | Somebody has already answered that question. |
version_conflict | 409 | expectedVersion is stale: somebody published first. |
approval_pending | 409 | A version of this report is awaiting approval. |
upload_not_found | 404 | The upload ticket is gone — usually because the first publish used it. |
scan_blocked | 422 | The publish scan found something it will not serve. |
public_sharing_denied | 403 | Agent keys cannot create public links. |
invalid_cursor | 400 | A list cursor this API did not issue. |
idempotency_key_reused | 422 | The same Idempotency-Key with a different body. |
idempotency_in_progress | 409 | The first request with this key has not finished. Retry with the same key. |
rate_limited | 429 | Too many requests from this key. |
The gate's refusal is not on this list, because it is not an error: a denied action answers 200 with allowed: false. See The gate.
Plan limits
A call that needs something your plan does not include, or that would take you past a limit, answers 402:
| Code | Status | When |
|---|---|---|
feature_unavailable | 402 | The plan does not include this capability — agent identities, for example. |
plan_required | 402 | Organisation-wide analytics, below the plan that includes it. |
plan_limit | 402 | The plan's number of live reports is reached. Archive one or upgrade. |
storage_limit | 402 | Storing this version would pass the plan's storage. |
seat_limit | 402 | The plan's number of seats is reached. |
limit_reached | 402 | The plan's number of report schedules is reached. A schedule records when an agent should publish; the agent runs elsewhere. |
| Free | Team | Business Starter | Business | Enterprise | |
|---|---|---|---|---|---|
| Approval requests a month | 100 | Unlimited | Unlimited | Unlimited | Unlimited |
| Live reports | 10 | 200 | 500 | 1,000 | Unlimited |
| Seats | 2 | 10 | 20 | 50 | 200 |
| Storage | 1 GB | 25 GB | 50 GB | 100 GB | 500 GB |
| Report schedules | — | 2 | 5 | 10 | 100 |
| Model screens a month | — | — | 500 | 1,000 | 10,000 |
Your organisation's actual limits are on Organisation → Billing; an Enterprise agreement can change them.
Rate limits
Each API key or token may make up to 120 requests a minute. Past that, calls answer 429 rate_limited until the minute is up. The SDKs retry a 429 with backoff, honouring Retry-After, and their polling starts quick and slows down, so an agent waiting on a person does not spend its allowance.
Retrying safely
- Retry
409,429and5xx. Do not retry other4xxwithout changing the request. - Send an
Idempotency-Keyon everyPOSTand keep it across your retries, so a request that arrives twice is done once. The SDKs do this for you. A5xxreleases the key, so your retry is a real attempt. - Give requests an
externalIdso a restarted agent can find what it already asked rather than asking twice. The gate requires one.
Try it on your own agent.
Free to start, no card. Sign up with Google, Microsoft or GitHub, and the first page after hands you an API key.