Errors and limits
The public sales agent reports errors in one of two ways, depending on what you called.
How errors arrive
Tool calls (tools/call) return errors as an AdCP error inside a
normal MCP tool result, with HTTP 200:
{
"jsonrpc": "2.0",
"id": 5,
"result": {
"isError": true,
"structuredContent": {
"adcp_error": {
"code": "UNSUPPORTED_FEATURE",
"message": "create_media_buy is not supported by this agent",
"recovery": "correctable"
}
}
}
}Read result.structuredContent.adcp_error. It always has code,
message and recovery, and it can also have field (the request field
at fault, as a JSON Pointer for VALIDATION_ERROR), issues,
retry_after (seconds, on RATE_LIMITED) or details.
Protocol methods (initialize, tools/list, and other non-tool MCP
methods) have no tool result, so errors come back as a JSON-RPC error
with a matching HTTP status. The AdCP code is in error.data.code:
{
"jsonrpc": "2.0",
"id": 7,
"error": {
"code": -32001,
"message": "Invalid or revoked bearer token",
"data": { "code": "AUTH_REQUIRED" }
}
}Error codes
| Code | Recovery | When you get it |
|---|---|---|
AUTH_REQUIRED | correctable | You sent an Authorization header, but the key is invalid or has been revoked. The agent doesn’t fall back to anonymous access. On a protocol method this is HTTP 401 with WWW-Authenticate: Bearer. |
RATE_LIMITED | transient | You went over your per-minute limit (see below). retry_after and the Retry-After header give the seconds until your window resets. On a protocol method this is HTTP 429. |
UNSUPPORTED_FEATURE | correctable | You called a tool the agent doesn’t expose. Only get_adcp_capabilities, list_creative_formats and get_products are supported (see supported tools). |
VALIDATION_ERROR | correctable | The request doesn’t match the AdCP 3.1 request schema, e.g. an unknown buying_mode or a lower-case country code. field is a JSON Pointer to the first bad value (/filters/countries/0); issues lists every one. |
INVALID_REQUEST | correctable | The request broke a rule the agent enforces: buying_mode: "brief" with an empty brief (field: "brief"), or more than 100 format_ids in list_creative_formats. |
VERSION_UNSUPPORTED | correctable | The request asked for an AdCP version the agent doesn’t serve, e.g. adcp_major_version: 2. details.supported_versions lists what it does serve (["3"]). |
SERVICE_UNAVAILABLE | transient | The agent couldn’t complete the request right now. On a protocol method, or when the agent is paused, this is a JSON-RPC error with HTTP 503 (or 500 for an unexpected failure). |
What each recovery value means
transient: nothing is wrong with your request. Retry automatically with exponential backoff. ForRATE_LIMITED, wait theretry_afterseconds first.correctable: fix the request before sending it again. Resending the same request gets the same error. ForAUTH_REQUIREDa person usually needs to step in, because the key needs replacing.
Transport-level errors
A few errors come from the MCP transport before any tool runs. They have no AdCP code:
| HTTP | What happened |
|---|---|
400 | The body was a JSON-RPC batch (a JSON array). Send one request per POST. |
405 | You used GET or DELETE. The agent only accepts POST. |
406 | Your Accept header didn’t include both application/json and text/event-stream. |
Request validation
Every tool call is checked against the AdCP 3.1 request schema before the
agent does any work. A request that breaks it gets VALIDATION_ERROR
with the failing field, for example:
{
"adcp_error": {
"code": "VALIDATION_ERROR",
"message": "get_products request failed schema validation at /filters/countries/0: must match pattern \"^[A-Z]{2}$\"",
"recovery": "correctable",
"field": "/filters/countries/0",
"issues": [
{
"pointer": "/filters/countries/0",
"message": "must match pattern \"^[A-Z]{2}$\"",
"keyword": "pattern"
}
]
}
}The same issues list is also in details.issues. For an enum field
such as buying_mode, each issue also has allowedValues.
Fix the field and send the request again. INVALID_REQUEST is for
requests that pass the schema but break one of the agent’s own rules
(see the table above).
Rate limits
| Caller | Limit |
|---|---|
| Anonymous | 30 requests per minute, per IP |
| With a key | 300 requests per minute, per key |
- Every request counts, including
tools/listandinitialize. MCP notifications don’t count. - Windows are fixed calendar minutes, so the count resets when the next minute starts.
- Requests with an invalid key count against the anonymous limit for your IP.
- When you’re over the limit, tool calls return
RATE_LIMITED(HTTP200, like every tool error) and protocol methods return HTTP429. Both carry aRetry-Afterheader with the whole seconds until your window resets (1 to 60), and the same number is in the error body:adcp_error.retry_afterfor tool calls,error.data.retry_afterfor protocol methods. Wait that long before retrying.
Safe to retry?
- Retry automatically:
RATE_LIMITED(afterretry_afterseconds) andSERVICE_UNAVAILABLE(with backoff). Every tool the agent exposes is read-only, so a retry can’t create anything twice. - Fix, then retry:
VALIDATION_ERROR,INVALID_REQUEST,VERSION_UNSUPPORTED,UNSUPPORTED_FEATURE, and the transport errors above. - Needs a person:
AUTH_REQUIREDmeans your key needs replacing, and aSERVICE_UNAVAILABLEthat keeps coming back after several minutes of backoff means you should get in touch with the Carbnn team.