Skip to main content

JSON-RPC 2.0 binding

Unreleased contract — planned 2.0.0

This page describes the editable next-major contract, not the frozen v1.3 release. Example extension and schema URLs use the non-routable draft.autoagentprotocol.invalid namespace; release preparation replaces them with approved version-pinned URLs. Do not send draft identifiers to a production agent. See migration guidance.

A2A defines a JSON-RPC 2.0 binding in Section 9 of its specification. AAP rides on top of A2A v1.0 without modification, and uses JSON-RPC 2.0 as its sole transport: every skill is invoked via the SendMessage JSON-RPC method, with the AAP request packaged as a typed DataPart inside params.message.parts[].

JSON-RPC request and response envelopes: SendMessage carries params.message in and result.message out

JSON-RPC is the SOLE binding

A JSON-RPC interface is REQUIRED on every AAP agent card: supportedInterfaces[] MUST include at least one entry with protocolBinding: "JSONRPC". JSON-RPC 2.0 is the only transport AAP defines — the HTTP+JSON (REST) binding was removed in v1.1.0, and gRPC is out of scope.

A2A v1.0 wire format — the ProtoJSON form

AAP rides on A2A v1.0, whose single canonical wire format is the ProtoJSON form: the method is SendMessage, Role is the enum name "ROLE_USER" / "ROLE_AGENT", and a Part has no kind discriminator (it is typed by the member it carries — AAP uses the data member). A compliant AAP agent MUST emit and accept this form so any A2A v1.0 client can parse its replies.

AspectA2A v1.0 (ProtoJSON)
Method nameSendMessage
Role"ROLE_USER" / "ROLE_AGENT"
Part discriminatormember-name (no kind)
Message discriminator(none — no kind)
result (JSON-RPC)the SendMessageResponse, i.e. { "message": <Message> }
messageIdrequired on every Message
contextIdrequired and nonempty on every server response Message; optional on an initial client request
mediaType on DataPartapplication/vnd.autoagent.<skill>-request+json

Endpoint and method

A dealer agent advertises one or more JSON-RPC endpoints under supportedInterfaces[] of its agent card. Each entry has protocolBinding: "JSONRPC" and a url.

POST {jsonrpc-url}
Content-Type: application/json
A2A-Version: 1.0
A2A-Extensions: https://draft.autoagentprotocol.invalid/extensions/aap/latest

All AAP skills use a single JSON-RPC method:

"method": "SendMessage"

SendMessage is the only A2A operation AAP uses (message-only pattern: request Message in, response Message out). The optional A2A surface — SendStreamingMessage, the tasks operations (Get/List/Cancel/Subscribe), push notification configs, and GetExtendedAgentCard — is out of scope for AAP: dealer agents do not need to implement it, and buyer agents MUST NOT require it.

Out of scope does not mean undefined on the wire. An AAP card normally declares no streaming, pushNotifications or extendedAgentCard capability. A2A §3.3.4 requires the following errors when the corresponding capability is false or absent:

Operation a client calls anywayA2A errorJSON-RPC
SendStreamingMessage, SubscribeToTaskUnsupportedOperationError-32004
Push notification config (Create/Get/List/Delete)PushNotificationNotSupportedError-32003
GetExtendedAgentCardUnsupportedOperationError-32004

Task retrieval, listing and cancellation have no capability flag in A2A §3.3.4. AAP does not require these operations; an implementation that omits them returns UnsupportedOperationError (-32004). An implementation that does serve them follows their A2A semantics, including TaskNotFoundError for an unknown task where applicable.

These are A2A protocol-level errors, so they carry A2A's own error.data shape — an array whose entries each carry an @type — not a typed aap.error payload.

A dealer agent MUST NOT declare a capability it cannot serve. The flags are not decorative: the reference a2a-python client branches on card.capabilities.streaming in its ordinary send path, so a card claiming streaming: true in front of a SendMessage-only dealer turns a default-configured client's normal call into SendStreamingMessage against an agent that cannot serve it. A dealer that genuinely serves A2A streaming to non-AAP partners MAY declare it truthfully — A2A requires the card to be truthful — but then it MUST actually implement that surface. AAP itself never requires it, and a buyer agent MUST NOT depend on it.

The id field is the standard JSON-RPC request id; AAP does not constrain it. The params.message is an A2A Message whose first parts[] entry is the typed AAP DataPart. A buyer agent MUST also include params.configuration.acceptedOutputModes listing the AAP response media type it expects.

Request headers

A2A carries its service parameters (A2A §3.2.6) as HTTP request headers on the JSON-RPC binding — A2A §9.2 requires it (MUST be transmitted as HTTP header fields). Two of the three headers below are A2A service parameters; Content-Type is the binding's own media type, not a service parameter. Every AAP request carries all three:

HeaderValueRule
Content-Typeapplication/jsonA2A §9.1 — the JSON-RPC binding's media type.
A2A-Version1.0A2A §3.6.1 — clients MUST send the version on every request. On this binding it MUST be a header: A2A §9.2 requires service parameters to travel as HTTP header fields. The general query-parameter allowance in §3.6.1 conflicts with binding-specific header requirements in both §9.2 (JSON-RPC) and §11.2 (REST); AAP requires the header and does not accept a query parameter as its substitute. Major.Minor only — A2A §3.6 says patch numbers SHOULD NOT appear in requests and MUST NOT be considered when negotiating versions.
A2A-Extensionshttps://draft.autoagentprotocol.invalid/extensions/aap/latestActivates the AAP profile. AAP cards declare the extension required: true, which A2A defines as the client having to "understand and comply with the extension's requirements" (a2a.proto, AgentExtension.required), so it MUST be present. A client activating several extensions sends them comma-separated in this one header.

Both A2A headers are load-bearing, not decorative:

  • Omitting A2A-Version is rejected on the new AAP interface. This missing-header rule is an explicit AAP requirement: return VersionNotSupportedError (-32009) before skill execution. A2A §3.6.2 separately requires an empty value to be interpreted as 0.3, not 1.0; reject it when that version is unsupported. Do not attribute the missing-header rule to A2A’s empty-value wording.
  • Omitting A2A-Extensions is a rejected request. The AAP extension is marked required: true on the agent card, so per A2A §3.3.4 a dealer agent MUST answer a request that did not activate it with ExtensionSupportRequiredError (JSON-RPC -32008). AAP is a profile extension — it constrains the shape of every message — so a client that has not declared AAP support cannot be served as an AAP client.
  • A version error naming 0.3 can indicate an empty version header. Check the actual transmitted headers and the interface's advertised version. A2A does not prescribe the order of version and extension validation, and an interface that also supports 0.3 need not reject that version.

A dealer agent parses A2A-Extensions as a comma-separated list and treats the AAP extension as activated when its exact URI appears as a member; extension URIs it does not recognize are ignored rather than rejected, per A2A's activation flow. A version mismatch is not a match — A2A §4.6.3 requires an error and forbids falling back to an earlier version of the extension. A dealer agent SHOULD echo the extensions it activated back on the response in an A2A-Extensions header.

ExtensionSupportRequiredError and VersionNotSupportedError are A2A protocol-level errors raised before the AAP payload is read, so they carry A2A's own error shape, not the typed aap.error payload.

Generic envelope

Every AAP request looks like this on the wire — headers and body together:

POST /a2a HTTP/1.1
Host: demo-toyota.example.com
Content-Type: application/json
A2A-Version: 1.0
A2A-Extensions: https://draft.autoagentprotocol.invalid/extensions/aap/latest

{
"jsonrpc": "2.0",
"id": "req-1",
"method": "SendMessage",
"params": {
"message": {
"messageId": "01HZ9F4M7C0X3K5RN8B3WJTW2P",
"role": "ROLE_USER",
"parts": [
{
"data": {
"type": "<scope>.<thing>.request",
"...": "skill-specific fields"
},
"mediaType": "application/vnd.autoagent.<skill>-request+json"
}
]
},
"configuration": {
"acceptedOutputModes": [
"application/vnd.autoagent.<skill>-response+json"
]
}
}
}

The JSON-RPC result is the SendMessageResponse, which ProtoJSON serializes as { "message": <Message> } — so the agent Message is wrapped under result.message:

{
"jsonrpc": "2.0",
"id": "req-1",
"result": {
"message": {
"messageId": "01HZ9F4N1JZ7QS8VKR2A3B4C5D",
"contextId": "ctx_example_001",
"role": "ROLE_AGENT",
"parts": [
{
"data": {
"type": "<scope>.<thing>.response",
"data": {
"...": "skill-specific response data"
}
},
"mediaType": "application/vnd.autoagent.<skill>-response+json"
}
]
}
}
}

The messageId on the response is generated by the dealer agent; it MUST differ from the messageId the buyer agent sent on the request.

Every server response Message MUST contain a nonempty contextId, even in this message-only interaction. The server preserves a client-supplied context for the same interaction and generates a context identifier when starting a new interaction. A client may omit contextId on its initial request; do not make it universally required on request Messages. This follows the A2A Message definition. The shared field’s optional annotation does not remove the server-message requirement. The earlier assertion that the TCK’s response-context check was a bug is withdrawn.

The remainder of this page shows the full envelope for each of the five skills. Every one of them is sent with the request headers above; the blocks below show the JSON-RPC body only.

dealer.information

Request

{
"jsonrpc": "2.0",
"id": "req-1",
"method": "SendMessage",
"params": {
"message": {
"messageId": "01HZ9G5N8D1Y4M6SP9C4XKVW3Q",
"role": "ROLE_USER",
"parts": [
{
"data": {
"type": "dealer.information.request"
},
"mediaType": "application/vnd.autoagent.dealer-information-request+json"
}
]
},
"configuration": {
"acceptedOutputModes": [
"application/vnd.autoagent.dealer-information-response+json"
]
}
}
}

Response

{
"jsonrpc": "2.0",
"id": "req-1",
"result": {
"message": {
"messageId": "01HZ9G5P2KA8RT9WMS3B4C5D6E",
"contextId": "ctx_example_001",
"role": "ROLE_AGENT",
"parts": [
{
"data": {
"type": "dealer.information.response",
"data": {
"name": "Demo Auto Group",
"rooftops": [
{
"name": "Demo Toyota San Francisco",
"legal_name": "Demo Toyota of San Francisco, LLC",
"website": "https://demo-toyota.example.com",
"geo": {
"latitude": 37.77,
"longitude": -122.41
},
"emails": [
{
"name": "Sales",
"value": "sales@demo-toyota.example.com"
}
],
"phones": [
{
"name": "Sales",
"value": "+14155550100"
}
],
"address": {
"country": "US",
"state": "CA",
"city": "San Francisco",
"address_line_1": "100 Market St",
"zip": "94105"
},
"timezone": "America/Los_Angeles",
"capabilities": [
"sales",
"service",
"financing",
"trade_in"
]
}
]
}
},
"mediaType": "application/vnd.autoagent.dealer-information-response+json"
}
]
}
}
}

inventory.facets

Request

{
"jsonrpc": "2.0",
"id": "req-2",
"method": "SendMessage",
"params": {
"message": {
"messageId": "01HZ9H6P9E2Z5N7TQ0D5YMWX4R",
"role": "ROLE_USER",
"parts": [
{
"data": {
"type": "inventory.facets.request",
"filters": {
"condition": [
"used"
]
}
},
"mediaType": "application/vnd.autoagent.inventory-facets-request+json"
}
]
},
"configuration": {
"acceptedOutputModes": [
"application/vnd.autoagent.inventory-facets-response+json"
]
}
}
}

Response

{
"jsonrpc": "2.0",
"id": "req-2",
"result": {
"message": {
"messageId": "01HZ9H6Q3KB9SV0XNT4C5D6E7F",
"contextId": "ctx_example_001",
"role": "ROLE_AGENT",
"parts": [
{
"data": {
"type": "inventory.facets.response",
"data": {
"makes": [
{
"value": "Honda",
"count": 12
},
{
"value": "Toyota",
"count": 27
}
],
"conditions": [
{
"value": "used",
"count": 39
}
],
"year_range": {
"min": 2015,
"max": 2024
},
"price_range": {
"min": 9990,
"max": 38990
}
}
},
"mediaType": "application/vnd.autoagent.inventory-facets-response+json"
}
]
}
}
}

inventory.search

Request

{
"jsonrpc": "2.0",
"id": "req-3",
"method": "SendMessage",
"params": {
"message": {
"messageId": "01HZ9F4M7C0X3K5RN8B3WJTW2P",
"role": "ROLE_USER",
"parts": [
{
"data": {
"type": "inventory.search.request",
"filters": {
"make": [
"Honda"
],
"condition": [
"used",
"cpo"
],
"year_min": 2020,
"price_max": 30000
},
"pagination": {
"skip": 0,
"limit": 20
},
"sort": {
"field": "price",
"order": "asc"
},
"privacy": {
"anonymous": true
}
},
"mediaType": "application/vnd.autoagent.inventory-search-request+json"
}
]
},
"configuration": {
"acceptedOutputModes": [
"application/vnd.autoagent.inventory-search-response+json"
]
}
}
}

Response

{
"jsonrpc": "2.0",
"id": "req-3",
"result": {
"message": {
"messageId": "01HZ9F4N1JZ7QS8VKR2A3B4C5D",
"contextId": "ctx_example_001",
"role": "ROLE_AGENT",
"parts": [
{
"data": {
"type": "inventory.search.response",
"data": {
"total": 1,
"skip": 0,
"limit": 20,
"vehicles": [
{
"dealer_id": "dealer_demo_toyota",
"vin": "1HGCY2F57RA000001",
"stock": "T12345",
"year": 2022,
"make": "Honda",
"model": "Civic",
"trim": "EX",
"condition": "cpo",
"list_price": 24990,
"price": 26780,
"fees": [
{ "name": "Documentation fee", "amount": 500 },
{ "name": "Pre-installed theft protection", "amount": 1290 }
],
"status": "available",
"rooftop": "Demo Toyota San Francisco",
"inventory_date": "2026-04-12",
"updated_at": "2026-04-30T10:15:00Z"
}
]
}
},
"mediaType": "application/vnd.autoagent.inventory-search-response+json"
}
]
}
}
}

inventory.vehicle

Request

{
"jsonrpc": "2.0",
"id": "req-4",
"method": "SendMessage",
"params": {
"message": {
"messageId": "01HZ9J7Q0F3A6P8VR1E6ZNXY5S",
"role": "ROLE_USER",
"parts": [
{
"data": {
"type": "inventory.vehicle.request",
"vin": "1HGCY2F57RA000001"
},
"mediaType": "application/vnd.autoagent.vehicle-detail-request+json"
}
]
},
"configuration": {
"acceptedOutputModes": [
"application/vnd.autoagent.vehicle-detail-response+json"
]
}
}
}

Response

{
"jsonrpc": "2.0",
"id": "req-4",
"result": {
"message": {
"messageId": "01HZ9J7R4MC0TW1YPV5D6E7F8G",
"contextId": "ctx_example_001",
"role": "ROLE_AGENT",
"parts": [
{
"data": {
"type": "inventory.vehicle.response",
"data": {
"dealer_id": "dealer_demo_toyota",
"vin": "1HGCY2F57RA000001",
"stock": "T12345",
"year": 2022,
"make": "Honda",
"model": "Civic",
"trim": "EX",
"condition": "cpo",
"msrp": 26500,
"list_price": 24990,
"price": 26780,
"fees": [
{ "name": "Documentation fee", "amount": 500 },
{ "name": "Pre-installed theft protection", "amount": 1290 }
],
"status": "available",
"rooftop": "Demo Toyota San Francisco",
"city_mpg": 31,
"highway_mpg": 40,
"features": [
"Adaptive Cruise Control",
"Apple CarPlay",
"Lane Keep Assist"
],
"vdp_url": "https://demo-toyota.example.com/inventory/T12345",
"inventory_date": "2026-04-12",
"updated_at": "2026-04-30T10:15:00Z"
}
},
"mediaType": "application/vnd.autoagent.vehicle-detail-response+json"
}
]
}
}
}

lead.submit

The unified lead carries customer info plus any combination of vehicle_of_interest, trade_in, and appointment. Below: a single test-drive lead that also queues the buyer's trade-in for in-person appraisal.

Request

{
"jsonrpc": "2.0",
"id": "req-5",
"method": "SendMessage",
"params": {
"message": {
"messageId": "01HZ9K8R1G4B7Q9WS2F7APYZ6T",
"role": "ROLE_USER",
"parts": [
{
"data": {
"type": "lead.submit.request",
"customer": {
"first_name": "Anna",
"last_name": "Lee",
"email": "anna@example.com",
"phone": "+14155550123",
"preferred_contact": "phone",
"address": {
"address_line_1": "200 Folsom St",
"city": "San Francisco",
"state": "CA",
"zip": "94105"
}
},
"consent": {
"granted_at": "2026-04-30T10:16:00Z",
"allowed_channels": [
"email",
"phone"
],
"consent_text": "I agree to share my contact info with Demo Toyota about VIN 1HGCY2F57RA000001, my Saturday test drive, and the trade-in of my 2014 Toyota Corolla.",
"scope": [
"lead_submission"
]
},
"vehicle_of_interest": {
"vin": "1HGCY2F57RA000001",
"year": 2022,
"make": "Honda",
"model": "Civic",
"trim": "EX",
"condition": "cpo"
},
"trade_in": {
"year": 2014,
"make": "Toyota",
"model": "Corolla",
"condition": "good",
"mileage": 96000
},
"appointment": {
"appointment_type": "test_drive",
"appointment_at": "2026-05-02T17:00:00Z",
"duration_minutes": 60
},
"message": "Interested in this Civic; is it still available? Please appraise my Corolla at the same visit.",
"source_agent": {
"name": "chatgpt-shopping",
"url": "https://chatgpt.com",
"agent_card_url": "https://chatgpt.com/.well-known/agent-card.json"
},
"submitted_at": "2026-04-30T10:16:05Z"
},
"mediaType": "application/vnd.autoagent.lead-submit-request+json"
}
]
},
"configuration": {
"acceptedOutputModes": [
"application/vnd.autoagent.lead-submit-response+json"
]
}
}
}

Response

{
"jsonrpc": "2.0",
"id": "req-5",
"result": {
"message": {
"messageId": "01HZ9K8S5ND1VX2ZQW6E7F8G9H",
"contextId": "ctx_example_001",
"role": "ROLE_AGENT",
"parts": [
{
"data": {
"type": "lead.submit.response",
"data": {
"lead_id": "lead_2026_04_30_anna_001",
"status": "received",
"appointment": {
"appointment_id": "appt_2026_04_30_anna_001",
"status": "confirmed",
"confirmed_at": "2026-05-02T17:00:00Z"
},
"dealer": {
"name": "Demo Toyota",
"phone": "+14155550100"
}
},
"message": "Confirmed for Saturday at 10am Pacific. Please bring a valid driver's license. Your Corolla is queued for in-person appraisal at the same visit."
},
"mediaType": "application/vnd.autoagent.lead-submit-response+json"
}
]
}
}
}

Error mapping (A2A Section 9.5)

When a skill cannot be fulfilled, the dealer agent MUST return a JSON-RPC error envelope. AAP defines a typed error payload (aap.error) carried in error.data. The mapping uses the standard JSON-RPC code for transport errors and AAP's own code for business errors.

Validation errors (SCHEMA_VALIDATION_FAILED, MISSING_REQUIRED_FIELD, INVALID_CONDITION) MUST list every failing field at once in details.errors[] — each entry carrying instanceLocation, keyword, and error — so the buyer agent can fix the whole payload in a single retry instead of one round-trip per error. See Errors for the full shape.

{
"jsonrpc": "2.0",
"id": "req-3",
"error": {
"code": -32602,
"message": "Invalid params: 2 validation errors",
"data": [
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "SCHEMA_VALIDATION_FAILED",
"domain": "autoagentprotocol.org",
"metadata": {
"code": "SCHEMA_VALIDATION_FAILED",
"error_id": "err_01HZ9EXAMPLE",
"retryable": "false",
"created_at": "2026-04-30T10:15:30Z"
}
},
{
"@type": "type.googleapis.com/google.rpc.BadRequest",
"fieldViolations": [
{ "field": "/filters/year_min", "description": "type: must be integer" },
{ "field": "/filters/condition/0", "description": "enum: must be one of: new, used, cpo" }
]
},
{
"@type": "https://autoagentprotocol.org/extensions/aap/error",
"type": "aap.error",
"error_id": "err_01HZ9EXAMPLE",
"code": "SCHEMA_VALIDATION_FAILED",
"message": "Request failed validation with 2 errors; see details.errors[].",
"retryable": false,
"details": {
"errors": [
{ "instanceLocation": "/filters/year_min", "keyword": "type", "error": "must be integer" },
{ "instanceLocation": "/filters/condition/0", "keyword": "enum", "error": "must be one of: new, used, cpo" }
]
},
"created_at": "2026-04-30T10:15:30Z"
}
]
}
}

Recommended JSON-RPC code mapping:

AAP codeJSON-RPC codeNotes
SCHEMA_VALIDATION_FAILED-32602JSON-RPC "Invalid params".
MISSING_REQUIRED_FIELD-32602"Invalid params".
INVALID_CONDITION-32602"Invalid params" — a condition value is in the wrong vocabulary.
UNSUPPORTED_SKILL-32004A2A UnsupportedOperationError — the dealer does not implement this AAP skill. Dispatch AAP behavior from the typed payload’s code, not -32004 alone; protocol-level capability and task-operation errors also use -32004. NOT -32601: the JSON-RPC method is always SendMessage and it always exists, so "Method not found" would tell a generic A2A client the endpoint does not speak A2A.
VEHICLE_NOT_FOUND-32000Application error.
VEHICLE_UNAVAILABLE-32000Application error.
CONTACT_CONSENT_REQUIRED-32000Application error.
INVALID_CONSENT-32000Application error.
APPOINTMENT_TIME_UNAVAILABLE-32000Application error.
IDEMPOTENCY_CONFLICT-32000Application error — an idempotency_key was reused with a different payload.
RATE_LIMITED-32000Generic JSON-RPC server error. NOT -32002: A2A §5.4 assigns that code to TaskNotCancelableError, which both reference SDKs decode as a terminal task-lifecycle failure rather than a retryable throttle.
INTERNAL_ERROR-32603JSON-RPC "Internal error".

See Errors for the full vocabulary and per-code semantics.