Errors
Every failure carries a stable machine string in error.code. Branch on that and never on the message. Two of them are not failures at all: a place we do not cover and a deadline we cannot hit answer 200 with alternatives, because a machine caller treats a 4xx as "give up".
Every error carries a stable machine string in error.code. Branch on that, never on the message — the message is written for a person and may be reworded; the code is a contract.
{
"ok": false,
"error": {
"code": "schema_violation",
"message": "The task spec does not match the capability schema.",
"detail": { "errors": [ { "path": "spec.subject", "message": "required" } ] }
}
}
| Code | HTTP | Means |
|---|---|---|
unauthorized | 401 | Missing or unrecognised API key. |
key_revoked | 401 | This API key has been revoked. |
agent_suspended | 403 | This agent is suspended. |
forbidden_scope | 403 | This key does not carry the required scope. |
rate_limited | 429 | Too many requests. |
quota_exceeded | 429 | Plan quota exhausted for this period. |
invalid_request | 400 | The request could not be understood. |
schema_violation | 422 | The task spec does not match the capability schema. |
capability_unknown | 404 | No such capability. |
capability_refused | 403 | This capability is not offered. |
coverage_unavailable | 200 | We do not operate there yet. |
sla_unavailable | 200 | That deadline is not available. |
insufficient_credit | 402 | Not enough credit on this agent. |
policy_denied | 403 | The agent policy refuses this task. |
plan_required | 402 | This needs a paid plan. The sandbox has the full API and MCP; live dispatch and webhooks do not. |
task_not_found | 404 | No such task for this agent. |
task_not_cancellable | 409 | This task can no longer be cancelled. |
bad_reason | 400 | Not a dispute reason we recognise. |
not_disputable | 409 | There is no result to dispute yet, or the money has already gone back. |
already_disputed | 409 | This task already has an open dispute. |
idempotency_conflict | 409 | That Idempotency-Key was used with a different body. |
method_not_allowed | 405 | Wrong method for this endpoint. |
server_error | 500 | Something went wrong on our side. |
The two that are not errors
coverage_unavailable and sla_unavailable answer 200, not 4xx. A city we do not operate in is information you can act on — the response carries the nearest live city, the distance to it and a waitlist URL — and a machine caller treats a 4xx as "give up" and a 200 as "reason about this". Check feasible on a quote and created on a task rather than relying on the status code alone.
Over MCP
The same codes appear in a tool result with isError: true, so a model sees them and can correct itself. Refusals it can act on come back as ordinary successful results with created: false.