The error shape
Every error has one shape: a code, a message a person can read, and details with machine-readable facts when there are any.
{
"error": {
"code": "mandate_refused",
"message": "This plan is over the key's monthly cap.",
"details": { "reason": "over_monthly_cap", "limit_cents": 500000, "requested_cents": 640000, "over_by_cents": 140000, "escalate": true }
}
}
Over MCP the same code, message and details arrive as the tool's error result.
Codes
| Code | Status | When | What to do |
|---|---|---|---|
bad_request |
400 | A parameter is not valid or not taken; details.problems names each field |
Fix the named fields and send again |
filter_required |
400 | A creator list named no audience and the brand has none on file | Pass match, a segment, q or a filter |
unauthorized |
401 | No key, an unknown key or a revoked key | Send a live key as Authorization: Bearer |
forbidden |
403 | The key lacks the scope this call needs | Add the scope in Developers, or use a key that has it |
mandate_refused |
403 | A write is outside the key's mandate; details.reason says which limit |
Take it to a person at the brand; do not retry |
not_found |
404 | No campaign, plan, line or creator of yours has that id | Check the id; another brand's ids are never found |
method_not_allowed |
405 | The path does not take that method | Use the method on the endpoint's page |
conflict |
409 | The state does not allow it; details.reason says why (over_budget, plan_signed, terms_changed, already_priced and others) |
Read the reason: quote again, remove a creator, or pick another action |
idempotency_conflict |
409 | The same Idempotency-Key was used with a different body |
Use a new key for a new request |
in_progress |
409 | The first request with this Idempotency-Key is still running |
Wait Retry-After seconds and retry with the same key |
unprocessable |
422 | The body is readable but a value cannot be used; details.problems names each |
Fix the named values |
rate_limited |
429 | Over a limit; details.reason and Retry-After say which and for how long |
Wait Retry-After seconds; pace by the RateLimit headers |
internal |
500 | The request failed on our side | Retry with the same Idempotency-Key |
unavailable |
502 or 503 | The service, or this part of it, is not reachable right now; a write still running after 25 seconds answers 503 with Retry-After |
Retry after Retry-After with the same Idempotency-Key, so the write never runs twice |
Reasons worth handling
mandate_refusedreasons:no_mandate,mandate_expired,channel_not_allowed,approval_not_allowed,terms_not_accepted,over_max_plan_total,over_monthly_cap,over_budget. Each carries the limit and how much over.conflictwithover_budget: a creator would take the plan over its campaign's budget. The answer carriesbudget_cents,remaining_centsandover_by_cents, and the plan is left as it was.conflictwithterms_changed: the plan changed since its quote. Quote again and approve with the newterms_fingerprint.rate_limitedwithdaily_campaign_limit: the brand started 3 campaigns in the last 24 hours.details.resets_atsays when the next one can start.
Reporting a failure
Quote the x-request-id header of the failed response. Any key can send a report with POST /feedback; it reaches the team directly.