Errors
Nine A2A errors map into three bindings, and because their status codes collide, the ErrorInfo reason in the details is what tells them apart.
The planner asks test-runner for a task id that does not exist, once over each JSON binding. JSON-RPC answers HTTP/1.1 200 OK with code -32001, and HTTP+JSON answers HTTP/1.1 404 Not Found. Both carry the same ErrorInfo, with the reason TASK_NOT_FOUND, as figure 5.3 shows.
When you finish this section, you can read an error in any binding, name the A2A error behind it, and choose what to do next.
The detail that names the error
The specification defines nine A2A errors and maps each one into every binding. JSON-RPC numbers them -32001 to -32009, HTTP answers 400 for seven, and gRPC answers FAILED_PRECONDITION for six, as the error table lists.
Every error carries a code, a message, and optional details, each detail naming its type in @type. For A2A errors the deciding detail is a google.rpc.ErrorInfo:
Its reason is the error's name in upper snake case without the Error suffix, such as TASK_NOT_FOUND, and its domain is a2a-protocol.org. The gRPC binding carries the same MUST, and JSON-RPC only a SHOULD. The metadata is free-form: the kit fills it, and the reference SDK leaves it empty, as the kit README lists.
JSON-RPC errors arrive with HTTP 200
A JSON-RPC error is an ordinary response with HTTP 200. Its error member holds code, message, and data where result would be. The five standard JSON-RPC codes, -32700 and -32600 to -32603, keep their meaning.
Validation errors carry a different detail type, a google.rpc.BadRequest that names the field:
"message": {
"role": "ROLE_USER",
…
Content-Type: application/json
{
"jsonrpc": "2.0",
"id": 4,
"error": {
"code": -32602,
"message": "Invalid parameters",
"data": [
{
"@type": "type.googleapis.com/google.rpc.BadRequest",
"fieldViolations": [
{
"field": "message.messageId",
"description": "messageId is required"Do not match on message texts: the reference SDK answers the same case with "Validation failed" and names the field message.message_id.
Authentication can fail before JSON-RPC runs, with HTTP 401 as the specification's example, so read the status first, as Security schemes shows.
HTTP+JSON errors are google.rpc.Status
An HTTP+JSON error uses the HTTP status and a JSON google.rpc.Status. Its error object repeats the status in code, names the gRPC status in status, and adds message and details:
…
Content-Type: application/a2a+json
{
"error": {
"code": 400,
"status": "FAILED_PRECONDITION",
"message": "Task cannot be canceled",
"details": [
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "TASK_NOT_CANCELABLE",
"domain": "a2a-protocol.org",
"metadata": {
"taskId": "bbfb9ea5-50ea-4441-b991-2a8ba0d6f3f3",
"state": "TASK_STATE_COMPLETED"Two samples in the specification still show application/problem+json bodies in the older RFC 9457 style (conflict D13). PR #1641 and PR #1689 replace those samples, and both are still open. The migration guide gives the type as application/json, where §11.6 shows application/a2a+json. The SDKs send all three, and all answer JSON-RPC errors with HTTP 200:
| SDK | JSON-RPC errors | HTTP+JSON errors |
|---|---|---|
| a2a-python 1.2.2 | application/json | application/json |
| a2a-go | application/json | application/json |
| a2a-java | application/json | application/json |
| a2a-js | application/json | application/a2a+json |
| a2a-dotnet | application/json | application/problem+json, a problem-details body |
| a2a-rs | application/json | application/problem+json |
Send application/a2a+json, and accept all three types when you read. Parse the body as google.rpc.Status whatever the type says. The exception is a2a-dotnet at this release, whose problem-details body carries no ErrorInfo. The gRPC binding puts the same three parts in status.code, status.message, and status.details.
What to retry
UnsupportedOperationError covers three problems with one code, -32004, and one reason, UNSUPPORTED_OPERATION. The card lacks streaming or the extended card, or the task is terminal, as in 10-cancel.http. Only message and metadata tell the cases apart, and the specification fixes neither. So when a task operation gets -32004, read the task with GetTask before you decide.
Most A2A errors describe the request or the task, so resending the same bytes gets the same answer:
-32603, HTTP500or503, or a dropped connection: retry with backoff, and honorRetry-After.- HTTP
401: get a fresh credential, then retry. -32600,-32602,-32005,-32009, and their400forms: fix the request before you send it again.-32001: stop, because the id is wrong, purged, or hidden from you.-32002, or-32004on a terminal task: start a new task, as CancelTask and terminal states shows.-32003,-32007, or-32004from a capability check: read the card again.
A retried SendMessage can start a second task, because duplicate detection by messageId is optional. Send the same messageId anyway, as Message explains.
A2A protocol 1.0.1 · 3303592 · 2026-05-28 · Edition 2026.10