AGENTS
x402: HTTP-native pay-per-call
Charge per HTTP request at a 1¢ minimum, settle on-chain, no Stripe fixed fee. Tab is a turnkey backend for x402, the open HTTP-payment standard. One API call mints a spec-compliant HTTP 402 Payment Required response that points to a real Tab checkout. Settlement is multi-chain, you keep the same dashboard, receipts, refunds, and webhooks as any other Tab payment.
Why x402 vs Stripe
| Stripe | Tab x402 | |
|---|---|---|
| Minimum chargeable amount | $0.50 (after $0.30 fixed fee) | $0.01 |
| Fee on a $0.01 charge | Not chargeable | $0.0001 (1%) |
| Per-token / per-byte agent billing | Not viable | Native |
| Settlement | T+2 to T+7 to bank | ~3 sec on-chain |
| Caller account / signup | Required upstream | Wallet signs the 402. No signup |
The flow in three steps
- Caller hits your agent's URL without an
X-PAYMENTheader. - Your agent calls
POST /api/x402/chargeon Tab with the price + resource URL. Tab returns a spec-compliant 402 JSON body. Your agent forwards it verbatim with status 402. - Caller pays via the Tab checkout URL (or via the standard EIP-3009 path if they're a Coinbase-facilitator client). Caller retries with the
orderId. Your agent verifies viaGET /api/x402/verify?orderId=…and, onpaid: true, returns the resource.
Authentication
Every call to POST /api/x402/charge requires an API key with write scope. Grab one (or create a key) at /dashboard/keys. Live keys start with sk_live_, test keys with sk_test_. The key must be sent in the Authorization: Bearer … header on every /chargerequest. Without it you'll get a 401 with no x402 challenge.
GET /api/x402/verify?orderId=… is public, no Bearer header needed. The order id is a 24-char nanoid so the id itself is the secret. Returns { paid: true | false } plus order metadata so your agent can decide whether to release the resource.
Minting a 402
POST https://thetab.bar/api/x402/charge
Authorization: Bearer sk_live_…
Content-Type: application/json
{
"amount": "0.50",
"chain": "base",
"resource": "https://your-agent.example.com/research",
"description": "Run a 500-word research summary on the supplied topic"
}Response (HTTP 402, forward this verbatim to the caller):
{
"x402Version": 1,
"error": "X-PAYMENT header required",
"accepts": [
{
"scheme": "exact",
"network": "base",
"maxAmountRequired": "500000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0x…recipient",
"resource": "https://your-agent.example.com/research",
"description": "Run a 500-word research summary on the supplied topic",
"mimeType": "application/json",
"outputSchema": null,
"maxTimeoutSeconds": 60,
"extra": {
"name": "USDC",
"version": "2",
"tab": {
"checkoutUrl": "https://thetab.bar/pay/ord_8H3kZ…",
"orderId": "ord_8H3kZ…",
"verifyUrl": "https://thetab.bar/api/x402/verify?orderId=ord_8H3kZ…",
"chain": "base",
"currency": "USDC"
}
}
}
]
}The extra.tab block is a Tab-only shortcut. Standard x402 clients ignore it and use the asset + payTo + maxAmountRequired fields to build their own EIP-3009 transaction. Tab-aware clients can skip the signing dance entirely and open checkoutUrl in a browser.
Verifying a payment
On the caller's retry, hit the verify endpoint with the orderId from the original 402 response. (How does the caller know the orderId? The standard x402 client extracts it from accepts[0].extra.tab.orderId and includes it as a query param on retry; Coinbase-facilitator clients use the X-PAYMENT-RESPONSE header convention. Both paths converge here.)
GET https://thetab.bar/api/x402/verify?orderId=ord_8H3kZ…
// when settled:
{
"paid": true,
"orderId": "ord_8H3kZ…",
"txHash": "0x…",
"chain": "base",
"amount": "0.50",
"currency": "USDC",
"payerAddress": "0x…"
}
// still waiting:
{ "paid": false, "status": "awaiting_payment", "expiresAt": 17040… }Or skip polling and use a Tab webhook subscription, every x402 order fires order.created on issue and order.completed on settlement, identical to any other Tab payment.
Accept multiple chains in one 402
The x402 spec lets you offer several payment options in a single accepts[] array; the client picks whichever it can satisfy. Useful when your callers might hold balance on different chains. To mint a multi-chain 402, send an array under chains instead of a single chain:
POST /api/x402/charge
{
"amount": "0.50",
"chains": ["base", "bsc", "solana"],
"resource": "https://your-agent.example.com/research",
"description": "AI research summary"
}
→ accepts: [
{ network: "base", asset: "<base USDC>", payTo: "0x…", … },
{ network: "bsc", asset: "<bsc USDC>", payTo: "0x…", … },
{ network: "solana", asset: "<solana USDC>", payTo: "<base58>", … }
]Tab tracks a single orderId across all three; the first chain to settle wins, the others auto-cancel. Most facilitator clients today are single-chain so this is forward-looking, but the wire format is in place.
End-to-end in 20 lines
import express from "express";
const app = express();
const TAB = "https://thetab.bar";
const KEY = process.env.TAB_API_KEY!;
app.get("/research", async (req, res) => {
const orderId = req.query.orderId as string | undefined;
if (!orderId) {
// No payment yet, mint a 402. The client retries with ?orderId=…
// after paying — the orderId is in our 402 body under
// accepts[0].extra.tab.orderId, standard clients echo it back.
const r = await fetch(`${TAB}/api/x402/charge`, {
method: "POST",
headers: { authorization: `Bearer ${KEY}`, "content-type": "application/json" },
body: JSON.stringify({
amount: "0.50",
chain: "base",
resource: `${req.protocol}://${req.host}/research`,
description: "AI-generated research summary",
}),
});
res.status(402).json(await r.json());
return;
}
// Caller is retrying, verify the order settled.
const v = await fetch(`${TAB}/api/x402/verify?orderId=${orderId}`).then((r) => r.json());
if (!v.paid) return res.status(402).json({ error: "still waiting", status: v.status });
// Paid, return the resource.
res.json({ summary: await runResearch(req.query.topic as string) });
});
app.listen(3000);What you don't have to build
- EIP-3009 / EIP-712 signing flow. Tab's checkout handles it.
- Multi-chain routing. Tab handles USDC on Base / BSC / Ink / Celo / Solana. One canonical stable everywhere.
- Refunds. Call
/api/orders/[id]/refundif your agent fails to deliver and the caller's funds go straight back on-chain. - Dashboards, receipts, exports. Every x402 charge shows up at
/dashboard/ordersthe same as any other Tab payment.
Cost
Flat 1% protocol feeon settled charges, same as any Tab payment. No per-call surcharge, no minimum amount, no fixed fee. A $0.01 x402 charge nets the recipient $0.0099. That's how per-token agent billing becomes viable: Stripe requires $0.50+ charges to clear its $0.30 fixed fee; Tab makes 1¢ profitable.
FAQ
Who pays the gas?
The caller's wallet does. They sign the EIP-3009 transferWithAuthorization against USDC and submit it. If the caller uses the Tab-checkout shortcut (via extra.tab.checkoutUrl), Tab's own relayer covers gas and the caller pays only the stablecoin amount, no native asset needed.
Can I issue refunds on x402 charges?
Yes, identical to any other Tab order. POST /api/orders/[id]/refundpushes the USDC back to the payer's address on-chain. Tab still charges its 1% on each leg unless you flag the order as a refund explicitly.
What stablecoins does Tab settle?
USDC on every supported chain (Base, BSC, Ink, Celo, Solana). The endpoint always quotes in the chain's canonical USDC contract; we don't support non-Tab stables today.
Is my x402 endpoint discoverable to other agents?
If you've registered your handle on ERC-8004 via the on-chain registry, Tab's manifest endpoint advertises your x402 charge URL under x402Endpoint. Other agents resolve your manifest and know where to send 402-triggering requests.
How long does an x402 order stay open?
15 minutes from creation. The x402 spec defaults maxTimeoutSecondsto 60, which is unrealistic given on-chain confirmation times; Tab's 15-min window covers slow L2s and bridges. Override per charge with the maxTimeoutSeconds field if you need a tighter SLA.