---
title: "x402 example: how AI agents pay for an API with USDC"
description: "A working x402 example on Base and Solana: an AI agent pays USDC and gets an API key and credit in one HTTP call. The 402 flow, spend caps, rotation, errors."
lang: en
date: 2026-10-11
tags: ["x402", "ai agents", "payments", "usdc"]
source_url: https://spacecontext.app/blog/x402-ai-agents-pay-for-api-usdc/
---

# x402 example: how AI agents pay for an API with USDC

A working x402 example on Base and Solana: an AI agent pays USDC and gets an API key and credit in one HTTP call. The 402 flow, spend caps, rotation, errors.

An AI agent pays for an API with x402 by calling the endpoint, receiving an HTTP 402 response that says what to pay and where, signing a USDC payment with its wallet, and sending the same request again with a `PAYMENT-SIGNATURE` header. Space Context uses this for `POST /v1/agent/credits`: one paid call on Base or Solana returns a live API key and prepaid credit, with no account, no card and no gas token to hold. Below is the complete flow, with the client code from the docs.

**In short**

- Endpoint: `POST https://api.spacecontext.app/v1/agent/credits`, USDC on Base (`eip155:8453`) or Solana (`solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp`), from $1 to $500 per payment.
- The official x402 client for TypeScript wraps `fetch`. It refuses to pay more than $1 at once unless you raise `spendControls`.
- The facilitator pays the network fee: the wallet needs only USDC.
- The paying wallet is the account. The key is shown once; `rotate_key` with the current key replaces it.
- You can build and test everything else for free with a test key first.

## What is x402?

HTTP has a status code for payments that has never had a defined use. [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110.html#section-15.5.3) still says only: "The 402 (Payment Required) status code is reserved for future use." [x402](https://x402.org) gives it a use. It describes itself as "an open, neutral standard for internet-native payments", and the mechanism fits in one sentence from its site: "If a request arrives without payment, the server responds with HTTP 402, prompting the client to pay and retry."

A third party, the **facilitator**, does the blockchain work. In the [x402 docs](https://docs.x402.org/core-concepts/facilitator), it verifies that a payment payload meets the server's requirements and settles it on chain, and it "does not hold funds or act as a custodian." Space Context implements x402 v2.

For an agent, this removes the steps that usually need a person: signing up, adding a card, copying a key into a config file.

## How does the x402 payment flow work on the wire?

Four steps, as the [agent payments docs](https://spacecontext.app/developers/docs/account/agent-payments/) describe them:

1. **Ask.** `POST /v1/agent/credits` without payment answers **402** with a `PAYMENT-REQUIRED` header: base64 of a JSON whose `accepts` lists one option per network, Base first. Each option names the amount in atomic USDC units (6 decimals: $1.00 is `1000000`), the USDC contract or mint, the address to pay (`payTo`) and, on Solana, `extra.feePayer`, the facilitator account that pays the network fee.
2. **Sign.** The client picks one option. On Base it signs an EIP-3009 `transferWithAuthorization`; on Solana a transaction that moves exactly that amount of USDC into the `payTo` wallet's USDC account, which the client signs and the facilitator co-signs as fee payer.
3. **Retry.** It sends the same request, with the same body, adding `PAYMENT-SIGNATURE`.
4. **Settle.** Space Context checks the payment against what it asked (network, asset, recipient, amount, signature) before anything is sent on. The facilitator verifies and settles it, and the answer carries the key, the balance and a `PAYMENT-RESPONSE` header with the transaction.

The same JSON as the header is in the body as `error.payment_required`, so you can inspect the options with `curl`:

```bash
curl -s -X POST https://api.spacecontext.app/v1/agent/credits | jq .error.payment_required.accepts
```

The 402 also carries `extensions.bazaar`, which describes the call for x402 catalogs: an agent that browses a catalog can find this endpoint without reading any docs.

Building the rest of your integration first? A [free test key](https://spacecontext.app/developers/console/) runs every API flow on the demo space with no charge, so you can pay with USDC only when you go live.

## x402 example on Base (TypeScript)

Install the client, the EVM scheme and viem:

```bash
npm install @x402/fetch @x402/evm viem          # Base
```

Then wrap `fetch` with the wallet's private key (`0x…`). This example buys five scans:

```ts
import { wrapFetchWithPaymentFromConfig } from '@x402/fetch';
import { ExactEvmScheme } from '@x402/evm';
import { privateKeyToAccount } from 'viem/accounts';

const pay = wrapFetchWithPaymentFromConfig(fetch, {
  schemes: [{ network: 'eip155:8453', client: new ExactEvmScheme(privateKeyToAccount(process.env.EVM_PRIVATE_KEY as `0x${string}`)) }],
  spendControls: { maxAmountPerPayment: '$5' }, // the client's own cap: $1 unless raised
});

const res = await pay('https://api.spacecontext.app/v1/agent/credits', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ amount_cents: 500 }),
});
const credit = await res.json();
// credit.api_key: store it now, it is shown only once.
```

## x402 example on Solana (TypeScript)

```bash
npm install @x402/fetch @x402/svm @solana/kit   # Solana
```

With the wallet's secret key in base58 (what Phantom or Solflare export):

```ts
import { wrapFetchWithPaymentFromConfig } from '@x402/fetch';
import { ExactSvmScheme, toClientSvmSigner } from '@x402/svm';
import { createKeyPairSignerFromBytes, getBase58Encoder } from '@solana/kit';

const signer = await createKeyPairSignerFromBytes(getBase58Encoder().encode(process.env.SOLANA_PRIVATE_KEY!));
const pay = wrapFetchWithPaymentFromConfig(fetch, {
  schemes: [{ network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', client: new ExactSvmScheme(toClientSvmSigner(signer)) }],
  spendControls: { maxAmountPerPayment: '$5' },
});
// …then the same pay('https://api.spacecontext.app/v1/agent/credits', …) call as above.
```

Either way, the answer looks like this:

```json
{
  "object": "agent_credit",
  "network": "eip155:8453",
  "payer": "0x5e1f…c0de",
  "transaction": "0x8a3b…41f2",
  "livemode": true,
  "amount_cents": 500,
  "balance_cents": 500,
  "api_key": "sc_live_…",
  "api_key_prefix": "sc_live_ab12",
  "notice": "Store api_key now: it is shown only this once. …",
  "docs": "https://spacecontext.app/developers/docs/"
}
```

## Why does the x402 client stop at $1?

The x402 client has a spending cap of its own: it refuses to pay more than $1 in one payment unless you raise it with `spendControls`. A call with no body buys $1, one scan, and goes through as is. To buy more, raise `maxAmountPerPayment` and send `amount_cents`, as in the examples above (`'$5'` and `500`).

Treat that cap as the agent's card limit. Set it to what the task needs, not to the maximum the endpoint accepts ($500). The server holds its side of the deal too: the amount follows the body, so a payment signed for $5 does not buy $10.

## Who pays the gas fee?

The facilitator, on both networks. The wallet needs only USDC, no ETH and no SOL.

- **Base.** [EIP-3009](https://eips.ethereum.org/EIPS/eip-3009) lets a token holder sign a transfer that a third party submits on chain. The agent signs a `transferWithAuthorization`; the facilitator submits it and pays the gas.
- **Solana.** The client builds a transaction whose fee payer is the facilitator account named in `extra.feePayer`. The agent signs the transfer; the facilitator co-signs as fee payer and sends it.

## How can a payment be the API credential?

A signature from the wallet proves control of the wallet, so the wallet can be the account: no email, no password, no console. Every payment from the same wallet tops up the same account. A Base wallet and a Solana wallet are two accounts.

A signed payment has one weakness: whoever holds it can submit it, and the request body is not part of what the wallet signs. Space Context designs around that:

- **The key is shown once**, in the answer of the payment that opens the account. Later payments from the wallet top it up and return only `api_key_prefix`. A payment alone never hands out a key for an account that already exists.
- **Rotation needs the current key.** Pay again with `{"rotate_key": true}` and `Authorization: Bearer <the current key>`: a new key comes back and the old one is revoked. Without a current key the request answers `403 AGENT_KEY_REQUIRED`, and nothing is charged.
- **The same payment twice** (a retry, a relay) is recognized by its transaction: nothing is credited or created twice.
- **Nothing is taken on trust.** The server compares the signed payment with its own requirements (network, asset, recipient, amount, signature) before the facilitator sees it.

A rotation, with the `pay` function from the examples above. Like any payment without `amount_cents`, it also adds $1 of credit:

```ts
const res = await pay('https://api.spacecontext.app/v1/agent/credits', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${process.env.SPACECONTEXT_API_KEY}`,
  },
  body: JSON.stringify({ rotate_key: true }),
});
```

Lost the key? Write to support with the paying address and one of its transactions.

## What does the agent get for its money?

- **A live key with every scope:** `spaces:read`, `scans:read`, `scans:write` and `spaces:sensitive`. Keep it in a secret store: whatever holds it can read the codes recorded in your spaces and spend the credit.
- **Credit for live scans at $1.00 each.** Reading, search and answers are included, and the credit does not expire. Agent accounts have no free scan. Failed scans are refunded to the balance.
- **The whole API.** Use the key as `Authorization: Bearer sc_live_…`, or as `SPACECONTEXT_API_KEY` for the CLI and the SDK. A first scan takes three steps: create a space, upload a video, start the scan. The [quickstart](https://spacecontext.app/developers/docs/quickstart/) walks through it, and [turning a video into structured JSON](https://spacecontext.app/blog/video-to-structured-json-api/) shows what comes back.

Paying accepts the [Terms](https://spacecontext.app/terms/); section 30 covers payments by agents.

## Which x402 errors should an agent handle?

Every answer uses the usual error envelope, and `reason` says why a payment was refused. Nothing is charged in any of these cases, except where noted.

| Status | Code | When |
| --- | --- | --- |
| 400 | `PAYMENT_INVALID` | `PAYMENT-SIGNATURE` is malformed or does not match what was asked: another amount, network, asset or recipient, a Solana transaction for another fee payer, a signature that does not verify |
| 402 | `PAYMENT_REQUIRED` | No payment yet: the options are in `PAYMENT-REQUIRED` |
| 402 | `PAYMENT_FAILED` | The facilitator refused or could not settle it, e.g. not enough USDC in the wallet (`invalid_exact_evm_insufficient_balance` on Base, `invalid_exact_svm_transaction_simulation_failed` on Solana) |
| 403 | `AGENT_KEY_REQUIRED` | `rotate_key`, or a key for an account that has none, without one of the account's current keys |
| 429 | `RATE_LIMIT_EXCEEDED` | More than 5 requests a minute from one IP address |
| 500 | `INTERNAL_ERROR` | The payment settled but could not be recorded: it is reconciled by hand; keep `transaction` |
| 502 | `PAYMENT_FAILED` | The settlement could not be confirmed: it may have gone through; write to support with `nonce` (Base) or `signature` (Solana) |
| 503 | `PAYMENTS_UNAVAILABLE` | The chosen network cannot be served right now; try again later or pay on the other one |

Two rows deserve a rule in your agent: on a 500, store `transaction`; on a 502, store the nonce or signature, and do not pay again until you know whether the first payment went through.

## Start without paying

Space Context turns a video of a physical space into a structured Space Document that agents read through the API, the SDK, the CLI and the MCP server. An agent can buy its own access with x402. You can build the whole integration before that on a free test key, which runs every flow on the demo space with no charge and no model called.

**[Get a free test key](https://spacecontext.app/developers/console/)**

## FAQ

### How does an AI agent pay for an API with x402?

It calls the endpoint, receives HTTP 402 with a PAYMENT-REQUIRED header listing what to pay and where, signs a USDC payment with its wallet, and sends the same request again with a PAYMENT-SIGNATURE header. A facilitator verifies and settles the payment, and the server answers the request.

### Does the agent's wallet need ETH or SOL for gas?

Not for Space Context. The facilitator pays the network fee on both Base and Solana, so the wallet needs only USDC.

### Why does my x402 client refuse to pay more than $1?

x402 clients refuse to pay more than $1 at once unless you raise their limit with spendControls, for example maxAmountPerPayment: '$5'. A call to POST /v1/agent/credits with no body buys $1, one scan, and goes through without changing the limit.

### What happens if the agent loses the API key it bought?

The key is shown only once, in the answer of the payment that opens the account. To replace a key you still have, pay again with {"rotate_key": true} and the current key as a bearer token; if it is lost, write to support with the paying address and one of its transactions.

### Can I test the integration without spending USDC?

Yes. A Space Context test key (sc_test_…) is free and runs every flow on the demo space with no charge and no model called. Create one in the console.
