Overview
AtlanticβsPOST /atlantic-query endpoint speaks the canonical
x402 v2 HTTP-payments protocol.
Header names match the public spec exactly
(PAYMENT-REQUIRED, PAYMENT-SIGNATURE, PAYMENT-RESPONSE), so any
standard x402 client library β x402-fetch, @coinbase/x402-axios, or
your own β works against Atlantic without modification.
Use x402 when you want pay-per-call access to Atlantic from an EVM
wallet, either:
- as a fallback when an API-key project runs out of prepaid credits, or
- as a fully anonymous wallet-only client with no account on Herodotus Cloud.
atlantic-api AI skill.
Two flows at a glance
| API-key flow | Anonymous flow | |
|---|---|---|
| When it triggers | API key + insufficient project credits | No API key on the request |
| Identity | Project + API key (existing) | EVM wallet recovered from EIP-3009 signature |
| Credit residue | Settled amount β project credit balance, 2-year TTL, drawn down by future queries | None β pay-once, use-once |
| Refunds / leftover | Yes, leftover credits remain on project | No |
dedupId / bucketId | Supported | Rejected (WALLET_FLOW_DEDUP_ID_NOT_SUPPORTED, WALLET_FLOW_BUCKET_NOT_SUPPORTED) |
| Resumable after disconnect | Yes, via extra.atlantic_query_id | Yes, via extra.atlantic_query_id |
paymentRequired.error field | "insufficient_credits" | "payment_required" |
Wire-level walkthrough
Step 1 β Submit the query normally
200 with the query body. Do not preemptively send a
PAYMENT-SIGNATURE header β the server only invokes x402 after
detecting INSUFFICIENT_CREDITS, and a payment header sent with
credits available is wasted.
Step 2 β Receive the 402
If credits are insufficient (or no API key was provided and anonymous payments are enabled), the server responds:PAYMENT-REQUIRED HTTP header carries the same payload, base64-
encoded, for compatibility with standard x402 clients. Either source
is canonical; the body form is shown here because itβs easier to
inspect.
extra field reference:
| Field | Where | Purpose |
|---|---|---|
name | both flows | EIP-712 domain name of the asset contract (e.g. "USDC"). Use this β do not call eip712Domain() on the contract. |
version | both flows | EIP-712 domain version of the asset contract (e.g. "2"). |
creditAmount | both flows | Number of credits this payment will buy. Usually equals amount in cents. |
challengeId | both flows | Server-issued ULID. Single-use β the server invalidates it after a successful settle. |
atlantic_query_id | both flows (when set) | Pre-issued query ID. If you echo this in the signed payment, retrying the request resumes the same query rather than creating a new one. |
projectId, clientId | API-key flow only | Identity binding β the settle endpoint rejects payments whose extra does not match the calling project. |
Step 3 β Sign EIP-3009 transferWithAuthorization
Pick one entry from accepts[] (typically the first; production
deployments may offer multiple networks). Then sign EIP-712 typed-data
for transferWithAuthorization on the chosen asset contract:
value units of the asset on the
specified network. For base-sepolia USDC, thatβs
Circleβs faucet.
Step 4 β Retry with PAYMENT-SIGNATURE
Build a v2 PaymentPayload and base64-encode it as the
PAYMENT-SIGNATURE header. Resubmit the same body:
Step 5 β Read PAYMENT-RESPONSE
On success, the response is 200 with the usual query body and a
settlement receipt in the PAYMENT-RESPONSE header:
PAYMENT-RESPONSE:
alreadyProcessed: true means the facilitator has already settled this
exact (challengeId, signature) pair before β the query is allowed
through but no new credit is added. Do not retry the payment. This
typically happens when the client times out after the server has
settled but before the response is read.
Replay, dedup, and idempotency
- Challenges are single-use. The server stores each issued
challenge with its
challengeIdand consumes it on a successful settle. Reusing the samePAYMENT-SIGNATUREafter success returnsX402_SETTLEMENT_FAILED. For each new query, fetch a fresh 402. - Settlements are idempotent on
providerPaymentId. Submitting the same signed payment twice in a tight loop will not double-charge β the second attempt returns the same200withalreadyProcessed: true. - Resume an in-flight query. If you submit a query, get the 402,
sign, but lose the connection before retrying, you can recover the
pre-issued
extra.atlantic_query_idfrom your decoded challenge, embed it in the signedaccepted.extra, and retry. The server routes the payment to the original query rather than minting a new one.
Error taxonomy
| Code (agent-visible) | Status | When it fires | What to do |
|---|---|---|---|
X402_NOT_ENABLED | 503 | x402 disabled in this Atlantic deployment | Back off; do not retry |
MISSING_API_KEY | 400 | Anonymous flow disabled and no API key supplied | Authenticate with herodotus-auth and use the API-key flow |
X402_CHALLENGE_FAILED | 502 | Upstream challenge build failed | Transient β retry with backoff |
X402_SETTLEMENT_FAILED | 402 | Verify or settle rejected (bad signature, replay, expired authorization, insufficient wallet balance, mismatched extra.projectId/clientId) | Fetch a fresh 402 and try again with a new nonce; do not reuse the prior signature |
X402_SETTLEMENT_RESPONSE_INVALID | 502 | Settlement succeeded on-chain but the response failed schema validation | Treat as ambiguous β call GET /atlantic-query/:id before paying again to avoid double-charging |
X402_SERVICE_AUTH_NOT_CONFIGURED | 500 | Server misconfiguration with the x402 facilitator | Surface to the user; do not retry |
WALLET_FLOW_DEDUP_ID_NOT_SUPPORTED | 400 | Anonymous flow + dedupId in body | Drop dedupId and resubmit |
WALLET_FLOW_BUCKET_NOT_SUPPORTED | 400 | Anonymous flow + bucketId in body | Drop bucketId and resubmit |
WALLET_FLOW_NOT_RETRIABLE | 400 | This anonymous query cannot be retried | Submit a fresh query |
curl-only recipe
For sanity-checking your environment before integrating a signer:AI agent integration
If youβre integrating Atlantic from an AI assistant or autonomous agent, load theatlantic-api AI skill. It
includes this entire flow as a copy-pasteable playbook with anti-
hallucination guardrails and a viem reference snippet that handles
both flows in one parameterized function.
In Claude Code:

