Learn

A2A protocol bindings: JSON-RPC, gRPC, and HTTP+JSON

What an A2A protocol binding is, how JSON-RPC, gRPC and HTTP+JSON map methods and errors, how Agent Cards declare them, and how version negotiation works.

A protocol binding is the concrete wire format for A2A. Version 1.0 of the specification defines every operation once, in abstract terms, with a2a.proto as the normative data model. Bindings then map those operations onto a transport: method names or URLs, how headers travel, how errors are encoded, and how streaming works. The specification defines three standard bindings: JSON-RPC 2.0 over HTTP, gRPC over HTTP/2, and HTTP+JSON, a REST-style mapping. Anyone can define more.

An agent declares its bindings in the supportedInterfaces list of its Agent Card, one entry per endpoint, binding and protocol version. Every binding an agent offers must provide the same operations with the same behavior, the same error meanings and the same authentication (section 5.1). So the choice between them comes down to your infrastructure.

What a binding covers

JSON-RPC gRPC HTTP+JSON
Transport JSON-RPC 2.0 over HTTP(S) gRPC over HTTP/2 with TLS HTTP(S)
Operation naming PascalCase methods, such as SendMessage RPCs of the A2AService service HTTP verb plus resource path, such as POST /message:send
Payload application/json Protocol Buffers 3 application/a2a+json (SHOULD)
Service parameters HTTP headers gRPC metadata, lowercased keys HTTP headers
Errors JSON-RPC error object with a data array google.rpc.Status with details HTTP status plus a google.rpc.Status JSON body
Streaming Server-Sent Events, each event a JSON-RPC response Server-streaming RPC Server-Sent Events, each event a StreamResponse
Spec section 9 10 11

Service parameters are the protocol’s horizontal settings. The two standard ones are A2A-Version and A2A-Extensions (section 3.2.6). Their names are case-insensitive, and a list such as several extension URIs goes in one comma-separated value.

The HTTP+JSON media type changed during the 1.0 line. The v1.0.1 patch release made application/a2a+json the preferred content type for that binding. JSON-RPC stays on application/json.

Method mapping

Section 5.3 maps each operation across the three bindings.

Operation JSON-RPC method gRPC method HTTP+JSON endpoint
Send message SendMessage SendMessage POST /message:send
Send streaming message SendStreamingMessage SendStreamingMessage POST /message:stream
Get task GetTask GetTask GET /tasks/{id}
List tasks ListTasks ListTasks GET /tasks
Cancel task CancelTask CancelTask POST /tasks/{id}:cancel
Subscribe to task SubscribeToTask SubscribeToTask POST /tasks/{id}:subscribe (see inconsistencies)
Create push config CreateTaskPushNotificationConfig same POST /tasks/{id}/pushNotificationConfigs
Get push config GetTaskPushNotificationConfig same GET /tasks/{id}/pushNotificationConfigs/{configId}
List push configs ListTaskPushNotificationConfigs same GET /tasks/{id}/pushNotificationConfigs
Delete push config DeleteTaskPushNotificationConfig same DELETE /tasks/{id}/pushNotificationConfigs/{configId}
Get extended Agent Card GetExtendedAgentCard same GET /extendedAgentCard

JSON-RPC and gRPC share method names, which keeps the two close. HTTP+JSON has three extra rules:

  • GET and DELETE requests carry parameters in the path or the query string, with camelCase names that match the JSON fields, such as GET /tasks/{id}?historyLength=10 (section 11.5). Nested objects can’t go in a query string, so operations that need them use POST.
  • The proto adds a tenant form of every path, such as POST /{tenant}/message:send, for agents that share one endpoint.
  • Version 1.0 dropped the /v1 prefix that 0.3 put in front of these paths. A version can still be part of the base URL if the agent owner wants one.

Error mapping

Every A2A error has a code, a message and optional details, and each binding maps them to its native form. Section 5.4 gives the table.

A2A error JSON-RPC gRPC status HTTP status
TaskNotFoundError -32001 NOT_FOUND 404 Not Found
TaskNotCancelableError -32002 FAILED_PRECONDITION 400 Bad Request
PushNotificationNotSupportedError -32003 FAILED_PRECONDITION 400 Bad Request
UnsupportedOperationError -32004 FAILED_PRECONDITION 400 Bad Request
ContentTypeNotSupportedError -32005 INVALID_ARGUMENT 400 Bad Request
InvalidAgentResponseError -32006 INTERNAL 500 Internal Server Error
ExtendedAgentCardNotConfiguredError -32007 FAILED_PRECONDITION 400 Bad Request
ExtensionSupportRequiredError -32008 FAILED_PRECONDITION 400 Bad Request
VersionNotSupportedError -32009 FAILED_PRECONDITION 400 Bad Request

JSON-RPC also keeps its standard codes: -32700 parse error, -32600 invalid request, -32601 method not found, -32602 invalid params and -32603 internal error. The A2A codes sit in the range JSON-RPC 2.0 reserves for implementation-defined server errors. Authentication and authorization failures have no A2A code. Section 3.3.2 suggests 401 or UNAUTHENTICATED and 403 or PERMISSION_DENIED, and leaves JSON-RPC to a custom error.

Because several errors share FAILED_PRECONDITION or 400, the status alone is ambiguous. gRPC and HTTP+JSON responses must include a google.rpc.ErrorInfo detail whose reason names the error in upper snake case without the “Error” suffix, such as TASK_NOT_FOUND, with the domain a2a-protocol.org. JSON-RPC carries detail objects in error.data. The specification says implementations should use ErrorInfo there, and its A2A error example does.

Here is one error in all three forms. The JSON-RPC response is real, from Emissar’s agent on 2026-09-26, after a SendMessage with the header A2A-Version: 0.5. The agent returned it with HTTP status 200.

{
  "jsonrpc": "2.0",
  "id": 9,
  "error": {
    "code": -32009,
    "message": "Protocol version 0.5 is not supported. Supported: 1.0, 0.3",
    "data": [
      {
        "@type": "type.googleapis.com/google.rpc.ErrorInfo",
        "reason": "VERSION_NOT_SUPPORTED",
        "domain": "a2a-protocol.org"
      }
    ]
  }
}

Illustrative: the same error over HTTP+JSON, sent with status 400 Bad Request.

{
  "error": {
    "code": 400,
    "status": "FAILED_PRECONDITION",
    "message": "Protocol version 0.5 is not supported. Supported: 1.0, 0.3",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.ErrorInfo",
        "reason": "VERSION_NOT_SUPPORTED",
        "domain": "a2a-protocol.org"
      }
    ]
  }
}

Illustrative: the same error over gRPC, in Protocol Buffers text form.

status {
  code: FAILED_PRECONDITION
  message: "Protocol version 0.5 is not supported. Supported: 1.0, 0.3"
  details: [
    {
      type: "type.googleapis.com/google.rpc.ErrorInfo"
      reason: "VERSION_NOT_SUPPORTED"
      domain: "a2a-protocol.org"
    }
  ]
}

Declaring bindings in the Agent Card

Each supportedInterfaces entry has four fields.

Field Required Notes
url Yes Where this interface listens
protocolBinding Yes JSONRPC, GRPC, HTTP+JSON, or a URI for a custom binding
protocolVersion Yes Major.Minor, such as "1.0"
tenant No Opaque routing value; if set, clients send it in every request

The list is ordered by preference. A client must pick the first entry it supports, use that entry’s URL, and send the entry’s tenant if it has one (section 8.3.2). The same URL may appear more than once if several bindings share an endpoint. An agent can also list the same binding twice with different protocol versions.

Illustrative: an agent that offers all three bindings on 1.0.

{
  "supportedInterfaces": [
    { "url": "https://agent.example.com/a2a/v1", "protocolBinding": "JSONRPC", "protocolVersion": "1.0" },
    { "url": "agent.example.com:443", "protocolBinding": "GRPC", "protocolVersion": "1.0" },
    { "url": "https://agent.example.com/a2a/rest", "protocolBinding": "HTTP+JSON", "protocolVersion": "1.0" }
  ]
}

A custom binding should be named by a URI, and a breaking change to it needs a new URI (section 5.8). It must implement every core operation, keep the data model, and document how it carries service parameters, errors, streaming and authentication (section 12). If it can’t stream, it must say so. The A2A project hosts custom bindings under a cpb- repository prefix, with experimental ones as experimental-cpb-.

Version negotiation with A2A-Version

A protocol version is Major.Minor. Patch numbers don’t affect compatibility and must not be considered when clients and servers negotiate (section 3.6).

  • Clients must send A2A-Version with every request. The exception is 0.3 clients, because 0.3 had no such header. A client may send the version as a request parameter instead of a header.
  • Servers must process a request with the semantics of the version it names. If the interface doesn’t support that version, the server must return VersionNotSupportedError. A missing or empty value must be read as 0.3.
  • SDKs must help clients manage versions. A client that needs newer features should request that version explicitly and not fall back to an older one automatically, which would lose features silently.

JSON-RPC and HTTP+JSON send the version as an HTTP header, A2A-Version: 1.0. gRPC sends it as the metadata key a2a-version. An agent that serves 0.3 and 1.0 side by side can list one interface per version, at the same URL or different ones. The 0.3 to 1.0 migration guide shows how one endpoint answers both.

Known inconsistencies in the specification

The text on main disagrees with itself in a few places. As of 2026-09-26:

Topic What the text says What else says otherwise
gRPC interface URL The sample Agent Card in section 8.5 uses https://georoute-agent.example.com/a2a/grpc The AgentInterface.url comment in a2a.proto asks for hostname:port, such as grpc.example.com:443
HTTP+JSON subscribe verb Sections 5.3 and 11.3.2 say POST /tasks/{id}:subscribe The HTTP annotation in a2a.proto says get
HTTP+JSON error body Section 6.4’s version example returns application/problem+json in the RFC 9457 shape Section 11.6 and What’s New in v1.0 specify the google.rpc.Status JSON shape

Section 1.4 makes a2a.proto the single authoritative definition of the data objects and messages. For the gRPC URL, that points to host:port. The subscribe verb is less clear, since the verb comes from the binding sections as much as the proto. A server can accept both verbs on that path until the text is fixed; clients generated from the proto will send GET.

How to choose

Binding Fits well when Watch for
JSON-RPC You want one URL, plain HTTP tooling and the shortest path to a working agent All calls go to one URL as POST bodies, so HTTP caches and verb- or path-based gateway rules see little
gRPC Your services already use gRPC and Protocol Buffers, and you want generated, typed clients Needs HTTP/2 with TLS (section 10.1); metadata keys arrive lowercased
HTTP+JSON Your API gateway, WAF or logging works on HTTP verbs, paths and status codes Query-parameter mapping rules, and the subscribe verb conflict above

Start with one binding and add others when a real caller needs them. The functional equivalence rule means a second binding is a transport adapter over the same logic, and clients are told to fall back between declared bindings (section 5.2). Emissar’s own public agent declares a single interface: JSON-RPC, protocol version 1.0, at https://emissar.ai/a2a/v1.

Whichever binding you pick, push notifications don’t use it. Webhook calls always use plain HTTP with the JSON payloads of the HTTP+JSON binding (section 3.5.1).

Questions

Does an A2A agent have to support all three bindings?
No. An agent must declare every binding it supports in supportedInterfaces, and each one must offer the same operations and behavior. Clients may pick any declared binding and should be able to fall back to another.
Do JSON field names change between bindings?
No. JSON-RPC and HTTP+JSON both use camelCase field names and ProtoJSON enum strings such as TASK_STATE_COMPLETED. gRPC sends the same proto messages in binary Protocol Buffers.
Which binding carries push notifications?
None of them. Webhook calls always use plain HTTP with the JSON payloads of the HTTP+JSON binding, whichever binding the client used to create the task.

Sources

  1. A2A Protocol Specification (sections 3.2.6, 3.3, 3.6, 5, 8.3, 9, 10, 11 and 12) (accessed )
  2. A2A protocol definition (a2a.proto): A2AService HTTP annotations, AgentInterface (accessed )
  3. What's new in A2A v1.0 (A2A documentation) (accessed )
  4. A2A v1.0.1 release notes (prefer application/a2a+json in the HTTP binding) (accessed )
  5. A2A Extension and Protocol Binding Governance (accessed )
  6. JSON-RPC 2.0 Specification (accessed )
  7. Emissar Agent Card (live example agent) (accessed )