API reference
The v1 HTTP API — authentication, every endpoint's contract, the payment object, and the Testnet/Mainnet boundary.
Base URL: your Tab deployment origin (hosted: https://app.runtab.xyz).
All responses are JSON with cache-control: no-store.
Authentication
Every request authenticates with one of three keys. Which one you use is determined by where the code runs:
Secret key — your server
Authorization: Bearer sk_test_…Created in Dashboard → API keys, shown once, stays on your server.
Full-access keys can mutate (manage); read-only keys can only read.
Rotating a key revokes the old one immediately — no grace window.
Publishable key — the buyer's browser
Authorization: Bearer pk_test_…Passed to <PayButton publishableKey=…> and safe to expose. It can only
act inside a checkout for your merchant account; settlement reporting
additionally requires the buyer's own identity token.
Agent key — the MCP proxy
TAB_AGENT_KEY=agent_sk_…Issued once at agent provisioning; lives only in the tab-mcp proxy's
environment. Authorizes the x402 payment flow for one agent, inside its cap.
Environments
Every key is bound to test (Testnet, Base Sepolia) or live (Mainnet,
Arbitrum One). A test key cannot read or move live data and vice versa;
livemode on every object always matches the key that created it.
The payment object
{
"id": "1d15cc1f-30a7-4f28-9d33-b93f4fd806aa",
"amount": "1.00",
"currency": "USD",
"env": "test",
"livemode": false,
"status": "settled",
"refCode": "TAB-2J7VNW4Q",
"intentUrl": "https://your-store.example/api/payment-intent",
"createdAt": "2026-07-22T09:00:00.000Z",
"payerType": "human",
"payerAddress": "0x9999…",
"reportedAt": "2026-07-22T09:00:08.000Z",
"reportedTransactionId": "0x7cc3…7136",
"settledAt": "2026-07-22T09:00:09.000Z",
"failureReason": null,
"token": { "address": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", "chainId": 84532 }
}Invariants the API upholds (and the SDK re-validates):
status∈pending | settled | failed.settledalways carriessettledAt,reportedAt,reportedTransactionId,payerAddress;pendingnever carriessettledAtorfailureReason.tokenis the settlement identity for the env: Testnet → Base Sepolia USDC (84532), Mainnet → Arbitrum One USDC (42161).- On Testnet,
reportedTransactionIdis the real Base Sepolia transaction hash, verified via RPC before settling.
Endpoints
Create a payment intent
POST /api/v1/payment-intents
Authorization: Bearer sk_test_… (manage permission)Body — exactly these two fields:
| Field | Type | Constraints |
|---|---|---|
amount | string | USD, up to 14 integer + 6 decimal digits, > 0 (e.g. "1.00") |
intentUrl | string | ≤ 2048 chars, HTTPS (localhost HTTP allowed in dev), no credentials or fragment |
201 → { intent: { amount, currency, mode, receiver, token }, intentToken }.
The intentToken is a signed, 5-minute commitment to the amount — the
checkout presents it back to Tab, so the browser can never change the price.
List payments
GET /api/v1/payments?limit=20
Authorization: Bearer sk_test_…| Query | Type | Constraints |
|---|---|---|
limit | integer | 1–100, default 20 |
env | string | optional; must equal the key's environment |
200 → { payments: [PaymentObject…] }, newest first, scoped to your
merchant + the key's environment.
Retrieve a payment
GET /api/v1/payments/{id}
Authorization: Bearer sk_test_…200 → { payment: PaymentObject }. The id must be a UUID; payments from
other tenants or environments answer 404.
Manage the webhook endpoint
One active endpoint per environment. manage permission for mutations.
GET /api/v1/webhook-endpoint → { endpoint: EndpointView | null }
POST /api/v1/webhook-endpoint { url } → 201 { endpoint, secret: "whsec_…" }
PATCH /api/v1/webhook-endpoint { url } → { endpoint } (resets verifiedAt)
DELETE /api/v1/webhook-endpoint → 204
POST /api/v1/webhook-endpoint/test → { delivery, endpoint }The whsec_ signing secret is returned only on creation. URLs must be
public HTTPS — loopback, private ranges, and raw IPs are rejected.
EndpointView: { id, url, env, health, listening, secretLast4, lastDeliveredAt, verifiedAt, createdAt, updatedAt }. See
Webhooks for events and verification.
Playground intent
GET /api/v1/checkout/playground-intent?pk=pk_test_…&amount=1.00Mints a capped, Testnet-only intent with just a publishable key (which is
public by design) — this is what powers try.runtab.xyz.
Amounts: 0.50 | 1.00 | 2.00 | 5.00. Live keys answer
403 PLAYGROUND_TESTNET_ONLY. CORS-open GET, so a static page can be a
complete storefront.
Checkout endpoints (called by the SDK)
You don't call these directly — <PayButton> does — but they define the
trust boundary:
GET /api/v1/checkout-context— merchant display info + client config for the publishable key's environment.POST /api/v1/payments— opens a pending payment from anintentToken(body is exactly{ intentToken }; replays are idempotent).PATCH /api/v1/payments/{id}— reports settlement. Body is exactly{ buyerDidToken, transactionId, tokenChanges }. On Testnet thetransactionIdmust be a real transaction hash; the server verifies the Base SepoliaTransferon-chain before settling (200), answers202withTEST_SETTLEMENT_PENDINGwhile unindexed, and422if the transfer doesn't match.GET /api/v1/checkout/test-balance?address=0x…— real on-chain USDC + gas balances ({ balance: { usdcAtomic, gasWei } }). Testnet keys only.POST /api/v1/checkout/test-funds— real faucet grant to the verified buyer. Testnet keys only; see Test funds.
Rate limits
429 responses carry a retry-after header (seconds). Current limits:
faucet grants 3/recipient/day, 25/merchant/day, 20/IP/hour; agent signing
and manual webhook sends have their own windows.
Idempotency
- Opening payments is idempotent by design: the payment id is the
intent's
jti, so replaying the sameintentTokenreturns the same payment (200instead of201). - Settlement reports are first-write-wins: identical evidence replays
cleanly; different evidence answers
409 PAYMENT_REPORT_CONFLICT. - Webhook deliveries carry a stable delivery UUID across retries —
idempotency key
${event.type}:${event.id}. - Mutation endpoints do not accept an
Idempotency-Keyheader today.
Errors
Stable machine-readable codes in a uniform envelope — the full catalog lives in the errors reference.
The Testnet/Mainnet boundary
- Testnet keys operate on Base Sepolia with real sandbox settlement — real faucet funding, real balance reads, RPC-verified transfers.
- Mainnet execution is gated until live money-mover verification lands;
reported live payments stay honestly
pending. - Test-rail endpoints refuse live keys outright
(
403 LIVE_MODE_NO_TEST_FUNDS).