Skip to main content

A2A JSON-RPC

POST {basePath}/jsonrpc
The A2A JSON-RPC endpoint implements the Agent-to-Agent protocol for payment-gated access. The default basePath is /a2a, making the full path /a2a/jsonrpc. The x402 HTTP middleware sits in front of this endpoint and intercepts message/send calls. If the request does not include an X-A2A-Extensions header, the middleware handles the payment flow directly via HTTP 402 responses. If the header is present, the request passes through to the standard A2A JSON-RPC handler.

x402 HTTP Flow (no X-A2A-Extensions header)

When a client sends a message/send JSON-RPC call without the X-A2A-Extensions header, the x402 middleware intercepts it and implements a two-step payment flow.

Step 1: Request Access

Send an AccessRequest in the message parts to receive an x402 payment challenge.
curl -X POST https://api.example.com/a2a/jsonrpc \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "message/send",
    "params": {
      "message": {
        "role": "user",
        "parts": [
          {
            "kind": "data",
            "data": {
              "type": "AccessRequest",
              "requestId": "550e8400-e29b-41d4-a716-446655440000",
              "planId": "basic",
              "resourceId": "default",
              "clientAgentId": "did:example:client-agent"
            }
          }
        ]
      }
    }
  }'
Response: HTTP 402 Payment Required The server responds with payment requirements in both the response body and the payment-required header (base64-encoded).
{
  "x402Version": 2,
  "resource": {
    "url": "https://api.example.com/a2a/jsonrpc",
    "method": "POST",
    "description": "Access to default",
    "mimeType": "application/json"
  },
  "accepts": [
    {
      "scheme": "exact",
      "network": "eip155:84532",
      "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
      "amount": "100000",
      "payTo": "0xYourSellerWalletAddress",
      "maxTimeoutSeconds": 900,
      "extra": {
        "name": "USDC",
        "version": "2",
        "description": "basic — $0.10 USDC"
      }
    }
  ],
  "challengeId": "http-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "error": "PAYMENT-SIGNATURE header is required"
}
Response Headers:
HeaderValue
payment-requiredBase64-encoded JSON of the payment requirements
www-authenticatePayment realm="https://api.example.com", accept="exact", challenge="http-..."

Step 2: Submit Payment

Sign an EIP-3009 transferWithAuthorization off-chain, encode the payment payload as base64url, and resend the same request with the PAYMENT-SIGNATURE header.
curl -X POST https://api.example.com/a2a/jsonrpc \
  -H "Content-Type: application/json" \
  -H "PAYMENT-SIGNATURE: eyJ4NDAyVmVyc2lvbiI6Miwi..." \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "message/send",
    "params": {
      "message": {
        "role": "user",
        "parts": [
          {
            "kind": "data",
            "data": {
              "type": "AccessRequest",
              "requestId": "550e8400-e29b-41d4-a716-446655440000",
              "planId": "basic",
              "resourceId": "default",
              "clientAgentId": "did:example:client-agent"
            }
          }
        ]
      }
    }
  }'
Response: HTTP 200 OK
{
  "type": "AccessGrant",
  "challengeId": "http-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "requestId": "550e8400-e29b-41d4-a716-446655440000",
  "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "tokenType": "Bearer",
  "resourceEndpoint": "https://api.example.com/a2a/resources/default",
  "resourceId": "default",
  "planId": "basic",
  "txHash": "0xabc123def456...",
  "explorerUrl": "https://sepolia.basescan.org/tx/0xabc123def456..."
}
Response Headers:
HeaderValue
payment-responseBase64-encoded X402SettleResponse JSON

A2A-Native Flow (with X-A2A-Extensions header)

When a client sends the X-A2A-Extensions header, the x402 middleware passes through to the standard A2A JSON-RPC handler. In this mode, two skills are available:

Skill: request-access

Issues an X402Challenge via the A2A protocol. Request:
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "message/send",
  "params": {
    "message": {
      "role": "user",
      "parts": [
        {
          "kind": "data",
          "data": {
            "type": "AccessRequest",
            "requestId": "550e8400-e29b-41d4-a716-446655440000",
            "planId": "basic",
            "resourceId": "default",
            "clientAgentId": "did:example:client-agent"
          }
        }
      ]
    }
  }
}
Response (task with X402Challenge):
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "id": "task-id",
    "status": {
      "state": "completed",
      "message": {
        "role": "agent",
        "parts": [
          {
            "kind": "data",
            "data": {
              "type": "X402Challenge",
              "challengeId": "chg-abc123",
              "requestId": "550e8400-e29b-41d4-a716-446655440000",
              "planId": "basic",
              "amount": "$0.10",
              "asset": "USDC",
              "chainId": 84532,
              "destination": "0xYourSellerWalletAddress",
              "expiresAt": "2025-01-15T12:15:00.000Z",
              "description": "Send $0.10 USDC to 0xYour... on chain 84532. Then call the \"submit-proof\" skill with a PaymentProof...",
              "resourceVerified": true
            }
          }
        ]
      }
    }
  }
}

Skill: submit-proof

Submits a PaymentProof with the on-chain transaction hash. Returns an AccessGrant on success. Request:
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "message/send",
  "params": {
    "message": {
      "role": "user",
      "parts": [
        {
          "kind": "data",
          "data": {
            "type": "PaymentProof",
            "challengeId": "chg-abc123",
            "requestId": "550e8400-e29b-41d4-a716-446655440000",
            "chainId": 84532,
            "txHash": "0xabc123def456789...",
            "amount": "$0.10",
            "asset": "USDC",
            "fromAgentId": "did:example:client-agent"
          }
        }
      ]
    }
  }
}
Response (task with AccessGrant):
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "id": "task-id",
    "status": {
      "state": "completed",
      "message": {
        "role": "agent",
        "parts": [
          {
            "kind": "data",
            "data": {
              "type": "AccessGrant",
              "challengeId": "chg-abc123",
              "requestId": "550e8400-e29b-41d4-a716-446655440000",
              "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
              "tokenType": "Bearer",
              "resourceEndpoint": "https://api.example.com/a2a/resources/default",
              "resourceId": "default",
              "planId": "basic",
              "txHash": "0xabc123def456789...",
              "explorerUrl": "https://sepolia.basescan.org/tx/0xabc123def456789..."
            }
          }
        ]
      }
    }
  }
}

Error Handling

All errors in the x402 HTTP flow return standard JSON error responses:
HTTP StatusError CodeDescription
400TIER_NOT_FOUNDThe requested planId does not exist in the plan catalog.
400INVALID_REQUESTMalformed request body or missing required fields.
402PAYMENT_FAILEDPayment verification or settlement failed.
409TX_ALREADY_REDEEMEDThe transaction hash has already been used for another challenge.
410CHALLENGE_EXPIREDThe challenge has expired or was cancelled.
500INTERNAL_ERRORUnexpected server error or concurrent state transition conflict.

Idempotency

The requestId field serves as an idempotency key. If a client retries the same requestId:
  • PENDING challenge exists: Returns the same challenge without creating a new one.
  • DELIVERED grant exists: Returns the existing AccessGrant directly.
  • EXPIRED or CANCELLED: Creates a new challenge.