A2A Interoperability Reference: Bindings, Versions, Wire Differences

A2A Interoperability Reference: Bindings, Versions, Wire Differences

Two A2A implementations interoperate when they agree on four things: which interface to call, which protocol version to speak, how each operation looks in the chosen binding, and how errors are carried back. This page is the lookup for all four. It is built for the moment something does not connect, or when you must support v0.3 and v1.0 side by side.

Everything is checked against A2A v1.0.1 (a2a.proto and the specification at that tag), compared with v0.3.0 (specification/grpc/a2a.proto, specification/json/a2a.json, and that tag’s specification). The v1.0.1 service definition is identical to main at commit 98279d6. Where the specification and the proto disagree, the proto wins (section 1.4); the cases are listed in section 2 .

The objects inside the messages are covered elsewhere: Task Runtime Guide for states and events, Agent Card Proto Field Numbers for card fields, and A2A Error Codes for the nine errors.

1. Will the two sides connect?

Check in this order. The first failing step is the cause.

StepCheckRuleIf it fails
1. Pick an interfaceRead supportedInterfaces in order and take the first entry whose protocolBinding you support and whose protocolVersion you speak. Call that entry’s urlSections 8.3.2, 3.6.2, and 3.6.3No usable entry: stop. The specification defines no error for this; section 5.2 only asks clients to implement fallback to another protocol
2. Send the versionSend A2A-Version with the Major.Minor of that entry’s protocolVersion, such as 1.0, on every requestSections 3.6.1 and 14.2.1VersionNotSupportedError. An empty value is read as 0.3 (section 3.6.2)
3. Carry the tenantCopy the entry’s tenant into the tenant field of every request message; omit the field when the entry has noneSection 8.3.2The specification defines no error for a wrong value; the server decides. See Multi-Tenancy
4. Check capabilitiesRead capabilities on the card before you stream, use push notifications, or fetch the extended cardSection 3.3.4UnsupportedOperationError, PushNotificationNotSupportedError, or ExtendedAgentCardNotConfiguredError
5. Declare extensionsSend the extension URIs you support in A2A-Extensions, comma-separatedSections 3.2.6 and 3.3.4ExtensionSupportRequiredError when the card marks an extension required: true and you did not declare it
6. Read the error by bindingDecide by the A2A error, not by the HTTP or gRPC status aloneSections 5.4 and 11.6See section 3

How a server reads A2A-Version

Header sentInterface exposesResultRule
Absent or empty1.0 onlyVersionNotSupportedError, because an empty value is read as 0.3Section 3.6.2
Absent or empty0.3Processed with 0.3 semanticsSection 3.6.2
1.01.0Processed with 1.0 semanticsSection 3.6.2
0.31.0 onlyVersionNotSupportedErrorSection 3.6.2
0.3One 0.3 and one 1.0 interface, on the same or on different URLsEach interface applies its own version; call the one that declares 0.3Section 3.6.2
2.0, or any value no interface declaresAnyVersionNotSupportedErrorSection 3.6.2
1.0.11.0Not specified. Patch numbers must not take part in negotiation (section 3.6), but the registered header value is Major.Minor (section 14.2.1). Send 1.0Sections 3.6 and 14.2.1

2. The operations across the three bindings

JSON-RPC and gRPC use the same name. The HTTP+JSON routes and the return types come from the HTTP annotations and signatures of A2AService in a2a.proto at v1.0.1.

OperationJSON-RPC method and gRPC RPCHTTP+JSON routeReturns
Send a messageSendMessagePOST /message:sendSendMessageResponse: a task or a message
Send and streamSendStreamingMessagePOST /message:streamStream of StreamResponse
Get a taskGetTaskGET /tasks/{id}Task
List tasksListTasksGET /tasksListTasksResponse
Cancel a taskCancelTaskPOST /tasks/{id}:cancelTask
Subscribe to a taskSubscribeToTaskGET /tasks/{id}:subscribeStream of StreamResponse
Create a push configCreateTaskPushNotificationConfigPOST /tasks/{task_id}/pushNotificationConfigsTaskPushNotificationConfig
Get a push configGetTaskPushNotificationConfigGET /tasks/{task_id}/pushNotificationConfigs/{id}TaskPushNotificationConfig
List push configsListTaskPushNotificationConfigsGET /tasks/{task_id}/pushNotificationConfigsListTaskPushNotificationConfigsResponse
Delete a push configDeleteTaskPushNotificationConfigDELETE /tasks/{task_id}/pushNotificationConfigs/{id}Empty
Get the extended cardGetExtendedAgentCardGET /extendedAgentCardAgentCard

Notes:

  • Every route also exists under a /{tenant} prefix, for example POST /{tenant}/message:send. The proto declares it as an additional binding; the specification’s route list (section 11.3) shows only the tenant-less forms. The request message of each of the eleven operations has a tenant field.
  • The proto names the path variables task_id and id; the specification’s table (section 5.3) writes {id} and {configId}. The URL shape is the same.
  • GET and DELETE have no body. Their parameters travel in the path or in camelCase query parameters, such as pageSize and contextId (section 11.5).

Where the documents disagree

TopicThe documentsFollow
SubscribeToTask over HTTP+JSONSections 5.3 and 11.3.2 say POST /tasks/{id}:subscribe; the proto annotation says GETThe proto. Handling is in the Task Runtime Guide
JSON-RPC methodThe base request structure (section 9.3) shows "method": "category/action", the v0.3 pattern. Sections 9.1 and 9.4 specify PascalCase namesSections 9.1 and 9.4: SendMessage, GetTask, and so on
A2A-Version in examplesThe examples in sections 3.2.6, 9.2, 10.2, and 11.2 send 0.3; section 3.6.1 and the 1.0 card examples use 1.0Send the protocolVersion of the interface you call. Pasting the examples against a 1.0-only interface returns VersionNotSupportedError
Header registrationSection 14.2.1 names section 3.2.5 as the defining section; the service parameters are in section 3.2.6Section 3.2.6
Tenant routesSection 11.3 lists no /{tenant} routes; the proto has themThe proto

3. What differs between the bindings

A server that supports several bindings must return equivalent results and map errors consistently (section 5.1). The transport details still differ.

AspectJSON-RPCgRPCHTTP+JSONSource
CallPOST to the interface url with {jsonrpc, id, method, params}; params is the request messageA unary or server-streaming RPC on A2AServiceOne route per operation (section 2 )Sections 9.4, 10.3, and 11.3
ParametersIn paramsIn the request messageJSON body for POST; path and camelCase query for GET and DELETESections 9.4 and 11.5
Service parametersHTTP headers, case-insensitivegRPC metadata; keys arrive lowercase, such as a2a-versionHTTP headers, case-insensitiveSections 9.2, 10.2, and 11.2
Content typeapplication/jsonNot specifiedapplication/a2a+json (SHOULD)Sections 9.1 and 11.1
Tenantparams.tenanttenant in the messageThe tenant field, or the /{tenant}/... route prefixSection 8.3.2 and the proto
Error carriererror.code, error.message, and error.data, an array of typed objectsstatus.code, status.message, and status.detailsHTTP status, plus error.code, error.message, and error.detailsSections 9.5, 10.6, and 11.6
Which A2A error it isThe code, -32001 to -32009, is unique per errorSix errors share FAILED_PRECONDITION; read ErrorInfo.reasonSeven errors share 400; read ErrorInfo.reasonSections 5.4, 10.6, and 11.6
StreamingSSE; each data: line is a complete JSON-RPC response whose result is a StreamResponseA server-streaming RPC of StreamResponseSSE; each data: line is a bare StreamResponseSections 9.4.2, 10.7, and 11.7

Two consequences follow. First, a client that parses JSON-RPC stream lines must unwrap result; a client that parses REST stream lines must not. Second, an HTTP 404 or 400 identifies an A2A error only when the body carries an ErrorInfo with domain set to a2a-protocol.org; a bare 404 from a proxy or a wrong route does not.

4. From v0.3 to v1.0.1 on the wire

Names and routes

For v0.3.0 the gRPC names and REST routes are read from the proto. The JSON-RPC names are the method constants in the JSON schema. For v1.0.1, JSON-RPC and gRPC use the operation name in section 2 .

Operationv0.3.0 JSON-RPCv0.3.0 gRPCv0.3.0 RESTv1.0.1 REST
SendMessagemessage/sendSendMessagePOST /v1/message:sendPOST /message:send
SendStreamingMessagemessage/streamSendStreamingMessagePOST /v1/message:streamPOST /message:stream
GetTasktasks/getGetTaskGET /v1/tasks/{id}GET /tasks/{id}
ListTasksnonenonenoneGET /tasks
CancelTasktasks/cancelCancelTaskPOST /v1/tasks/{id}:cancelPOST /tasks/{id}:cancel
SubscribeToTasktasks/resubscribeTaskSubscriptionGET /v1/tasks/{id}:subscribeGET /tasks/{id}:subscribe
CreateTaskPushNotificationConfigtasks/pushNotificationConfig/setCreateTaskPushNotificationConfigPOST /v1/task/{id}/pushNotificationConfigsPOST /tasks/{task_id}/pushNotificationConfigs
GetTaskPushNotificationConfigtasks/pushNotificationConfig/getGetTaskPushNotificationConfigGET /v1/tasks/{id}/pushNotificationConfigs/{configId}GET /tasks/{task_id}/pushNotificationConfigs/{id}
ListTaskPushNotificationConfigstasks/pushNotificationConfig/listListTaskPushNotificationConfigGET /v1/tasks/{id}/pushNotificationConfigsGET /tasks/{task_id}/pushNotificationConfigs
DeleteTaskPushNotificationConfigtasks/pushNotificationConfig/deleteDeleteTaskPushNotificationConfigDELETE /v1/tasks/{id}/pushNotificationConfigs/{configId}DELETE /tasks/{task_id}/pushNotificationConfigs/{id}
GetExtendedAgentCardagent/getAuthenticatedExtendedCardGetAgentCardGET /v1/cardGET /extendedAgentCard

What the v0.3.0 documents get wrong, and what to build against:

  • ListTasks did not exist in v0.3.0. The specification’s method table (v0.3.0 section 3.5.6) and its tasks/list section list ListTask and GET /v1/tasks, but the JSON schema defines no such method and the proto no such RPC. A v1.0 client that calls ListTasks on a v0.3 agent has nothing to reach.
  • Push-config RPC names. The v0.3.0 table names four RPCs without the Config suffix (CreateTaskPushNotification and its siblings). The proto, which is normative, has the suffix, and its list RPC is singular: ListTaskPushNotificationConfig. v1.0.1 makes it plural.
  • The create route. The v0.3.0 specification gives POST /v1/tasks/{id}/pushNotificationConfigs. The v0.3.0 proto annotation is /v1/{parent=task/*/pushNotificationConfigs}, which expands to task, singular.
  • SubscribeToTask. The v0.3.0 table says POST; the proto says GET. The same mismatch survives in v1.0.1.
  • Resource names. v0.3.0 gRPC and REST requests identify a resource with one name string, such as tasks/{id} or tasks/{id}/pushNotificationConfigs/{push_id}, or with parent. v1.0.1 uses separate id and task_id fields.

Parameters that changed

These are the JSON-RPC params of v0.3.0 (from the JSON schema) against the request messages of v1.0.1 (from the proto, each with an added tenant).

Operationv0.3.0 paramsv1.0.1 paramsWhat breaks
GetTaskid, historyLength, metadataid, historyLengthmetadata has no field to carry it
SubscribeToTaskid, metadataidmetadata has no field to carry it
CancelTaskid, metadataid, metadataNothing
CreateTaskPushNotificationConfigtaskId, pushNotificationConfig holding id, url, token, authenticationtaskId, id, url, token, authenticationThe pushNotificationConfig wrapper is gone; a request in the old shape puts the URL where the server does not read it
GetTaskPushNotificationConfigid is the task ID; pushNotificationConfigId is the config ID; metadatataskId is the task ID; id is the config IDid changes meaning without changing name; metadata has no field
DeleteTaskPushNotificationConfigid is the task ID; pushNotificationConfigId is the config ID; metadatataskId is the task ID; id is the config IDSame silent change as the get call
ListTaskPushNotificationConfigsid is the task ID; metadatataskId, pageSize, pageTokenid becomes taskId; the list is paged; metadata has no field

The id rows are the dangerous ones. The old call is still well-formed JSON with a field named id, so nothing fails at the parser. The server reads that value as a config ID.

Other wire-level changes

Areav0.3.0v1.0.1Source
Version signalThe specification has no version headerA2A-Version on every request; an empty value is read as 0.3Section 3.6
Interface declarationurl, preferredTransport, and additionalInterfaces; one protocolVersion for the cardsupportedInterfaces, in preference order; protocolVersion per entry as Major.Minorv0.3.0 section 5.6; sections 8.3.1 and 8.3.2
REST content typeapplication/json (MUST)application/a2a+json (SHOULD)v0.3.0 section 3.2.3; section 11.1
REST route prefix/v1None; a version belongs to the interfacev0.3.0 section 3.5.3; section 5.3
TenantNo tenant fieldtenant in every request message, and in AgentInterfaceSection 8.3.2
JSON-RPC error codes-32001 to -32007; -32007 is AuthenticatedExtendedCardNotConfiguredErrorThe same numbers; -32007 is ExtendedAgentCardNotConfiguredError; -32008 and -32009 are newv0.3.0 section 8; section 5.4
Error detailsThe data field can carry details; its structure is not defineddata is an array of typed objects, with ErrorInfo.reason and domain for A2A errorsSections 9.5 and 11.6
REST streamingSSE “similar to JSON-RPC”; the data format is not definedEach data: line is a bare StreamResponsev0.3.0 section 3.3.3; section 11.7
Proto locationspecification/grpc/a2a.protospecification/a2a.protov0.3.0 section 3.2.2; section 10.1

The message-level changes (kind, enum spelling, parts, final, and the blocking default) are in the Task Runtime Guide . The card-level changes are in Agent Card Proto Field Numbers .

5. When interop fails

What you seeLikely causeFix
JSON-RPC -32601 (MethodNotFoundError) for SendMessageThe agent is v0.3 and serves message/sendRead the card. Call the interface that declares 1.0, or use the v0.3 method names for a v0.3 interface
JSON-RPC -32601 for message/sendThe agent is v1.0 and serves SendMessageSame, in the other direction
gRPC UNIMPLEMENTED for ListTasks, SubscribeToTask, or GetExtendedAgentCardA gRPC server answers a method it does not define with UNIMPLEMENTED (a gRPC rule, not an A2A one). A v0.3 server has none of these namesCheck the interface’s protocolVersion; use TaskSubscription and GetAgentCard against v0.3
REST 404 on /v1/message:send or on /message:sendThe route prefix differs between the versionsv0.3: /v1/...; v1.0: no prefix. A 404 is an A2A TaskNotFoundError only when error.details holds an ErrorInfo with reason TASK_NOT_FOUND
VersionNotSupportedErrorThe header is missing, names a version the interface does not declare, or was copied from the specification’s 0.3 exampleSend the entry’s protocolVersion as Major.Minor (section 1 )
A gRPC server treats every call as 0.3 although the client sets the versiongRPC lowercases metadata keys, so the key arrives as a2a-version. A server that looks up A2A-Version finds nothing and reads an empty valueLook up the lowercase key (section 10.2)
An HTTP 400 that does not match the error you expectedSeven errors share 400Read error.details[].reason (section 11.6)
A push-config get or delete finds nothing, or the wrong configA v0.3 client sends the task ID in id; v1.0 reads id as the config IDSend taskId and id as in section 4
A stream parser fails on the first lineJSON-RPC lines are wrapped in {jsonrpc, id, result}; REST lines are a bare StreamResponseUnwrap result for JSON-RPC only
SubscribeToTask over HTTP+JSON is refused with a method errorThe specification says POST; the proto says GETCall GET; if the peer answers with a method error, try the other verb
The client lands on an old version although the agent also serves 1.0One card lists the same binding twice, with protocolVersion 0.3 and 1.0, and the client took the first entryChoose by protocolVersion, not by position alone (section 3.6.2)
The client cannot find supportedInterfacesThe card is a v0.3 card with url and preferredTransportRead it by the v0.3 rules (v0.3.0 section 5.6.3), or ask the owner to upgrade. See the Agent Card Implementation Guide

6. Interop test scenarios

Run these against a server (S) or a client (C). Server scenarios can be driven with curl or any gRPC client. Client scenarios need a card to read and, for I14, a peer that answers with a method error.

IDSideScenarioExpectedSource
I1SSend the same message over every binding the card declaresSemantically equivalent resultsSection 5.1
I2SProvoke the same failure, such as an unknown task ID, over every bindingThe binding’s native code (-32001, NOT_FOUND, 404) plus an ErrorInfo with reason TASK_NOT_FOUND and domain a2a-protocol.orgSections 5.4, 9.5, 10.6, and 11.6
I3SCall a 1.0-only interface with no A2A-VersionVersionNotSupportedErrorSection 3.6.2
I4SCall it with A2A-Version: 1.0, then with 9.9The first succeeds; the second returns VersionNotSupportedErrorSection 3.6.2
I5SCall it with A2A-Version: 1.0.1Record the result; the specification does not define itSections 3.6 and 14.2.1
I6SSend the version as a2a-version in HTTP headers, and as a2a-version in gRPC metadataHandled exactly like A2A-VersionSections 3.2.6, 9.2, 10.2, and 11.2
I7SSend A2A-Extensions with two URIs, comma-separated; omit a required extensionBoth URIs are seen; the omission returns ExtensionSupportRequiredErrorSections 3.3.4 and 9.2
I8SWith a tenant on the interface, call over HTTP+JSON once with the tenant field and once with the /{tenant} route prefixBoth reach the same tenantSection 8.3.2 and the proto
I9CRead a card that lists JSONRPC first and GRPC second, with a client that speaks only gRPCThe client calls the second entry’s urlSection 8.3.2
I10CRead a card with two entries for one binding, 0.3 and 1.0The client calls the entry that matches the version it sendsSections 3.6.2 and 3.6.3
I11SStream over JSON-RPC and over HTTP+JSONJSON-RPC lines parse as JSON-RPC responses; REST lines are bare StreamResponse objectsSections 9.4.2 and 11.7
I12SGET /tasks?contextId=...&status=TASK_STATE_WORKING&pageSize=10The filters and the page size applySection 11.5
I13SAdd an unknown field to a requestThe request is handled; the field is ignoredSection 5.7
I14CSubscribe over HTTP+JSONThe client calls GET; on a method error it retries with POSTSections 5.3 and 11.3.2, and the proto

Source: A2A official specification: Protocol Binding Requirements and Interoperability (5), Versioning (3.6), Agent Card Protocol Declaration (8.3), and the JSON-RPC, gRPC, and HTTP+JSON bindings (9 to 11) ; a2a.proto at v1.0.1 and v0.3.0 ; the v0.3.0 JSON schema and specification