5.5

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.

Fig. 5.3TaskNotFoundError in two JSON bindingscomparison
One error in two JSON bindings Two columns. Left, a JSON-RPC GetTask for task 00000000 posted to /a2a/jsonrpc gets HTTP 200 OK with an error member: code -32001, message Task not found, and a data array. Right, the same lookup as GET /a2a/rest/tasks/00000000 gets HTTP 404 Not Found with a google.rpc.Status body: code 404, status NOT_FOUND, the same message, and a details array. Lines from data and details lead to one shared box, the google.rpc.ErrorInfo with reason TASK_NOT_FOUND, domain a2a-protocol.org, and the task id in metadata. A · JSON-RPC B · HTTP+JSON POST /a2a/jsonrpc A2A-Version: 1.0 "jsonrpc": "2.0", "id": 1, "method": "GetTask", "params": {"id": "00000000"} HTTP/1.1 200 OK Content-Type: application/json "jsonrpc": "2.0", "id": 1, "error": { "code": -32001, "message": "Task not found", "data": [ ErrorInfo ] GET /a2a/rest/tasks/00000000 A2A-Version: 1.0 no body: the id is in the path HTTP/1.1 404 Not Found Content-Type: application/a2a+json "error": { "code": 404, "status": "NOT_FOUND", "message": "Task not found", "details": [ ErrorInfo ] data[0] details[0] google.rpc.ErrorInfo, the same object in both bindings "@type": "type.googleapis.com/google.rpc.ErrorInfo", "reason": "TASK_NOT_FOUND", "domain": "a2a-protocol.org", "metadata": {"taskId": "00000000"}
The same TaskNotFoundError arrives as HTTP 200 with code -32001 in JSON-RPC and as HTTP 404 in HTTP+JSON, carrying the same ErrorInfo. Left, GetTask over JSON-RPC from the error capture. Right, the same lookup over HTTP+JSON from the HTTP+JSON capture. The box at the bottom is the detail both replies carry. Ids shortened to 8 characters.

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:

a message without messageId, refused with a BadRequest detailhttpFull log
    "message": {
      "role": "ROLE_USER",
…
HTTP/1.1 200 OK
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"
The request is cut to the start of its message, which has no messageId.

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:

a cancel of a completed task over HTTP+JSONhttpFull log
POST /a2a/rest/tasks/bbfb9ea5-50ea-4441-b991-2a8ba0d6f3f3:cancel HTTP/1.1
…
HTTP/1.1 400 Bad Request
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"
The request headers and body are cut. Over JSON-RPC this error is code -32002, as in 10-cancel.http.

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:

SDKJSON-RPC errorsHTTP+JSON errors
a2a-python 1.2.2application/jsonapplication/json
a2a-goapplication/jsonapplication/json
a2a-javaapplication/jsonapplication/json
a2a-jsapplication/jsonapplication/a2a+json
a2a-dotnetapplication/jsonapplication/problem+json, a problem-details body
a2a-rsapplication/jsonapplication/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, HTTP 500 or 503, or a dropped connection: retry with backoff, and honor Retry-After.
  • HTTP 401: get a fresh credential, then retry.
  • -32600, -32602, -32005, -32009, and their 400 forms: fix the request before you send it again.
  • -32001: stop, because the id is wrong, purged, or hidden from you.
  • -32002, or -32004 on a terminal task: start a new task, as CancelTask and terminal states shows.
  • -32003, -32007, or -32004 from 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