Docs/A2A Protocol

>_ DOCS / REFERENCE

A2A
PROTOCOL.

P402 implements the Google A2A (Agent-to-Agent) protocol over JSON-RPC 2.0. This page is the complete reference: task lifecycle, all four RPC methods, SSE streaming, and the x402 payment extension.

What is A2A?

A2A is a standardized communication protocol that lets AI agents talk to each other using structured tasks over JSON-RPC 2.0. Instead of each agent defining its own API format, A2A provides a shared vocabulary that any conforming agent can understand.

P402 extends A2A with the x402 payment extension, which adds machine-native payment negotiation to any A2A task. An agent can request USDC payment, receive a signed authorization from the caller, and confirm settlement — all within the same task context.

Transport
JSON-RPC 2.0 over HTTPS POST
Streaming
Server-Sent Events (SSE)
Auth
Bearer token (P402 API key)
Endpoint
POST /api/a2a
Stream endpoint
GET /api/a2a/stream
Agent discovery
GET /.well-known/agent.json

Task Lifecycle

Every unit of work in A2A is a Task. A task moves through a fixed state machine. Understanding these states is essential for building agents that handle edge cases correctly.

Task State Machine
pendingprocessing
completedfailedcancelled
StateMeaningTerminal?
pendingTask received, queued for processing.No
processingAgent is actively working on the task.No
completedTask finished successfully. Artifacts are available.Yes
failedTask encountered an unrecoverable error. Check status.message.Yes
cancelledTask was explicitly cancelled via tasks/cancel.Yes

Task Object

All methods that return a task return this structure.

json — Task object
{
  "id": "task_01J...",
  "contextId": "ctx_...",        // Groups related tasks in a conversation
  "status": {
    "state": "completed",        // pending | processing | completed | failed | cancelled
    "message": {                 // Set when state = failed; explains the error
      "role": "agent",
      "parts": [{ "type": "text", "text": "Error: upstream provider timeout" }]
    },
    "timestamp": "2026-04-16T12:00:00.000Z"
  },
  "artifacts": [                 // Output produced by the task
    {
      "name": "completion",
      "parts": [{ "type": "text", "text": "The answer is 42." }]
    }
  ],
  "metadata": {
    "cost_usd": 0.0003,
    "latency_ms": 1240,
    "provider": "deepseek",
    "model": "deepseek-v3"
  }
}

JSON-RPC Methods

All requests are POST /api/a2a with the standard JSON-RPC 2.0 envelope. The method field selects the operation. All methods require a Authorization: Bearer <api_key> header.

POSTtasks/send

Submits a new task. The router processes the request synchronously and returns the completed (or failed) task. Use this for short tasks where you can wait for the result.

json — tasks/send request
{
  "jsonrpc": "2.0",
  "method": "tasks/send",
  "id": 1,
  "params": {
    "message": {
      "role": "user",
      "parts": [{ "type": "text", "text": "Summarize this in one sentence: [...]" }]
    },
    "configuration": {
      "mode": "cost",        // cost | speed | quality | balanced
      "session_id": "ses_..." // optional: attach to a budget-capped session
    }
  }
}
json — tasks/send response
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "id": "task_01J...",
    "status": { "state": "completed", "timestamp": "2026-04-16T12:00:01.240Z" },
    "artifacts": [{
      "name": "completion",
      "parts": [{ "type": "text", "text": "The document outlines three key themes." }]
    }],
    "metadata": { "cost_usd": 0.0003, "latency_ms": 1240, "provider": "deepseek" }
  }
}
POSTtasks/sendSubscribe

Submits a task and immediately opens an SSE stream for live updates. The connection stays open until the task reaches a terminal state. Use this for long-running tasks or when you want to stream tokens to a UI.

json — tasks/sendSubscribe request
{
  "jsonrpc": "2.0",
  "method": "tasks/sendSubscribe",
  "id": 1,
  "params": {
    "message": {
      "role": "user",
      "parts": [{ "type": "text", "text": "Write a 500-word report on..." }]
    },
    "configuration": { "mode": "quality" }
  }
}
text/event-stream — SSE response
data: {"id":"task_01J...","status":{"state":"processing","timestamp":"..."}}

data: {"id":"task_01J...","status":{"state":"processing"},"delta":{"type":"text","text":"The "}}

data: {"id":"task_01J...","status":{"state":"processing"},"delta":{"type":"text","text":"report "}}

data: {"id":"task_01J...","status":{"state":"completed","timestamp":"..."},"artifacts":[...]}
javascript — consuming the SSE stream
const response = await fetch('https://p402.io/api/a2a', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.P402_API_KEY}`,
    'Content-Type': 'application/json',
    'Accept': 'text/event-stream',
  },
  body: JSON.stringify({
    jsonrpc: '2.0',
    method: 'tasks/sendSubscribe',
    id: 1,
    params: {
      message: { role: 'user', parts: [{ type: 'text', text: 'Hello' }] },
      configuration: { mode: 'speed' },
    },
  }),
});

const reader = response.body!.getReader();
const decoder = new TextDecoder();
let output = '';

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  const lines = decoder.decode(value).split('\n');
  for (const line of lines) {
    if (!line.startsWith('data: ')) continue;
    const event = JSON.parse(line.slice(6));
    if (event.delta?.type === 'text') output += event.delta.text;
    if (event.status?.state === 'completed') break;
  }
}
console.log(output);
POSTtasks/get

Retrieves a task by ID. Use this to poll for completion when you submitted with tasks/send and want to check status asynchronously.

json — tasks/get request
{
  "jsonrpc": "2.0",
  "method": "tasks/get",
  "id": 2,
  "params": { "id": "task_01J..." }
}
POSTtasks/cancel

Cancels an in-progress task. Only tasks in pending or processing state can be cancelled. Cancelling a terminal task returns an error.

json — tasks/cancel request
{
  "jsonrpc": "2.0",
  "method": "tasks/cancel",
  "id": 3,
  "params": { "id": "task_01J..." }
}

// Response: task with state = "cancelled"
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": { "id": "task_01J...", "status": { "state": "cancelled", "timestamp": "..." } }
}

Agent Discovery

Before calling an agent, you can discover its capabilities by fetching its AgentCard — a machine-readable JSON manifest served at the standard well-known URL.

bash — fetch P402's AgentCard
curl https://p402.io/.well-known/agent.json
json — AgentCard response
{
  "protocolVersion": "1.0",
  "name": "P402 Payment Router",
  "description": "AI orchestration router with x402 payment settlement.",
  "url": "https://p402.io",
  "capabilities": {
    "streaming": true,
    "pushNotifications": false
  },
  "skills": [
    {
      "id": "ai-completion",
      "name": "Chat Completion",
      "description": "Route a chat completion to the optimal provider",
      "tags": ["ai", "llm", "routing"]
    }
  ],
  "extensions": [
    {
      "uri": "tag:x402.org,2025:x402-payment",
      "description": "Accepts x402 EIP-3009 USDC payments on Base"
    }
  ],
  "endpoints": {
    "a2a": {
      "jsonrpc": "https://p402.io/api/a2a",
      "stream": "https://p402.io/api/a2a/stream"
    }
  }
}

x402 Payment Extension

When an agent declares the x402-payment extension in its AgentCard, it can require payment before completing a task. The payment negotiation happens entirely within the A2A task context — no separate payment API call.

The flow has three steps: the agent sends a payment request, the caller submits a signed EIP-3009 authorization, and the agent confirms settlement before delivering the result.

1

Agent sends payment-required

The agent responds to the initial task with a payment request embedded in the result. The task state remains processing until payment is received.

json — payment-required in task result
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "id": "task_01J...",
    "status": { "state": "processing" },
    "extension": {
      "uri": "tag:x402.org,2025:x402-payment",
      "content": {
        "type": "payment-required",
        "data": {
          "payment_id": "pay_abc123",
          "amount_usd": 0.05,
          "schemes": [{
            "scheme": "exact",
            "asset": "USDC",
            "network": "eip155:8453",
            "amount": "50000",   // 0.05 USDC in atomic units (6 decimals)
            "payTo": "0xFa772434DCe6ED78831EbC9eeAcbDF42E2A031a6"
          }],
          "valid_until": "2026-04-16T12:05:00.000Z"
        }
      }
    }
  }
}
2

Caller submits signed payment

The caller signs an EIP-712 TransferWithAuthorization and submits it as a new task message. P402 verifies the signature and nonce before forwarding to the agent.

json — payment-submitted message
{
  "jsonrpc": "2.0",
  "method": "tasks/send",
  "id": 2,
  "params": {
    "id": "task_01J...",    // Continue the same task
    "message": {
      "role": "user",
      "parts": [{
        "type": "data",
        "data": {
          "extension": "tag:x402.org,2025:x402-payment",
          "type": "payment-submitted",
          "payment_id": "pay_abc123",
          "scheme": "exact",
          "authorization": {
            "from": "0xYourWalletAddress",
            "to": "0xFa772434DCe6ED78831EbC9eeAcbDF42E2A031a6",
            "value": "50000",
            "validAfter": "1713261600",
            "validBefore": "1713265200",
            "nonce": "0xabc123..."
          },
          "signature": "0x..."    // EIP-712 signature over the authorization
        }
      }]
    }
  }
}
3

Agent confirms and delivers result

After P402 settles the on-chain transfer, the agent receives confirmation and completes the original task. The response includes a receipt ID for your records.

json — payment-completed and task result
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "id": "task_01J...",
    "status": { "state": "completed", "timestamp": "2026-04-16T12:00:45.000Z" },
    "artifacts": [{
      "name": "completion",
      "parts": [{ "type": "text", "text": "Here is the premium analysis you requested..." }]
    }],
    "extension": {
      "uri": "tag:x402.org,2025:x402-payment",
      "content": {
        "type": "payment-completed",
        "payment_id": "pay_abc123",
        "receipt_id": "rec_789xyz",
        "tx_hash": "0x..."
      }
    },
    "metadata": { "cost_usd": 0.05, "latency_ms": 3200 }
  }
}

Error Handling

A2A errors are returned as standard JSON-RPC error objects. The HTTP status is always 200 — errors are embedded in the JSON payload, not in the HTTP status code. This lets orchestrators handle them without HTTP error detection logic.

json — JSON-RPC error response
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32000,
    "message": "BILLING_CAP_REACHED",
    "data": {
      "budget_usd": 10.00,
      "spent_usd": 10.00,
      "session_id": "ses_..."
    }
  }
}
CodeMessageMeaning & Action
-32700Parse errorRequest body is not valid JSON. Fix the serialization.
-32600Invalid requestMissing jsonrpc, method, or id. Check the envelope structure.
-32601Method not foundUnknown method. Valid: tasks/send, tasks/get, tasks/cancel, tasks/sendSubscribe.
-32602Invalid paramsMethod params are malformed. Check the required fields.
-32000BILLING_CAP_REACHEDSession budget exhausted. Create a new session or increase the budget.
-32000TASK_NOT_FOUNDtasks/get or tasks/cancel called with an unknown task ID.
-32000TASK_ALREADY_TERMINALtasks/cancel called on a completed/failed task.
-32000PAYMENT_EXPIREDx402 payment authorization expired before it was submitted.
-32000PAYMENT_INVALIDx402 signature failed verification. Re-sign and resubmit.

Billing errors are not HTTP 402

When an agent hits a billing cap, P402 maps it to JSON-RPC error code -32000 with message BILLING_CAP_REACHED. It does NOT return HTTP 402. This prevents orchestrators from misinterpreting it as an x402 payment request rather than a budget error.