Skip to content
Console & keys / Agents that pay (x402)
  1. Docs
  2. Console & keys
  3. Agents that pay (x402)

Agents that pay (x402)

An agent with USDC on Base or Solana buys API credit and a live key in one HTTP call, with no account and no card: x402 v2, the network fee paid for it.

Open .md

An agent with a crypto wallet does not need a person to sign up, add a card or copy a key. It pays in USDC on Base or on Solana with x402, the HTTP payment protocol, and the same call returns an API key and the credit. The paying wallet is the account: no email, no password, no console.

EndpointPOST https://api.spacecontext.app/v1/agent/credits
NetworksBase (eip155:8453) and Solana (solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp), USDC
Amountamount_cents from 100 ($1) to 50 000 ($500); without a body, $1
What it buysCredit for live scans at $1.00 each; reading, search and answers are included. The credit does not expire
Network feePaid by the facilitator: the wallet needs only USDC, no ETH or SOL
KeyA live key with every scope (spaces:read, scans:read, scans:write, spaces:sensitive)

To try the API first without paying, use a test key: it is free and runs everything on the demo space.

Pay with an x402 client

Any x402 v2 client works. With the official one for TypeScript, a call with no body buys $1, one scan, and goes through as is: x402 clients refuse to pay more than $1 at once unless you raise their limit (spendControls), as below to buy five scans.

Terminal
npm install @x402/fetch @x402/evm viem          # Base
npm install @x402/fetch @x402/svm @solana/kit   # Solana

Base, with the wallet’s private key (0x…):

TypeScript
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.

Solana, with the wallet’s secret key in base58 (what Phantom or Solflare export):

TypeScript
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.

The answer:

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/"
}

Then use the key like any other: Authorization: Bearer sc_live_… on the API, SPACECONTEXT_API_KEY for the CLI and the SDK. Start with Quickstart: create a space, upload a video, start the scan.

What happens on the wire

  1. 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 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. The same JSON is in the body as error.payment_required, so you can read it with curl:

    Terminal
    curl -s -X POST https://api.spacecontext.app/v1/agent/credits | jq .error.payment_required.accepts
  2. The client picks one option and signs it: on Base 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. It sends the same request with the same body again, adding PAYMENT-SIGNATURE. The amount follows the body: a payment signed for $5 does not buy $10.

  4. We check the payment against what we asked (network, asset, recipient, amount, signature) before anything is sent on; the facilitator verifies and settles it; the answer carries the key, the balance and a PAYMENT-RESPONSE header with the transaction.

The 402 also carries extensions.bazaar, which describes the call for x402 catalogs: agents that browse a catalog can find this endpoint without these docs.

The account and its key

  • One wallet, one account. Every payment from the same wallet tops up the same account. A Base wallet and a Solana wallet are two accounts. Agent accounts have no free scan.
  • The key is shown once, in the answer of the payment that opens the account. Later payments from the wallet return only api_key_prefix: a signed payment can be submitted by whoever holds it, so a payment alone never hands out a key for an account that already exists.
  • Replace the key by paying again with {"rotate_key": true} and Authorization: Bearer <the current key>: a new key comes back and the old one is revoked. Lost the key? Write to support with the paying address and one of its transactions.
  • The same payment twice (a retry, a relay) is recognised by its transaction: nothing is credited or created twice.
  • Failed scans are refunded to the balance, as for every account. Paying accepts the Terms (section 30 covers payments by agents).

Errors

All answers use the usual error format; reason says why a payment was refused. Nothing is charged in any of these cases, except where noted.

StatusCodeWhen
400PAYMENT_INVALIDPAYMENT-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
402PAYMENT_REQUIREDNo payment yet: the options are in PAYMENT-REQUIRED
402PAYMENT_FAILEDThe 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)
403AGENT_KEY_REQUIREDrotate_key, or a key for an account that has none, without one of the account’s current keys
429RATE_LIMIT_EXCEEDEDMore than 5 requests a minute from one IP address
500INTERNAL_ERRORThe payment settled but could not be recorded: it is reconciled by hand; keep transaction
502PAYMENT_FAILEDThe settlement could not be confirmed: it may have gone through; write to support with nonce (Base) or signature (Solana)
503PAYMENTS_UNAVAILABLEThe chosen network cannot be served right now; try again later or pay on the other one

Space Context docs · llms.txt · download Edit on GitHub

esc

↑↓ navigate↵ openesc close