Skip to main content
Limitguard implements the x402 V2 micropayment protocol for AI agent pay-per-use access. No prepaid key or API key required — agents pay per call in USDC.

Overview

x402 extends HTTP with a payment layer:
  1. Agent makes request without payment
  2. Server returns HTTP 402 with payment requirements
  3. Agent constructs EIP-3009 TransferWithAuthorization signature
  4. Agent retries with X-PAYMENT header
  5. Server verifies signature, processes request

Supported Networks

Step 1: Discover Payment Requirements

Response (HTTP 402):
Each accepts entry is a standard x402 PaymentRequirements object, so official clients (npm x402-fetch) can consume it directly. Notes on individual fields: Every 402 also carries additive recovery hints under extensions.bazaar.info, so an agent without a wallet can still get started (full reference: docs/errors.md): The same body is mirrored base64-encoded in the PAYMENT-REQUIRED response header.

Step 2: Build EVM Payment (Base/EIP-3009)

Step 3: Make Paid Request

Response Headers

Replay Prevention

Each nonce can only be used once (Redis, 10-minute TTL — tied to the quote’s maxTimeoutSeconds). Reusing a nonce returns HTTP 402 with “Nonce already used (replay detected)”. Settled-transfer tx hashes (see below) use a longer 24-hour replay window instead.

Settled Transfer Fallback (EVM)

If your agent settles by sending USDC directly to the payTo address from the 402 response, retry the request with the transaction hash instead of a signature. The retry must also carry the X-Payment-Challenge token from the same 402 response you were quoted with (also in the settled-transfer accepts entry as extra.challengeId):
JSON form (base64 in X-PAYMENT or PAYMENT-SIGNATURE): {"chainId": "eip155:8453", "txHash": "0x…"}. Every 402 advertises this path as the accepts entry with scheme: "settled-transfer", whose extra carries the header name and these rules. The official x402 clients skip it — they only select schemes they have registered — so it costs them nothing. Limitguard verifies on-chain that the transaction succeeded, emitted a USDC Transfer of at least the endpoint price to payTo, and was mined within the last ~150 blocks (~5 minutes). Each hash is accepted once (24-hour replay window). If our RPC node cannot see the transaction yet (not found, or its view of the chain head is still behind the transaction’s block, which a load-balanced RPC often is right after mining), the server re-reads for a few seconds before answering. If it still cannot see it, you get 402 settlement_not_yet_visible with a Retry-After header. The hash has not been consumed and stays valid for the rest of the 150-block window, so send the same request again (same hash, same X-Payment-Challenge). Solana uses txSignature (see above). When a proof fails verification its replay claim is released so you can retry once the transaction confirms. That release is retried a bounded number of times (3 attempts); if it still fails, the claim is marked stuck rather than left indistinguishable from a real duplicate, and your next attempt with the same proof gets 402 settlement_claim_stuck (no automatic retry — the same response repeats for up to 24h) instead of a misleading duplicate_settlement. REST and MCP share this code path, so both transports give the same retry budget and the same message. Send the amount for a single call. A transfer is redeemed exactly once, so batching several calls’ USDC into one gas-efficient transfer buys one call and forfeits the surplus — the excess is credited to the ledger and reported in X-Payment-Amount, but it is not refunded and cannot be spent on a later request.

Why the challenge token (issue #205)

A settled-transfer proof is just a public tx hash, with no signature binding it to whoever presents it. Before the challenge existed, a third party watching the payTo address on-chain could submit your hash before you did and be served your result. The challenge token is delivered only in the HTTP 402 you received — never on-chain — so a proof is now redeemable only together with the token minted for that resource, and the token is checked before the replay guard and before any on-chain lookup: a submission without it is refused with 402 settlement_challenge_required, takes no claim on the hash, and leaves the hash redeemable by you. Rules:
  • The token is bound to the resource path it was quoted for. A token for /v1/risk/score does not redeem a transfer for /v1/entity/check.
  • It expires with the quote (the same ~5-minute window a transfer must confirm within). If it has expired, request a fresh quote (a bare request → 402) and retry with the new token: your hash is still unclaimed and still yours.
  • It is single-use: once a proof has verified with it, it is spent.
  • The same header applies to a Solana txSignature for an already-confirmed transaction.
  • There is no grace period after which an unrelated submission is served anyway.
What the token alone does not do: it is not proof of wallet ownership. It binds a redemption to a resource and a deadline, not to a wallet, so a party who requested their own 402 for the same resource ahead of time holds a valid token and can still race you with your hash. The optional signature below closes exactly that gap. Until you send it, submit your hash promptly after it confirms. The EIP-3009 (PAYMENT-SIGNATURE) path and the Solana partially-signed-transaction path are delivered to us privately, are not exposed to this race at all, and need neither the token nor the signature.

Proving you own the sending wallet (issue #365)

Send the sending wallet’s signature over the challenge and a copied hash becomes worthless to anyone else:
The field is optional today. Omitting it behaves exactly as before — your proof is accepted and the redemption is counted as unsigned. It is planned to become required (issue #367) once unsigned redemptions fall to zero, so adopt it before then. The signed message is exactly five UTF-8 lines joined by a newline, with no trailing newline:
Signature schemes:
  • EVM (Base) — EIP-191 personal_sign over that message, the ordinary personal_sign / eth_sign any wallet exposes. Send it as hex, with or without the 0x prefix. The server recovers the address and compares it to the from of the USDC Transfer log in your transaction’s receipt.
  • Solana — a raw ed25519 signature over the UTF-8 bytes of that message, base58 encoded. The server verifies it against the transfer authority read from the confirmed transaction.
The comparison is always against the sender read from chain. There is no field in which you can assert who you are, so a signature only ever helps the wallet that really sent the USDC. If the header is present and does not verify, the request is refused with 402 settlement_signature_invalid — and, importantly, your proof is not consumed: the deduplication claim is released, so the genuine payer can still redeem the same hash. A wrong signature therefore cannot be used to burn someone else’s transfer. All three redemption paths enforce this identically: the protected REST endpoints, the MCP tool calls, and POST /x402/verify.

Verifying a settled-transfer proof via /x402/verify

The same tx hash and challenge token can be redeemed against POST /x402/verify directly, without retrying the protected endpoint — useful for pre-validating a proof before spending it. Because /x402/verify has no request URL of its own to infer the resource from, you must say which resource the proof was quoted for, via the v2 envelope’s resource.url object or a plain resourcePath string:
Omitting the resource, or naming the wrong one, fails closed with settlement_challenge_required even for an otherwise-valid proof and token — see Why the challenge token. resource must be an object with a url field (this endpoint’s own convention, not the x402 spec’s accepts[].resource, which is a plain string); a bare string there is not recognized and falls back to requiring resourcePath instead. /x402/verify never consumes the challenge token — only redeeming the proof against the protected endpoint does, so calling /verify first does not burn your one-time token. The same rules apply to a Solana txSignature settled-transfer proof.

Quality Tiers with x402

Control cost via X-Response-Quality header:
The amount in X-PAYMENT must match or exceed the price for the selected tier: enhanced was retired on /v1/entity/check 2026-09-24 (#405): it took the identical fan-out as fresh, so X-Response-Quality: enhanced is now quoted, charged and served at the fresh amount above (850,000), not the old 1,500,000 — the 402 body’s info.note says so. The same retirement applies to /v1/risk/score, /v1/reputation/score and /v1/kyb/check. Not every tiered endpoint sells every tier. POST /v1/entity/deep-check sells fresh (0.75,750,000:PEP/RCA+Dutchinsolvencyregister)and‘enhanced‘(0.75, 750,000: PEP/RCA + Dutch insolvency register) and `enhanced` (1.50, 1,500,000, adds adverse media) always — it is unaffected by the #405 retirement above, since its enhanced tier genuinely reaches a source fresh does not — plus a third, separately opt-in credit tier ($2.25, 2,250,000, adds an Italian credit report) only while OPENAPI_CREDIT_API_KEY is configured — X-Response-Quality: credit is otherwise quoted, charged and served as fresh, and even once configured a non-Italian request on the credit tier is requoted down to the fresh amount before payment is ever required. It has no cache-only branch, so X-Response-Quality: cached is quoted, charged and served as fresh. cached is served only from cache. If nothing is cached for the entity, the response is 404 with errorCode: "not_cached" and a retry block naming X-Response-Quality: fresh and its price; no data source is called (see docs/errors.md) and the request is not charged. The unpaid 402 quote for a cached request probes the cache first: on a confirmed miss it is priced at the fresh tier and carries extensions.bazaar.info.cachedTierAvailable: false, so a caller is not sold the cheap tier for a call that would deliver nothing. When a quote carries that flag, retry with X-Response-Quality: fresh or drop the header; the amount in the quote is already the fresh price, so paying it as-is also works. If the probe cannot run (cache unconfigured or unreachable) the cached price is quoted as before; a miss on the paid call is still not charged, because settlement is gated on the route’s 2xx response. The probe’s answer is subject to the per-entity limit: an anonymous caller gets at most 5 quotes per entity per hour on these four endpoints (the same counter that meters the checks themselves, see docs/rate-limits.md), then 429, so cache state cannot be used to enumerate which entities have been checked. Fresh calls fill the cache, so “cached when available” means: after a fresh check of the same request (entity, country and the same identifiers — a different KVK number is a different cache entry). A fresh call itself never reads a cache: it always runs the live check. The refill TTL differs by endpoint: 1 hour for /v1/entity/check and /v1/kyb/check (the trust-response cache), 7 days by default for /v1/risk/score and /v1/reputation/score (the sanctions cache). Every endpoint warms the cache its own cached tier reads, so fresh then cached on one endpoint works on its own; a fresh /v1/entity/check or /v1/kyb/check additionally warms the sanctions cache that /v1/risk/score and /v1/reputation/score read, but not the reverse. Both cache keys are case-insensitive and ignore surrounding whitespace, so a fresh check of "Acme BV " is hit by a later cached call for "ACME BV".

Official x402 Client Payloads

The official x402 clients (@x402/fetch and x402-fetch on npm, x402 on PyPI) send a payload shaped differently from the flat one built in Step 2. Both of their shapes are accepted as-is — no adapter needed:
The Solana payload carries no amount and no payer: both are read out of the serialized transaction, then re-verified against the mint, destination ATA and fee payer before the payment is accepted, along with the transfer authority’s ed25519 signature over the transaction message — an unsigned or tampered transaction is rejected up front. You may optionally include amount and sender in payload; if present they are used for the price check, and the transaction is still verified. A partially signed transaction is the default the Python SDK’s SolanaWallet sends. It compiles the transfer with the address advertised as accepts[].extra.feePayer at account index 0 and leaves that signature slot empty, signing only as the transfer authority; the server co-signs as fee payer and broadcasts. A paying wallet therefore needs USDC and no SOL. Because no money has moved when the payload is presented, it is not a client-broadcast proof and is not gated on the X-Payment-Challenge token that a txSignature or txHash proof must be redeemed with. To pay the network fee yourself and send a txSignature instead, construct the wallet with broadcast=True. These payloads work against X-PAYMENT on any paid endpoint and against POST /x402/verify and POST /x402/settle.

V1 Backward Compatibility

The older X-PAYMENT header (V1 format with maxAmountRequired, from, to fields) is still accepted for backward compatibility. PAYMENT-SIGNATURE is the V2 spec header and is recommended for new integrations; it takes priority when both are sent.

Circuit Breaker Fallback

If the payment facilitator is unavailable (circuit breaker open), Limitguard falls back to a verified wallet cache. Wallets that have previously completed verified payments are cached. The response includes X-Payment-Fallback: true when this path is used. Routes settled through the Coinbase CDP facilitator (see CDP_FACILITATOR_ROUTES in Configuration) are the exception: a CDP outage fails closed with 402 server_error instead of falling back to the wallet cache, so a false “verified” never gets served on the same authorization the CDP outage might still land later.

Discovery

AI agents can discover Limitguard’s x402 payment requirements automatically:
Returns a Bazaar-compatible service listing with all endpoints, pricing, and accepted payment methods.