# Machine payments (x402)

MapMap is designed so that a software agent can pay for routing the same
way it calls it: per request, no dashboard, no human. This page documents the
`402 Payment Required` shape the gateway returns and the two payment paths an
agent can take: prepaid credit (always on) and x402 (on when the operator
configures it).

One thing first: **every request still needs an API key.** x402 pays for a
call; it does not replace authentication. A request without
`Authorization: Bearer snk_…` gets a `401` regardless of any payment header.
Get a key with one call to `POST /v1/keys`; see the
[Quickstart](/docs/quickstart).

**Status, honestly:** the gateway implements the x402 open payment standard
(HTTP transport, protocol version 1) on the server side. The hosted gateway at
`https://api.mapmap.ai` accepts x402 payments on mainnet as of 20 July 2026:
its 402s advertise USDC payment requirements on Base and Solana and settle
through a facilitator; [sign up](/signup) for a key, or run the
[self-host distro](/docs/self-host). Whether a self-hosted
deployment *accepts* x402 payments depends on its configuration: with
`SN_X402_*` set it advertises payment requirements and verifies payments through
a facilitator; without them it is "x402-ready", so the 402 keeps the same wire
shape with an empty `accepts` array and points agents at prepaid credit. Prepaid
Stripe credit is the settlement rail that works on every deployment.

## When you get a 402, and when a 429

Not every out-of-quota response is a 402. What you get past the free tier
depends on your key's state and payment history:

| Situation | Response |
|---|---|
| Provisional key (email not yet verified) past its 1,000-call allowance | `402`; the `error` field tells you to verify your email; an `X-PAYMENT` retry is accepted where x402 is configured, and where `SN_TOPUP_PROVISIONAL_SPEND` is enabled (as it is on the hosted service) a provisional key can also top up and spend prepaid credit |
| Verified key past the free tier, identity **has bought credit before** but the balance is below the per-call price | `402` with payment options in `accepts` |
| Verified key past the free tier, identity has **never bought credit**, x402 configured | `402` with payment options in `accepts`, and an `error` naming the prepaid-credit route as well. Corrected 10 August 2026: this row previously said `429` with no payment offer, which stopped being true on 17 July |
| Verified key past the free tier, identity has **never bought credit**, x402 not configured | `429` quota-exceeded (RFC 9457 problem+json); the classic hard quota signal, no payment offer |
| Operator-issued key past its quota | `429` quota-exceeded; no self-serve payment path |
| Per-minute rate limit hit (any key) | `429` rate-limited, with a `Retry-After` header |

The two 429s are distinguishable by their problem `type`:
`urn:sn-gateway:problem:quota-exceeded` versus
`urn:sn-gateway:problem:rate-limited`. On a deployment with no x402 configured, the never-credited 429 looks like this:

```json
{
  "type": "urn:sn-gateway:problem:quota-exceeded",
  "title": "Monthly quota exceeded",
  "status": 429,
  "detail": "monthly quota exceeded (50000/50000)",
  "quota": 50000,
  "used": 50000
}
```

So an agent that has never paid should not wait for a 402 to discover payment
options: on a 429 quota-exceeded, top up prepaid credit (below); after the
first top-up, insufficient balance produces 402s. See
[Conventions](/docs/conventions) for the full error envelope and the
402-vs-429 rules.

## The 402 body

The `402` is **not** RFC 9457 problem+json like the gateway's other errors. It
is the x402 wire format (plain `application/json`), so x402 clients parse it
directly. On a deployment without x402 configured, a previously-credited key
whose balance has run below the per-call price gets:

```json
{
  "x402Version": 1,
  "error": "free tier of 50000 calls/month exhausted and prepaid balance is below the request price of 1p",
  "accepts": [],
  "instructions": "x402 payment is not enabled on this deployment yet; top up prepaid credits at https://api.mapmap.ai/account to continue"
}
```

A provisional key past its allowance gets the same shape with
`"error": "provisional key call allowance exhausted (1000 calls); verify your
email to unlock the free tier of 50000 calls/month"`. Dispatch on the HTTP
status, never on `error` strings; they are human-readable and may change.

When the operator has configured x402, `accepts` carries one or more
`PaymentRequirements` objects and `instructions` changes accordingly:

```json
{
  "x402Version": 1,
  "error": "free tier of 50000 calls/month exhausted and prepaid balance is below the request price of 1p",
  "accepts": [
    {
      "scheme": "exact",
      "network": "base",
      "maxAmountRequired": "10000",
      "asset": "0x…",
      "payTo": "0x…",
      "resource": "https://api.mapmap.ai/route/v1/truck/1.3134,51.1279;-1.8904,52.4862",
      "description": "One sn-gateway metered API call (truck/ADR routing, compliance or territory download)",
      "mimeType": "application/json",
      "outputSchema": null,
      "maxTimeoutSeconds": 60,
      "extra": null
    }
  ],
  "instructions": "Pay per call by retrying with an X-PAYMENT header satisfying one of `accepts`, or top up prepaid credits at https://api.mapmap.ai/account"
}
```

The `resource` and account URLs in the wire body are both built from the
deployment's single configured public base URL (`SN_PUBLIC_URL`), so they
always share a host and vary together per deployment.

Contract rules:

- The `402` body has no `type`/`title`/`instance`; dispatch on the HTTP
  status and the `x402Version`/`accepts` fields.
- `accepts[]` may be empty ("x402-ready") or carry several requirements;
  ignore schemes you don't implement.
- If `accepts` is empty, an `X-PAYMENT` header is ignored entirely; do not
  retry with payment; top up prepaid credit instead.
- Prepaid credit is always an option for verified keys: poll
  `GET $BASE/v1/keys/self` until `credits_pence > 0`, then retry the original
  request. No new key is needed.

## How an agent pays

Both walkthroughs assume:

```sh
export BASE=https://api.mapmap.ai   # or your own deployment; see /docs/conventions
export API_KEY=snk_...
```

### Prepaid credit (works everywhere)

Trigger: a truck-profile call past the free tier. Truck and ADR calls are the
premium class; ADR is the European agreement on carriage of dangerous goods
by road; tunnel codes B–E restrict which tunnels a hazmat load may use (full
tunnel-code table in [Conventions](/docs/conventions)).

```sh
curl -sS -G "$BASE/route/v1/truck/1.3134,51.1279;-1.8904,52.4862" \
  -H "Authorization: Bearer $API_KEY" \
  --data-urlencode "height=4.0" \
  --data-urlencode "weight=44.0"
```

Past the free tier with no usable balance, that returns one of the `402`
bodies above (or the never-credited `429`). Then:

1. Top up at [mapmap.ai/account](https://mapmap.ai/account) (Stripe card
   top-up), or ask the operator to credit your identity ledger. Credit is a
   balance on your email, shared by all its keys, so one top-up covers future
   402s until spent.
2. Poll your balance; this endpoint consumes no quota, credit or rate limit,
   so agents can poll it freely:

   ```sh
   curl -sS "$BASE/v1/keys/self" -H "Authorization: Bearer $API_KEY"
   ```

   ```json
   {
     "key_id": "3f1c…-uuid",
     "state": "verified",
     "monthly_quota": 50000,
     "used_this_month": 50012,
     "remaining": 0,
     "credits_pence": 500,
     "credits_millipence": 500000,
     "identity_keys": 1
   }
   ```

   `credits_millipence` is the full-precision balance (1p = 1,000
   millipence), useful because per-call prices are fractional.
3. Retry the original request unchanged. Each metered call beyond the free
   tier draws the price for its class: standard routing and data from
   0.05p/call, truck/ADR premium from 1p/call, detected per request (see
   [/pricing.json](https://mapmap.ai/pricing.json) and [Pricing](/pricing)).

**The 4xx refund rule:** the ledger is never charged for a request answered
with a client error. The debit is taken before the handler runs, and refunded
if the response is a 4xx (a bad request, no route found, and so on). Usage
counters still meter the request; only real money moves back. Budget per-call
spend against successful responses.

### x402 inline payment (when `accepts` is non-empty)

A *facilitator* is the service that verifies and settles x402 payments
on-chain on the gateway's behalf; the gateway POSTs to its `/verify` and
`/settle` endpoints and never touches a chain itself.

1. Pick a `PaymentRequirements` entry from `accepts`.
2. Build an x402 `PaymentPayload` for it (scheme `"exact"`). Truncated:

   ```json
   {
     "x402Version": 1,
     "scheme": "exact",
     "network": "base",
     "payload": {
       "signature": "0x…",
       "authorization": { "value": "10000", "…": "…" }
     }
   }
   ```

   The `payload` contents are defined by the scheme; see the
   [x402 core spec v1](https://github.com/coinbase/x402/blob/main/specs/x402-specification-v1.md)
   (§5.2, PaymentPayload) and the
   [HTTP transport spec](https://github.com/coinbase/x402/blob/main/specs/transports-v1/http.md).
   Client libraries in the x402 repository build this for you. The gateway
   itself checks the envelope (`x402Version` must be `1`; `scheme` and
   `network` must be strings) and leaves payload validation to the
   facilitator, with one exception: when the operator sets a maximum,
   `payload.authorization.value` is capped locally and an over-limit payment
   is refused with a 402.
3. Base64-encode the JSON and retry the request, key and payment together:

   ```sh
   export PAYMENT=$(printf '%s' "$PAYLOAD_JSON" | base64)
   curl -sS -G "$BASE/route/v1/truck/1.3134,51.1279;-1.8904,52.4862" \
     -H "Authorization: Bearer $API_KEY" \
     -H "X-PAYMENT: $PAYMENT" \
     --data-urlencode "height=4.0" \
     --data-urlencode "weight=44.0"
   ```

4. The gateway verifies the payment with the configured facilitator's
   `/verify` and, on success, serves the request. It fails **closed**: a
   request is served only after the facilitator confirms a valid, sufficient
   payment. A facilitator error is another `402` with the reason in `error`,
   never free routing. Your prepaid ledger is left untouched when you pay
   inline.
5. Settlement runs asynchronously via the facilitator's `/settle`. If it
   completes within the wait window (about 750 ms), the response carries the
   base64 `SettlementResponse` in the `X-PAYMENT-RESPONSE` header. A 2xx
   response means your payment was verified and the call is final; the
   absence of `X-PAYMENT-RESPONSE` only means settlement had not landed in
   time. There is no settlement-status endpoint; reconcile on-chain via your
   facilitator.

## Dynamic per-request pricing (when the deployment enables it)

With dynamic pricing on, the `402` quotes the exact price of **your**
request instead of one flat rate: the same classifier and size meter that
bill the ledger compute `entry-band list price × billable units` (a matrix
bills per started block of 25 elements, an isochrone per contour, an
optimisation a flat 10 units), converted to asset units at the
deployment's pinned FX rate and floored at a minimum charge. The `extra`
object itemises it:

```json
{
  "scheme": "exact", "network": "base",
  "maxAmountRequired": "5000",
  "description": "MapMap /matrix: 4 billable units, standard class, list 0.2p",
  "extra": {
    "class": "standard", "units": 4, "pencePerUnit": 0.05,
    "listPence": 0.2, "listAtomic": "3000", "minChargeApplied": true,
    "fxMicroUsdPerPence": 15000, "totalUsd": 0.005,
    "topup": { "resource": "https://api.mapmap.ai/v1/x402/topup", "bundlesUsd": ["0.10", "5", "20", "50"] }
  }
}
```

Rules an agent can rely on:

- **Quotes are a pure function of your request**: the same method, path
  and body always quote the same amount. Sign **exactly**
  `maxAmountRequired`; a mismatched value is refused before any
  facilitator round-trip.
- **`minChargeApplied: true`** means the settlement floor lifted the
  quote above list price (facilitator fees exceed sub-cent calls). The
  floor exists on the per-call rail only; prepaid credit bills true
  tiered list prices, so for standard-class volume the credit rail is
  strictly cheaper. `listAtomic` shows what the list price would have
  been.
- **One authorization pays for exactly one request.** The gateway claims
  the authorization's nonce before verifying: a byte-identical retry of a
  lost response is served again without settling twice; a different
  request on a used nonce is refused with "payment already used".
- **Only successful calls are charged.** A 4xx response releases the
  payment unused, the x402 analogue of the ledger's 4xx refund rule.
- **Large quotes can exceed your client's spend cap** (legacy `x402-fetch`
  defaults to 0.1 USDC). Raise your client's `maxValue` or use prepaid
  credit; quotes above the deployment's per-call cap are steered to
  credit in `instructions`.

### Paying on Base or Solana (multi-chain)

Where the deployment enables it, one 402 offers **more than one chain** in
`accepts[]`, an EVM entry (Base) and a Solana entry, and you pay on
whichever you hold USDC on. USDC is 6-decimals on both, so
`maxAmountRequired` is the same; only the rail differs:

- **EVM (`base`, `base-sepolia`):** `extra` carries the EIP-712 domain
  (`name`, `version`) you sign the `TransferWithAuthorization` against. The
  payment exposes a transparent `value` and `nonce`.
- **Solana (`solana`, `solana-devnet`):** `extra` carries `feePayer`; the
  facilitator sponsors gas and account rent, so you need only USDC (no
  SOL). The payment is a single pre-signed transaction; its amount is
  verified by the facilitator and it settles once via Solana's own
  blockhash + signature replay protection.

Stock clients handle both: an `x402-fetch` wired with an EVM signer picks
the EVM entry, one wired with a Solana signer picks the Solana entry.
Everything else (exact-quote binding, one-authorization-per-request,
4xx-never-charged, and the top-up bridge below) works identically on
either rail.

### Buying prepaid credit via x402 (`POST /v1/x402/topup`)

One settlement can fund thousands of calls: `POST
$BASE/v1/x402/topup?usd=5` (key-authed, consumes no quota) answers a
`402` quoting the bundle's exact amount; retry with `X-PAYMENT`, and on
settlement the credit lands on your email identity's ledger:
`{"status": "credited", "creditedMillipence": …, "balanceMillipence": …,
"tx": "0x…"}`. Bundles are listed in the 402's `extra` (the `$0.10`
micro-bundle clears stock client spend caps unconfigured). Credits are
idempotent by settlement transaction hash; a lost response never costs a
second payment; **poll `GET /v1/keys/self` before ever re-paying**. The
efficient loop: watch `credits_millipence`, top up on a low-water mark,
and never hit a 402 mid-workflow.

Enabling x402 on a self-hosted deployment requires all of `SN_X402_PAY_TO`,
`SN_X402_FACILITATOR_URL`, `SN_X402_NETWORK`, `SN_X402_ASSET` and
`SN_X402_PRICE_ATOMIC` (all-or-nothing), plus optional `SN_X402_MAX_AMOUNT`
(per-call rail only). Dynamic quotes and the top-up bridge switch on with
`SN_X402_MICROUSD_PER_PENCE` (µUSD per penny, e.g. `15000` = $1.50/£),
with optional `SN_X402_MIN_CHARGE_ATOMIC` (default `5000` = $0.005),
`SN_X402_TOPUP_BUNDLES_USD` (default `0.10,5,20,50`; `none` disables) and
`SN_TOPUP_PROVISIONAL_SPEND` (lets unverified identities spend credited
balance; payment as the verification signal).
`SN_X402_FACILITATOR_URL` points at any x402 facilitator exposing `/verify`
and `/settle`; the [x402 project](https://github.com/coinbase/x402) lists
public facilitators, including Coinbase's for the Base networks.

Agents that implement x402 pay inline; agents that don't keep using prepaid
credit. The response shape is the same either way.

## Next steps

- [Quickstart](/docs/quickstart): issue a key and make your first call
- [Conventions](/docs/conventions): base URL, auth, error envelope, 402-vs-429
- [Self-host](/docs/self-host): run your own gateway, with or without x402
- [Pricing](/pricing): per-call prices, plans and top-ups
