Protocol surfaces

A2A

Experimental A2A v1.0.1: Agent Card discovery, JSON-RPC SendMessage and the x402 extension at /a2a.

ExperimentalA2A v1.0.1, off unless protocols.a2a.enabled is true. Enabling it requires at least one resource with expose: [a2a].
BindingJSON-RPC 2.0 over HTTP(S)
MethodSendMessage (not the legacy message/send)
VersionA2A-Version: 1.0 header, or an A2A-Version query parameter when the header is absent; a patch suffix is accepted and ignored
Agent CardGET /.well-known/agent-card.json
Default mount/a2a
Task modelsynchronous; terminal, except an unpaid x402 extension task
Streaming, task lookup, push notificationsunsupported; only unpaid x402 extension tasks are stored, until they expire

Agent Card

GET/.well-known/agent-card.jsonpublic

Resources exposed with expose: [a2a] become skills: skill id = resource id; a paid skill is tagged paid and names its price. A2A v1.0 skills have no input-schema field, so each skill carries an illustrative JSON call example with its required input instead. The agent description explains the call shape, points to <publicBaseUrl>/api/resources for the schemas (see GET /api/resources), and names input._payment when any skill is paid. When x402 is a skill's selected rail, the card advertises the x402 extension as optional, with params: { "x402Version": 2 }.

The card has a content-based ETag and Cache-Control: public, max-age=300. A matching If-None-Match gets 304 with no body.

SendMessage

POST/a2apayment
json
{
  "jsonrpc": "2.0",
  "id": "1",
  "method": "SendMessage",
  "params": {
    "message": {
      "role": "ROLE_USER",
      "messageId": "msg-1",
      "parts": [
        {
          "data": { "resource": "market_report", "input": { "_payment": "<base64 x402 proof>" } },
          "mediaType": "application/json"
        }
      ]
    }
  }
}

One message with a non-empty messageId and exactly one data part naming the resource and its input. mediaType is optional; when present it must be application/json. A new purchase refuses text, file, bytes and URL parts, multi-part messages, roles other than ROLE_USER, and task or context continuation.

_payment is this gateway's carrier, not part of A2A or of either rail. MPP defines no A2A binding, so _payment is the only way to pay an MPP skill.

x402 extension

A client that sends A2A-Extensions: https://github.com/google-a2a/a2a-x402/v0.1 can pay an x402 skill without _payment. The gateway uses the x402 v2 documents of the x402 A2A transport with the A2A 1.0 forms: TASK_STATE_INPUT_REQUIRED, ROLE_AGENT, parts without kind, and the A2A-Extensions header.

  1. Payment required

    A call without a proof returns TASK_STATE_INPUT_REQUIRED. The status message carries x402.payment.status: payment-required and the v2 PaymentRequired in x402.payment.required.

  2. Pay

    The client sends a message with that taskId, x402.payment.status: payment-submitted and the PaymentPayload in x402.payment.payload message metadata. The gateway validates the stored purchase, verifies the proof, reserves its replay key, calls the backend and settles according to the configured payment flow.

  3. Result

    Success is TASK_STATE_COMPLETED with payment-completed and a settlement entry in x402.payment.receipts. A payment failure is TASK_STATE_FAILED with payment-failed, an x402.payment.error code and a failed receipt. A backend failure after settlement fails the task but reports payment-completed with the settlement entry. Sending payment-rejected ends the task without paying.

Since 2.6.0, unpaid tasks survive restarts in the receipt database until requirement expiry, capped at one hour or ten minutes without an expiry. At 256 pending tasks, new purchases get GATEWAY_BUSY; waiting tasks are not evicted. After validating a payment message, the adapter atomically takes its task before execution, so competing messages find no task.

AP2 mandates remain in memory. After restart, payment for a resource requiring a mandate must include _authorization again; otherwise it returns AUTHORIZATION_REQUIRED after taking the task. An instance with a separate receipt database returns TaskNotFoundError, requiring a new purchase.

A malformed payment message, a contextId other than the task's, a role other than ROLE_USER or a malformed _authorization gets -32602 and leaves the task pending. An x402 v1 payload fails the task with invalid_x402_version. If the first message carried no AP2 mandate, the payment message may add one as {"data": {"input": {"_authorization": {…}}}}; it is verified against the stored purchase. The extension covers x402 only; an MPP skill still returns a terminal task.

Results

OutcomeTask stateArtifact data
deliveredTASK_STATE_COMPLETEDthe merchant response (non-objects wrapped as { value }); delivery summary in agent-commerce/delivery metadata
payment required, without the x402 extensionTASK_STATE_FAILEDthe payment envelope
domain failureTASK_STATE_FAILEDthe error envelope

Without the extension, payment required is terminal and the buyer sends a new message carrying the proof. Every failed task has a ROLE_AGENT status message whose text is the envelope's message; after an x402 extension payment, the text summarizes the payment outcome instead.

JSON-RPC errors

Reserved for requests A2A cannot serve — never for commerce outcomes. Each A2A-specific error carries a google.rpc.ErrorInfo in error.data, with a reason such as VERSION_NOT_SUPPORTED and the domain a2a-protocol.org.

CodeMeaning
-32700bad JSON
-32600bad request object; a request without an id is refused without running
-32601unknown method
-32602bad params or envelope, a missing messageId or a non-user role
-32001TaskNotFoundError: an unknown, expired or completed taskId, and GetTask or CancelTask for any id
-32003the four push-notification configuration methods
-32004other unsupported methods, multi-part messages, a contextId or non-empty referenceTaskIds
-32005a text, file, bytes or URL part, or a non-JSON mediaType
-32009unsupported version; a missing header and parameter, or an empty header, counts as 0.3 (sent as HTTP 200)

Not implemented: SendStreamingMessage, ListTasks, SubscribeToTask, GetExtendedAgentCard, task lookup and cancellation, push notifications, the REST and gRPC bindings, SSE and A2A authentication schemes. A body over 256 KiB gets 413, and more than 20 requests at once on the mount get 503 GATEWAY_BUSY. The adapter's descriptor.unsupported carries the list at runtime.