Errors and retries
Tell the agent what failed, where, and whether retrying can help. Skill errors use the same aap.error object across all three bindings. Transport and authentication errors keep their own protocol semantics.
One automotive error
{
"type": "aap.error",
"error_id": "error_example_001",
"code": "UNSUPPORTED_FILTER",
"message": "This deployment does not support features_all.",
"retryable": false,
"details": {
"errors": [
{ "instanceLocation": "/filters/features_all", "keyword": "capability", "error": "Filter is not advertised." }
],
"hint": "Read the published search capabilities before forming another query."
},
"created_at": "2026-10-24T15:00:00Z"
}
type, error_id, code, message, retryable and created_at are required. details is optional except where a code requires diagnostics or retry timing. Correlation IDs must be suitable for support without exposing shopper identity.
Validation errors MUST list every currently detectable problem in details.errors[]. Each item uses a JSON Pointer instanceLocation, a constraint keyword and a safe explanation in error. Never echo customer values, credentials or internal stack traces. Include a concrete correction when possible.
Three envelopes
| Binding | Skill failure |
|---|---|
| MCP | Completed tool result, isError: true, typed error in structuredContent and matching serialized JSON text in content |
| A2A | JSON-RPC error, standard ErrorInfo first in error.data[], typed error inside google.protobuf.Struct.value |
| PAP OpenAPI | Direct aap.error JSON body with the mapped HTTP status |
An unknown MCP tool, malformed protocol request, missing token, DPoP challenge or unsupported upstream version is a protocol failure. Do not pretend an automotive skill ran. MCP authorization discovery and PAP OAuth challenges are described in their binding pages.
A2A detail encoding
For skill errors, the first google.rpc.ErrorInfo has reason equal to the AAP code and domain: "autoagentprotocol.org". Its string-valued metadata includes code, error_id, retryable and created_at. Duplicate the code in metadata for SDKs that omit reason.
The AAP payload is a separate standard Any detail with @type: "type.googleapis.com/google.protobuf.Struct" and value.type: "aap.error". Clients locate it by type, never by array position, validate its value, and ignore unrelated details. Other recognized detail objects may follow.
Complete A2A throttle response
{
"jsonrpc": "2.0",
"id": "req-rate-limited",
"error": {
"code": -32000,
"message": "Per-key rate limit exceeded.",
"data": [
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "RATE_LIMITED",
"domain": "autoagentprotocol.org",
"metadata": {
"code": "RATE_LIMITED",
"error_id": "err_01HZ9RATE01",
"retryable": "true",
"created_at": "2026-04-30T10:16:00Z"
}
},
{
"@type": "type.googleapis.com/google.protobuf.Struct",
"value": {
"type": "aap.error",
"error_id": "err_01HZ9RATE01",
"code": "RATE_LIMITED",
"message": "Per-key rate limit exceeded.",
"retryable": true,
"details": {
"retry_after_ms": 30000
},
"created_at": "2026-04-30T10:16:00Z"
}
}
]
}
}
Error code reference
code | Meaning | JSON-RPC | retryable default |
|---|---|---|---|
UNSUPPORTED_SKILL | The agent does not implement the skill identified by the typed request payload; the SendMessage method itself is supported. | -32602 (Invalid params) | false |
UNSUPPORTED_FILTER | A known filter or feature code is not supported in this deployment; nothing was silently ignored. | -32602 (Invalid params) | false |
UNSUPPORTED_SORT | The requested sort field is not advertised. | -32602 (Invalid params) | false |
INVALID_CURSOR | Continuation is expired, malformed, belongs to another scope, or no longer matches the request. | -32602 (Invalid params) | false |
SCHEMA_VALIDATION_FAILED | Request body fails JSON Schema validation. | -32602 (Invalid params) | false |
MISSING_REQUIRED_FIELD | A specifically required field is absent. | -32602 (Invalid params) | false |
INVALID_CONDITION | vehicle_of_interest.condition is in the trade-in vocabulary, or trade_in.condition is in the sale-condition vocabulary. | -32602 (Invalid params) | false |
DEALER_NOT_FOUND | The requested seller is unknown or inaccessible on this endpoint. | -32000 (Server error) | false |
VEHICLE_NOT_FOUND | The supplied vin / stock / vehicle_id does not match any listing. | -32000 (Server error) | false |
VEHICLE_UNAVAILABLE | The vehicle exists but its status is no longer one of available | intransit | pending. | -32000 (Server error) | false |
CONTACT_CONSENT_REQUIRED | consent is missing from a lead submission. | -32000 (Server error) | false |
INVALID_CONSENT | consent is present but malformed, expired, its scope does not cover the called skill, or a contact channel is not authorized. | -32000 (Server error) | false |
APPOINTMENT_TIME_UNAVAILABLE | The submission was rejected because no appointment time could be accepted. | -32000 (Server error) | false |
IDEMPOTENCY_CONFLICT | An idempotency_key was reused with a different request payload. | -32000 (Server error) | false |
RATE_LIMITED | Client has exceeded the dealer's rate limit. | -32000 (Server error) | true |
INTERNAL_ERROR | Unhandled dealer-side error. | -32603 (Internal error) | true |
These are the A2A numeric mappings. MCP keeps them as typed tool errors; PAP maps unsupported skill, unknown vehicle and unknown or inaccessible dealer (DEALER_NOT_FOUND) to 404, schema/filter/sort/cursor problems to 422, consent to 403, availability/appointment/idempotency conflict to 409, throttling to 429 and internal failure to 500.
Important distinctions
UNSUPPORTED_FILTERmeans a known filter or feature code is unsupported here. An unknown field isSCHEMA_VALIDATION_FAILED. The server MUST NOT quietly ignore either.INVALID_CURSORmeans restart the search; never continue from a fabricated offset. A cursor is scoped to its original request, tenant and authorization context.CONTACT_CONSENT_REQUIREDmeans the grant is missing.INVALID_CONSENTmeans the provided grant is unusable, expired, future-dated or does not permit the required channel.VEHICLE_NOT_FOUNDmeans no matching listing;VEHICLE_UNAVAILABLEmeans a previously known listing cannot be offered. Refresh the search and tell the buyer.- An accepted lead with a declined appointment returns
lead.submit.responsewith both outcomes.APPOINTMENT_TIME_UNAVAILABLEmust not conceal an accepted lead. IDEMPOTENCY_CONFLICTmeans the same key was reused with different content. Do not rotate the key merely to overcome an uncertain previous write; first reconcile it.
Protocol-level A2A failures
| A2A error | JSON-RPC | Cause |
|---|---|---|
ExtensionSupportRequiredError | -32008 | The exact AAP extension was not activated. |
VersionNotSupportedError | -32009 | Supplied A2A-Version is unsupported; an explicitly empty value means 0.3. Omission on a contract-pinned AAP endpoint uses 1.0. |
ContentTypeNotSupportedError | -32005 | Unsupported input media type, or no acceptable output alternative. |
UnsupportedOperationError | -32004 | Unsupported A2A operation. |
PushNotificationNotSupportedError | -32003 | Push notification operations are not supported. |
These use A2A's standard typed error.data array and need no aap.error. See headers and capabilities.
Activation recovery
An activation error MUST name both the exact required extension URI and A2A-Extensions in its message. Its ErrorInfo uses domain: "a2a-protocol.org", reason: "EXTENSION_SUPPORT_REQUIRED" and matching string metadata extensionUri / requiredHeader. Those two metadata keys are AAP-defined recovery hints.
{
"jsonrpc": "2.0",
"id": "req-3",
"error": {
"code": -32008,
"message": "Activate the supported AAP profile with A2A-Extensions: https://autoagentprotocol.org/extensions/aap/v3.0",
"data": [
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "EXTENSION_SUPPORT_REQUIRED",
"domain": "a2a-protocol.org",
"metadata": {
"extensionUri": "https://autoagentprotocol.org/extensions/aap/v3.0",
"requiredHeader": "A2A-Extensions"
}
}
]
}
}
A buyer retries with that header only if it already implements the advertised contract. An error hint cannot implement the extension for the client. On a contract-pinned AAP endpoint, a missing header selects the configured contract; a supplied conflicting list still fails. The server MUST resolve and validate the contract before skill execution; an SDK import or card flag alone does not enforce it.
SDK error handling
Preserve the raw protocol error before an SDK converts it. Extract and validate the typed AAP payload yourself; an exception class is not a retry policy.
The pinned @a2a-js/sdk 1.3.0 preserves the standard detail array in this repository's real HTTP interoperability checks. Some older client adapters discard unknown detail types or codes. Verify the exact client version you ship with validation, consent and rate-limit failures. MCP clients must preserve structured error results even though isError is true.
Retry without changing the outcome
Honor the returned retryable value. For a correctable input error, change the request deliberately; repeating identical invalid input cannot help. For transient failures, use bounded backoff.
A typed RATE_LIMITED MUST include details.retry_after_ms; an HTTP 429 MUST include Retry-After. Wait at least the applicable minimum, including when Retry-After is an HTTP date. Do not treat an HTTP 200 containing an MCP tool error as success.
For a lead, preserve the original payload and idempotency key across a timeout, retry, Session renewal or securely correlated binding change. Re-authentication produces fresh proofs, not a new business request. A non-retryable response does not authorize a new submission. See safe lead retries.