> ## Documentation Index
> Fetch the complete documentation index at: https://docs.key0.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# x402 HTTP Flow

> The simplest way to interact with Key0 -- a single POST /x402/access endpoint that handles discovery, challenge creation, and payment settlement.

The x402 HTTP flow is the simplest way to interact with Key0. Plan discovery uses `GET /discover`, and the `POST /x402/access` endpoint handles challenge and settlement:

1. **Discovery** -- `GET /discover` returns all available plans (HTTP 200). `POST /x402/access` without `planId` returns HTTP 400.
2. **Challenge** -- `planId` present, no `payment-signature` header, creates a PENDING record.
3. **Settlement (subscription)** -- `planId` + `payment-signature`, settles on-chain, returns an `AccessGrant` (JWT).
4. **Settlement (per-request standalone)** -- `planId` + `resource` + `payment-signature`, settles on-chain, proxies to the backend, returns a `ResourceResponse` (API data, no token).

<Note>
  For a full sequence diagram of the Discovery → Challenge → Settlement flow, see [How It Works](/introduction/how-it-works#sequence-diagram).
</Note>

## Four Cases

<Tabs>
  <Tab title="Case 1: Discovery">
    Use `GET /discover` to browse all available plans. No PENDING record is created -- this is a pure pricing query.

    Note: `POST /x402/access` without `planId` returns HTTP 400 with a pointer to this endpoint.

    ### Request

    ```bash theme={null}
    curl https://api.example.com/discover
    ```

    ### Response

    ```http theme={null}
    HTTP/1.1 200 OK
    Content-Type: application/json
    ```

    ```json theme={null}
    {
      "agentName": "My Agent",
      "description": "Payment-gated API",
      "plans": [
        {
          "planId": "basic",
          "unitAmount": "$0.10",
          "description": "Basic plan - $0.10 USDC"
        }
      ],
      "routes": []
    }
    ```

    The discovery response contains one entry per plan configured in `SellerConfig.plans`, plus any route metadata when per-request routes are enabled.
  </Tab>

  <Tab title="Case 2: Challenge">
    Send a `planId` (and optionally `requestId` and `resourceId`) without a `payment-signature` header. The server creates a PENDING challenge record via `engine.requestHttpAccess()` and returns payment requirements for that specific plan.

    <Note>
      If you omit `requestId`, the server auto-generates one in `http-{uuid}` format. You can provide your own UUID for idempotent retries -- sending the same `requestId` again returns the existing challenge instead of creating a new one.
    </Note>

    ### Request

    ```bash theme={null}
    curl -X POST https://api.example.com/x402/access \
      -H "Content-Type: application/json" \
      -d '{
        "planId": "basic",
        "requestId": "550e8400-e29b-41d4-a716-446655440000",
        "resourceId": "photo-123"
      }'
    ```

    ### Response

    ```http theme={null}
    HTTP/1.1 402 Payment Required
    payment-required: eyJ4NDAyVm... (base64-encoded JSON)
    www-authenticate: Payment realm="https://api.example.com", accept="exact", challenge="http-a1b2c3d4-..."
    Content-Type: application/json
    ```

    ```json theme={null}
    {
      "x402Version": 2,
      "accepts": [
        {
          "scheme": "exact",
          "network": "eip155:84532",
          "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
          "amount": "100000",
          "payTo": "0xSellerWallet...",
          "maxTimeoutSeconds": 900,
          "extra": {
            "name": "USDC",
            "version": "2",
            "description": "Basic plan - $0.10 USDC"
          }
        }
      ],
      "extensions": {
        "key0": {
          "inputSchema": { "..." : "..." },
          "outputSchema": { "..." : "..." },
          "description": "..."
        }
      },
      "challengeId": "http-a1b2c3d4-...",
      "error": "Payment required"
    }
    ```

    The response now includes a `challengeId` that ties the payment to this specific challenge record. The `www-authenticate` header also carries the challenge ID.
  </Tab>

  <Tab title="Case 3: Settlement">
    Send the same `planId` and `requestId` along with a `payment-signature` header containing the signed EIP-3009 authorization. The server decodes the header, calls `settlePayment()` to execute the transfer on-chain, then calls `engine.processHttpPayment()` to transition the challenge through PENDING, PAID, and DELIVERED.

    ### Request

    ```bash theme={null}
    curl -X POST https://api.example.com/x402/access \
      -H "Content-Type: application/json" \
      -H "payment-signature: eyJ4NDAyVm..." \
      -d '{
        "planId": "basic",
        "requestId": "550e8400-e29b-41d4-a716-446655440000",
        "resourceId": "photo-123"
      }'
    ```

    The `payment-signature` header is a base64-encoded `X402PaymentPayload` (see [EIP-3009 Authorization](#eip-3009-authorization) below for the decoded structure).

    ### Response

    ```http theme={null}
    HTTP/1.1 200 OK
    payment-response: eyJzdWNjZXNz... (base64-encoded X402SettleResponse with txHash)
    Content-Type: application/json
    ```

    ```json theme={null}
    {
      "type": "AccessGrant",
      "challengeId": "http-a1b2c3d4-...",
      "accessToken": "eyJhbGciOiJIUzI1NiIs...",
      "tokenType": "Bearer",
      "resourceEndpoint": "https://api.example.com/photos/photo-123",
      "resourceId": "photo-123",
      "planId": "basic",
      "txHash": "0xSettledTx...",
      "explorerUrl": "https://sepolia.basescan.org/tx/0xSettledTx..."
    }
    ```

    The `payment-response` header contains the full `X402SettleResponse` from the on-chain settlement, base64-encoded. The JSON body is the `AccessGrant` with the JWT and resource endpoint URL.
  </Tab>

  <Tab title="Case 4: Route-Based Settlement">
    For standalone paid routes (seller has `proxyTo` or `fetchResource` configured), include `routeId` and a `resource` field in the body. After payment, Key0 proxies the request to the backend and returns a `ResourceResponse` — **no JWT issued**.

    ### Challenge request

    ```bash theme={null}
    curl -X POST https://api.example.com/x402/access \
      -H "Content-Type: application/json" \
      -d '{
        "routeId": "weather-query",
        "requestId": "550e8400-e29b-41d4-a716-446655440001",
        "resource": { "method": "GET", "path": "/api/weather/london" }
      }'
    ```

    ### Response (HTTP 402 — same shape as subscription)

    ```json theme={null}
    {
      "x402Version": 2,
      "accepts": [
        {
          "scheme": "exact",
          "network": "eip155:84532",
          "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
          "amount": "10000",
          "payTo": "0xSellerWallet...",
          "maxTimeoutSeconds": 900,
          "extra": { "name": "USDC", "version": "2", "routeId": "weather-query" }
        }
      ],
      "challengeId": "http-a1b2c3d4-...",
      "error": "Payment required"
    }
    ```

    ### Settlement request (with resource + payment-signature)

    ```bash theme={null}
    curl -X POST https://api.example.com/x402/access \
      -H "Content-Type: application/json" \
      -H "payment-signature: eyJ4NDAyVm..." \
      -d '{
        "routeId": "weather-query",
        "requestId": "550e8400-e29b-41d4-a716-446655440001",
        "resource": { "method": "GET", "path": "/api/weather/london" }
      }'
    ```

    ### Response (HTTP 200 — ResourceResponse, not AccessGrant)

    ```http theme={null}
    HTTP/1.1 200 OK
    payment-response: eyJzdWNjZXNz... (base64-encoded X402SettleResponse)
    Content-Type: application/json
    ```

    ```json theme={null}
    {
      "type": "ResourceResponse",
      "challengeId": "http-a1b2c3d4-...",
      "requestId": "550e8400-e29b-41d4-a716-446655440001",
      "routeId": "weather-query",
      "txHash": "0xSettledTx...",
      "explorerUrl": "https://sepolia.basescan.org/tx/0xSettledTx...",
      "resource": {
        "status": 200,
        "body": { "city": "london", "tempF": 65, "condition": "Cloudy" }
      }
    }
    ```

    Use `resource.body` directly — no Bearer token, no follow-up request. Every call requires a fresh payment.

    If the backend returns a non-2xx status, the `ResourceResponse` still contains the backend's error body and status. The challenge stays in `PAID` state so the refund cron can process it.

    <Note>
      The `resource` field is required for standalone route purchases. For embedded per-request routes, the `payment-signature` header is sent directly on the route (e.g. `GET /api/weather/london`) — no `/x402/access` involved. See [Paying for Access](/guides/paying-for-access#embedded-per-request).
    </Note>
  </Tab>
</Tabs>

<Note>
  For the complete HTTP headers reference, see [API Reference → Overview](/api-reference/overview#common-headers).
</Note>

## EIP-3009 Authorization

The `payment-signature` header carries a signed [EIP-3009](https://eips.ethereum.org/EIPS/eip-3009) `transferWithAuthorization`. This means the client signs an off-chain authorization that permits a specific USDC transfer, but never sends a transaction directly and never pays gas.

The server (or a facilitator like Coinbase CDP) executes the `transferWithAuthorization` call on-chain, paying the gas fees. The USDC moves from the client's wallet to the seller's wallet in a single atomic transaction.

### Decoded `payment-signature` Structure

```json theme={null}
{
  "x402Version": 2,
  "network": "eip155:84532",
  "scheme": "exact",
  "payload": {
    "signature": "0xSignedEIP3009...",
    "authorization": {
      "from": "0xBuyer...",
      "to": "0xSeller...",
      "value": "100000",
      "validAfter": "0",
      "validBefore": "1741180560",
      "nonce": "0xRandomNonce..."
    }
  },
  "accepted": {
    "scheme": "exact",
    "network": "eip155:84532",
    "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
    "amount": "100000",
    "payTo": "0xSeller...",
    "maxTimeoutSeconds": 900,
    "extra": { "name": "USDC", "version": "2" }
  }
}
```

Key fields:

* **`payload.signature`** -- the EIP-3009 signature authorizing the USDC transfer.
* **`payload.authorization`** -- the transfer parameters: sender, recipient, amount (in USDC base units, 6 decimals), validity window, and a random nonce.
* **`accepted`** -- echoes the `PaymentRequirements` from the 402 response, so the server can verify the client accepted the correct terms.

## Next Steps

<CardGroup cols={2}>
  <Card title="Paying for Access" icon="credit-card" href="/guides/paying-for-access">
    Step-by-step client guide: discover plans, sign EIP-3009, and use the access token.
  </Card>

  <Card title="A2A Flow" icon="arrows-left-right" href="/protocol/a2a-flow">
    The JSON-RPC based agent-to-agent payment flow for native A2A clients.
  </Card>

  <Card title="Settlement Strategies" icon="link" href="/architecture/settlement-strategies">
    Facilitator vs. gas wallet settlement and how EIP-3009 is executed on-chain.
  </Card>

  <Card title="State Machine" icon="arrows-spin" href="/architecture/state-machine">
    The full PENDING / PAID / DELIVERED / EXPIRED / REFUNDED lifecycle.
  </Card>
</CardGroup>
