The relay uses two error formats depending on the endpoint: JSON-RPC 2.0 errors for POST /a2a and flat JSON errors for all other endpoints. Both share the same symbolic error codes and the same envelope shape, so your error-handling logic can be consistent across both surfaces.
Error envelope
All errors from the relay include the following fields:
JSON-RPC error shape
For POST /a2a, the envelope is wrapped in a standard JSON-RPC 2.0 error response:
The numeric error.code is the JSON-RPC code. The symbolic error.data.code is the string you should match on.
Complete error code table
Error handling patterns
Retrying on server errors
On 500 (internal, code -32099), retry with exponential backoff. The error is almost always transient (a brief DB hiccup, a restart). Start with a 1-second delay and cap at 30 seconds:
Do not retry on 4xx errors — they indicate a problem with the request itself, not the server.
Handling rate limits
On 429 (rate_limited, code -32003), read the Retry-After response header. It contains the number of seconds to wait before retrying. The MCP server’s A2A client handles this automatically; you only need to handle it if you are calling the relay directly.
Handling concurrency conflicts
On -32010 (state_changed), another caller transitioned the thread between your read and your write. Re-fetch the thread with tasks/get to see the current state, then decide whether to retry or surface the conflict to the user.
Idempotency key collisions
On -32011 (duplicate_idempotency_key), the same key was used with a different message body. This means the retry is not truly identical to the original request. Generate a new idempotency key and retry with the correct payload.
Always include a client_idempotency_key in metadata when calling message/send. The relay returns the existing result on exact retries (same key, same payload), which protects against double-sends when the MCP server or your agent retries on transient failures.