DOCS

How the toll works

x402 in plain words

Your API answers 402 Payment Required with a price tag. The agent's wallet signs a USDC payment, retries with that signature, a facilitator checks and settles it on Base, and the API answers.

The price tag travels in a PAYMENT-REQUIRED response header, the signed payment in a PAYMENT-SIGNATURE request header, and the receipt in a PAYMENT-RESPONSE header. All three are base64-encoded JSON (x402 protocol v2).

AgentTurnpikeFacilitatorBaseUpstream1. request /t/…2. 402 + PAYMENT-REQUIRED3. retry + PAYMENT-SIGNATURE4. POST /verify5. proxied request6. response (<500)7. POST /settle8. USDC transfer9. 200 + PAYMENT-RESPONSE

With the exact scheme on Base, the agent signs a USDC transfer authorization. Nothing moves until the facilitator settles it, and Turnpike only settles after your upstream answers.

For creators

  1. Go to Create, paste your upstream URL (HTTPS), pick API or MCP, set a price and your payout wallet.
  2. You get a toll link /t/<token>. Share it with agents, list it, put it in your docs.
  3. Each paid call sends USDC from the agent's wallet to your payout wallet on Base. Turnpike never holds funds.

Why the link is encrypted, not just signed

The token is your whole toll config sealed with AES-256-GCM (key derived from the server's TOLL_SECRET via HKDF-SHA256). If the upstream URL were readable in the link, an agent could call your upstream directly and skip the toll. GCM also authenticates the token: change one character and it no longer opens. Price and wallet can't be edited by whoever holds the link.

There is no database. If you lose the link, create a new one. If you need to change the price, create a new link and stop sharing the old one (old links keep working while the server's secret stays the same).

For agents

Use the official x402 client. wrapFetchWithPayment sees the 402, picks the matching payment option, signs it with your wallet and retries. Note the client's default spend cap is $1 per payment; raise it explicitly for pricier tolls.

// npm i @x402/fetch @x402/evm viem
import { wrapFetchWithPayment, x402Client, decodePaymentResponseHeader } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";

const signer = privateKeyToAccount(process.env.AGENT_PRIVATE_KEY as `0x${string}`);
const client = x402Client.fromConfig({
  schemes: [{ network: "eip155:*", client: new ExactEvmScheme(signer) }],
  spendControls: { maxAmountPerPayment: "$1" }, // default cap is $1 per call
});
const fetchWithPayment = wrapFetchWithPayment(fetch, client);

const res = await fetchWithPayment("https://turnpike.site/t/YOUR_TOKEN", { method: "GET" });
console.log(res.status, await res.text());

const receipt = res.headers.get("PAYMENT-RESPONSE");
if (receipt) console.log("paid, tx:", decodePaymentResponseHeader(receipt).transaction);

A runnable version lives in scripts/agent-demo.mts: AGENT_PRIVATE_KEY=0x… TOLL_URL=… npm run agent:demo.

MCP mode

Point a toll at a remote MCP server that speaks Streamable HTTP. Turnpike reads the JSON-RPC body: only tools/call needs payment. initialize, tools/list, ping, notifications and client responses are proxied free, so agents can discover tools before paying.

JSON-RPC batches are charged once per tools/call in the batch (count × price, one x402 payment, max 10 calls per batch). MCP tolls accept POST only; the optional GET SSE stream and DELETE session calls are not proxied.

// Discovery is free: initialize, tools/list, ping, notifications/*
const rpc = (method: string, params: object = {}, id = 1) => ({
  method: "POST",
  headers: { "content-type": "application/json", accept: "application/json, text/event-stream" },
  body: JSON.stringify({ jsonrpc: "2.0", id, method, params }),
});

const list = await fetch("https://turnpike.site/t/YOUR_TOKEN", rpc("tools/list"));
console.log(await list.text()); // 200, no payment

// tools/call is tolled: plain fetch gets 402, fetchWithPayment pays and retries
const call = await fetchWithPayment(
  "https://turnpike.site/t/YOUR_TOKEN",
  rpc("tools/call", { name: "your_tool", arguments: {} }, 2),
);
console.log(call.status, await call.text());

Building your own MCP server and want paid tools natively, without a proxy? The x402 project publishes @x402/mcp for that.

Security & limits

  • HTTPS only

    Upstreams must be https:// on port 443 or 8443. No user:pass@ in URLs.

  • No private networks

    Hostnames are resolved and blocked if any address is private, loopback, link-local (incl. 169.254.169.254), CGNAT, multicast or reserved, for IPv4, IPv6 and IPv4-mapped IPv6.

  • No DNS rebinding

    The connection is pinned to the IP that passed the check; TLS SNI and Host stay the original hostname.

  • Redirects re-checked

    At most 3 redirects, each hop validated again.

  • Timeouts & caps

    15 s per call end to end. Request bodies up to 1 MB, upstream responses up to 5 MB.

  • Settle only on success

    If the upstream errors (5xx, timeout, too large), payment is not settled: you only pay for calls that reach a working upstream.

  • Header hygiene

    Caller cookies, authorization, host, x-forwarded-* and payment headers are never forwarded upstream.

  • No toll loops

    A toll cannot point at this site, except the harmless /api/demo/* endpoints.

Testnet vs mainnet

By default a deployment runs on Base Sepolia (eip155:84532) with the free, keyless facilitator at https://x402.org/facilitator, which only supports testnet. Prices are in test USDC with no real value.

To get test USDC, use Circle's faucet at faucet.circle.com and pick Base Sepolia. The agent wallet needs test USDC, not ETH: the facilitator pays gas.

Base mainnet (eip155:8453) uses the Coinbase CDP facilitator, which needs free CDP API keys. The operator sets X402_NETWORK=base, CDP_API_KEY_ID and CDP_API_KEY_SECRET. Without keys the server refuses to create mainnet tolls. Each deployment serves one network; a toll made on the other network returns 409.

FAQ

+Do I need an account?

No. The link is the account. Nothing is stored server-side.

+Who holds the money?

Nobody in between. The agent signs a USDC transfer to your payout wallet and the facilitator executes it on Base.

+Can the agent see my upstream URL or API key?

No. Both are encrypted inside the token. The 402 response and the public manifest include only name, type, method, price, wallet and network.

+What if my API is down?

Upstream 5xx, timeouts and oversized responses are not settled. The agent gets a 502/504 and keeps its money.

+Do I need $PIKE to use Turnpike?

No. Payments are USDC on Base. $PIKE is a separate community token.

+Is the live feed real?

Yes, and only that. It is an in-memory list of settlements on the server instance you are talking to, each with its transaction hash. It resets on redeploy.

Self-host it

git clone <your-fork-url> turnpike && cd turnpike
npm install
cp .env.example .env.local
# required: 32+ random bytes, base64
openssl rand -base64 32   # paste into TOLL_SECRET
npm run dev               # http://localhost:3000

Keep TOLL_SECRET stable: changing it invalidates every toll link made with the old value.