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

# x402 Payments

> HTTP-native micropayments for API access

## Overview

ouroborai uses the [x402 protocol](https://x402.org) to gate access to agent routes. x402 extends HTTP with a native payment layer built on the `402 Payment Required` status code. Every request to `/agent/*` costs **\$0.01 USDC** on Arbitrum One.

This approach removes the need for API key management on agent routes. Instead of signing up for an account and provisioning credentials, you pay per request using an on-chain USDC transfer authorization.

<Note>
  USDC on Arbitrum uses 6 decimal places. The default price of \$0.01 is represented as `10000` in raw USDC units.
</Note>

## How it works

When a client makes a request to any `/agent/*` endpoint without a valid payment, the server responds with HTTP `402` and a JSON body describing what payment is required. The client constructs a signed EIP-3009 `transferWithAuthorization` payload, attaches it as a base64-encoded `X-PAYMENT` header, and retries the request.

<Steps>
  <Step title="Request without payment">
    The client sends a request to an agent endpoint. The server returns `402 Payment Required` with a JSON body describing the accepted payment schemes, asset, amount, and recipient.
  </Step>

  <Step title="Construct payment payload">
    The client reads the `402` response, builds an EIP-3009 `transferWithAuthorization` message for the specified USDC amount, and signs it with their wallet.
  </Step>

  <Step title="Retry with X-PAYMENT header">
    The client re-sends the original request with the signed payment encoded as a base64 JSON string in the `X-PAYMENT` header.
  </Step>

  <Step title="Server verifies and processes">
    The server decodes the header, validates the EIP-712 typed data signature, checks the nonce for replay prevention, and proceeds with the request. A `X-PAYMENT-RESPONSE` header is attached to the response confirming success.
  </Step>
</Steps>

## 402 response format

When you call an agent endpoint without a payment header, the server returns this structure:

```json theme={"theme":{"light":"dracula","dark":"dracula"}}
{
  "error": "Payment required",
  "x402Version": 1,
  "accepts": [
    {
      "scheme": "exact",
      "network": "arbitrum",
      "maxAmountRequired": "10000",
      "resource": "https://api.ouroborai.com/agent/prompt",
      "description": "ArbitrumAgent API — $0.01 USDC per request",
      "payTo": "0xYourFacilitatorAddress",
      "maxTimeoutSeconds": 300,
      "asset": "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
      "extra": {
        "name": "USD Coin",
        "version": "2"
      }
    }
  ]
}
```

Key fields:

| Field                          | Description                                                       |
| ------------------------------ | ----------------------------------------------------------------- |
| `scheme`                       | Payment scheme. Currently only `exact` is supported.              |
| `network`                      | Target chain. Always `arbitrum` for this server.                  |
| `maxAmountRequired`            | Price in raw USDC units (6 decimals). `10000` = \$0.01.           |
| `payTo`                        | The recipient wallet address for payment.                         |
| `asset`                        | USDC contract address on Arbitrum One.                            |
| `extra.name` / `extra.version` | EIP-712 domain parameters for USDC's `transferWithAuthorization`. |

## Payment payload construction

The `X-PAYMENT` header contains a base64-encoded JSON object with the signed EIP-3009 authorization:

```json theme={"theme":{"light":"dracula","dark":"dracula"}}
{
  "scheme": "exact",
  "network": "arbitrum",
  "payload": {
    "signature": "0x...",
    "authorization": {
      "from": "0xYourWalletAddress",
      "to": "0xFacilitatorAddress",
      "value": "10000",
      "validAfter": "0",
      "validBefore": "1716000000",
      "nonce": "0xUniqueNonceBytes32"
    }
  }
}
```

<CodeGroup>
  ```typescript viem theme={"theme":{"light":"dracula","dark":"dracula"}}
  import { signTypedData, encodePacked, keccak256 } from "viem";
  import { privateKeyToAccount } from "viem/accounts";

  const USDC_ARBITRUM = "0xaf88d065e77c8cc2239327c5edb3a432268e5831";

  const account = privateKeyToAccount("0xYourPrivateKey");

  // Generate a unique nonce (bytes32)
  const nonce = keccak256(
    encodePacked(["address", "uint256"], [account.address, BigInt(Date.now())])
  );

  const authorization = {
    from: account.address,
    to: "0xFacilitatorAddress", // from 402 response payTo
    value: 10000n,              // $0.01 USDC
    validAfter: 0n,
    validBefore: BigInt(Math.floor(Date.now() / 1000) + 300),
    nonce,
  };

  const signature = await signTypedData({
    account,
    domain: {
      name: "USD Coin",
      version: "2",
      chainId: 42161,
      verifyingContract: USDC_ARBITRUM,
    },
    types: {
      TransferWithAuthorization: [
        { name: "from", type: "address" },
        { name: "to", type: "address" },
        { name: "value", type: "uint256" },
        { name: "validAfter", type: "uint256" },
        { name: "validBefore", type: "uint256" },
        { name: "nonce", type: "bytes32" },
      ],
    },
    primaryType: "TransferWithAuthorization",
    message: authorization,
  });

  const paymentPayload = {
    scheme: "exact",
    network: "arbitrum",
    payload: {
      signature,
      authorization: {
        from: authorization.from,
        to: authorization.to,
        value: authorization.value.toString(),
        validAfter: authorization.validAfter.toString(),
        validBefore: authorization.validBefore.toString(),
        nonce: authorization.nonce,
      },
    },
  };

  const xPayment = btoa(JSON.stringify(paymentPayload));
  ```

  ```typescript fetch theme={"theme":{"light":"dracula","dark":"dracula"}}
  // After constructing xPayment from the previous example:

  const response = await fetch("https://api.ouroborai.com/agent/prompt", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-PAYMENT": xPayment,
    },
    body: JSON.stringify({
      prompt: "What is the price of ETH?",
    }),
  });

  const data = await response.json();
  // { jobId: "abc123", threadId: "thread_xyz", status: "pending" }
  ```
</CodeGroup>

## Replay prevention

Every payment includes a unique `nonce` (bytes32). The server atomically claims each nonce to prevent the same payment from being used twice.

<AccordionGroup>
  <Accordion title="Redis (production)">
    When `REDIS_URL` is set, nonces are stored using the `SET NX EX` pattern:

    ```
    SET arb:nonce:{nonce} "1" EX 86400 NX
    ```

    * `NX` ensures the key is only set if it does not already exist (atomic claim).
    * `EX 86400` sets a 24-hour TTL so stale nonces are automatically cleaned up.

    If the `SET` returns `null` (key already existed), the payment is rejected as a replay.
  </Accordion>

  <Accordion title="In-memory (development)">
    When no Redis connection is available, nonces are tracked in a local `Set`. This works for single-instance development but does not survive restarts and cannot be shared across multiple server instances.
  </Accordion>
</AccordionGroup>

## Revenue tracking

Every verified payment is recorded for per-facilitator revenue accounting. The payer address is extracted from the payment authorization and stored alongside the amount and request path.

Revenue data is available through the admin dashboard:

* `GET /admin/revenue` -- total revenue, unique payers, recent payments
* `GET /admin/revenue/history?days=30` -- daily breakdown

Revenue storage uses Redis when available (`arb:revenue:*` keys with 90-day TTL) and falls back to in-memory counters otherwise.

## Development mode

For local development, set the `SKIP_PAYMENT` environment variable to bypass payment verification entirely:

```bash theme={"theme":{"light":"dracula","dark":"dracula"}}
SKIP_PAYMENT=true bun run apps/api/src/index.ts
```

When payment is skipped, the middleware sets the owner ID to `"dev"` and all agent routes are accessible without an `X-PAYMENT` header.

<Warning>
  Never set `SKIP_PAYMENT=true` in production. Doing so disables all payment verification on agent routes.
</Warning>

## Verification details

The server validates the following before accepting a payment:

1. **Scheme and network** -- must be `exact` and `arbitrum`
2. **Recipient** -- the `to` address must match the configured `PAY_TO_ADDRESS`
3. **Amount** -- the `value` must be greater than or equal to the required amount (default `10000`)
4. **Sender** -- the `from` address must be present
5. **Nonce** -- the nonce must not have been previously claimed
6. **Signature** -- the EIP-712 typed data signature must be valid for the USDC `transferWithAuthorization` domain on Arbitrum (chain ID `42161`)

If any check fails, the server returns `402` with `{"error": "Invalid or insufficient payment"}`.
