Documentation menu
The autonomous pay loop
This page narrates one thing end to end: an AI agent paying for MapMap the way it calls it, with no human in the loop. It is the practical companion to Machine payments (x402) and the MCP server, and every request and response shape below is the real wire format, captured against the hosted gateway.
There is a runnable version in the repository at
examples/autonomous-pay-loop/ : a single zero-dependency Node script that runs
every step here live, except the payment itself, which it prints but never
sends.
The loop
- Discover the tools over the MCP server.
- Call a metered tool with the agent's own
Bearer snk_key. - See a 402 when there is no way to pay for the next call.
- Pay with an
X-PAYMENTretry (x402 crypto settlement). - Continue: poll the balance, then retry, no human involved.
What makes step 4 possible for a brand-new agent is one deployment setting:
SN_TOPUP_PROVISIONAL_SPEND=true. It lets an unverified provisional key
(the kind you get from a single unauthenticated call) top up and spend the
credit it buys. The payment itself stands in for email verification, so the loop
never has to stop for a person. See x402 for what that setting
does and does not enable.
export BASE=https://api.mapmap.ai
1. Discover
The hosted MCP server speaks streamable HTTP at https://mcp.mapmap.ai/mcp.
After the JSON-RPC initialize handshake, tools/list returns the catalogue an
agent chooses from: 30-plus tools spanning routing, geocoding, ADR compliance,
map styling and local geometry. See MCP server for client setup and
the full tool list.
An agent picks a tool and calls it. It does not need the MCP transport to pay:
the metered tools proxy the same gateway REST endpoints, and the payment loop
below is identical whether the call arrives over MCP or over plain HTTP. When an
MCP caller presents its own Authorization: Bearer snk_…, every metered call in
the tool bills that key, not the operator's shared key.
2. Get a key, then call
One unauthenticated call issues a working key immediately:
curl -sS -X POST "$BASE/v1/keys" \
-H "Content-Type: application/json" \
-d '{"email":"agent@example.com","accept_tos":true,"label":"my-agent"}'
{
"key": "snk_…",
"key_id": "…-uuid",
"state": "provisional",
"calls": 1000,
"expires_in_h": 72,
"verify": "link sent to email; clicking upgrades this key to the free tier",
"tos": "https://api.mapmap.ai/terms"
}
The key is provisional: 1,000 calls, works right now, no verification needed to
start. The agent calls a metered tool with it:
curl -sS -G "$BASE/route/v1/driving/-0.1276,51.5072;-1.2577,51.7520" \
-H "Authorization: Bearer snk_…"
{ "code": "Ok", "routes": [{ "distance": 96063.0, "duration": 4599.0, "…": "…" }] }
3. The 402
Eventually the agent needs to pay: its free allowance runs down, or it simply
decides to pre-fund credit before it does. Rather than wait to be refused
mid-workflow, it asks the top-up endpoint for a quote. With no X-PAYMENT
header, POST /v1/x402/topup answers the x402 wire 402 that names every rail it
may pay on:
curl -sS -X POST "$BASE/v1/x402/topup?usd=0.10" \
-H "Authorization: Bearer snk_…"
{
"x402Version": 1,
"error": "payment required: $0.10 prepaid credit bundle",
"accepts": [
{
"scheme": "exact",
"network": "base",
"maxAmountRequired": "100000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0x…",
"resource": "https://api.mapmap.ai/v1/x402/topup?usd=0.10",
"description": "MapMap prepaid credit top-up: $0.10 → ~6.666p of API credit …",
"mimeType": "application/json",
"maxTimeoutSeconds": 60,
"extra": {
"bundleUsd": "0.10",
"bundlesUsd": ["0.10", "5", "20", "50"],
"creditMillipenceEstimate": 6666,
"fxMicroUsdPerPence": 15000,
"name": "USD Coin",
"version": "2"
}
},
{ "scheme": "exact", "network": "solana", "maxAmountRequired": "100000", "extra": { "feePayer": "…" }, "…": "…" }
],
"instructions": "Retry with an X-PAYMENT header signing exactly maxAmountRequired. …"
}
Two rails, Base and Solana, the same USDC atomic amount on both, so the agent
pays on whichever chain it holds USDC. The EVM extra carries the EIP-712
domain (name, version) the payment is signed against; the Solana extra
carries the feePayer the facilitator sponsors. $0.10 is the micro-bundle
that clears stock client spend caps; the other bundles buy proportionally more
credit per settlement.
Why buy prepaid credit rather than pay per call? Standard-class calls cost a fraction of a penny, below what a per-call facilitator fee would be, so one settlement funds thousands of calls at true tiered list prices. Truck and ADR calls can also be paid inline per call; the mechanics are identical and covered in x402.
4. Pay
The agent signs an x402 PaymentPayload for the rail it chose and retries with
it base64-encoded in the X-PAYMENT header. On the EVM rail:
{
"x402Version": 1,
"scheme": "exact",
"network": "base",
"payload": {
"signature": "0x…",
"authorization": {
"from": "0x<funded wallet>",
"to": "0x<payTo from the 402>",
"value": "100000",
"validAfter": "0",
"validBefore": "<unix now + 60>",
"nonce": "0x<32-byte random>"
}
}
}
export PAYMENT=$(printf '%s' "$PAYLOAD_JSON" | base64)
curl -sS -X POST "$BASE/v1/x402/topup?usd=0.10" \
-H "Authorization: Bearer snk_…" \
-H "X-PAYMENT: $PAYMENT"
Sign exactly maxAmountRequired; a mismatched value is refused before any
facilitator round-trip. Client libraries in the
x402 project build and sign the payload for
you, an x402-fetch wrapper wired with an EVM signer picks the Base entry, one
wired with a Solana signer picks the Solana entry.
The gateway claims the authorization's nonce, verifies the payment with its
facilitator, and awaits settlement. Credit is minted only when settlement
returns success, never on verify alone. On success it credits the estimated
amount to the identity ledger and answers:
{
"status": "credited",
"creditedMillipence": 6666,
"balanceMillipence": 6666,
"tx": "0x…",
"network": "base",
"fxMicroUsdPerPence": 15000
}
Credit is idempotent by the payment's nonce and settlement transaction, so a
lost response never costs a second payment: poll GET /v1/keys/self before
ever re-paying. Because SN_TOPUP_PROVISIONAL_SPEND is on, this credit is
immediately spendable even though the key was never email-verified.
5. Continue
The agent polls its balance, a quota-free endpoint it can hit freely, and, once credit has landed, retries the original request unchanged:
curl -sS "$BASE/v1/keys/self" -H "Authorization: Bearer snk_…"
{
"key_id": "…-uuid",
"state": "provisional",
"monthly_quota": 1000,
"used_this_month": 3,
"remaining": 997,
"credits_pence": 6,
"credits_millipence": 6666
}
Each metered call now draws its class price from the ledger: standard routing
and data from 0.05p, truck/ADR premium from 1p, with no further 402 until the
balance runs low. The efficient loop watches credits_millipence, tops up on a
low-water mark, and never hits a 402 mid-workflow. Only successful calls are
charged; a 4xx releases the debit. See Pricing for the schedules.
That is the whole loop: discover, call, pay, continue, with no human at any step.
Run it
cd examples/autonomous-pay-loop
node autonomous-pay-loop.mjs
The script runs discovery, key issuance, the metered call, the 402 and the balance poll live against the hosted gateway. The payment step is printed as an exact request shape and deliberately not sent, so the example moves no funds. To settle a real top-up, sign the payload with a funded wallet using an x402 client.
Next steps
- Machine payments (x402): the full 402 contract, both rails, the top-up bridge
- MCP server: connect any MCP client and the per-caller billing seam
- Pricing: per-call prices, classes, and top-up bundles
- Quickstart: issue a key and make your first call