Skip to content

50,000 free calls a month, card-free. Get an API key →

Documentation menu
docs / autonomous-pay-loop · raw .md

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

  1. Discover the tools over the MCP server.
  2. Call a metered tool with the agent's own Bearer snk_ key.
  3. See a 402 when there is no way to pay for the next call.
  4. Pay with an X-PAYMENT retry (x402 crypto settlement).
  5. 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.

sh
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:

sh
curl -sS -X POST "$BASE/v1/keys" \
  -H "Content-Type: application/json" \
  -d '{"email":"agent@example.com","accept_tos":true,"label":"my-agent"}'
json
{
  "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:

sh
curl -sS -G "$BASE/route/v1/driving/-0.1276,51.5072;-1.2577,51.7520" \
  -H "Authorization: Bearer snk_…"
json
{ "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:

sh
curl -sS -X POST "$BASE/v1/x402/topup?usd=0.10" \
  -H "Authorization: Bearer snk_…"
json
{
  "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:

json
{
  "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>"
    }
  }
}
sh
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:

json
{
  "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:

sh
curl -sS "$BASE/v1/keys/self" -H "Authorization: Bearer snk_…"
json
{
  "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

sh
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