Tab Docs

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

  • statuspending | settled | failed. settled always carries settledAt, reportedAt, reportedTransactionId, payerAddress; pending never carries settledAt or failureReason.
  • token is the settlement identity for the env: Testnet → Base Sepolia USDC (84532), Mainnet → Arbitrum One USDC (42161).
  • On Testnet, reportedTransactionId is 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:

FieldTypeConstraints
amountstringUSD, up to 14 integer + 6 decimal digits, > 0 (e.g. "1.00")
intentUrlstring≤ 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_…
QueryTypeConstraints
limitinteger1–100, default 20
envstringoptional; 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.00

Mints 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 an intentToken (body is exactly { intentToken }; replays are idempotent).
  • PATCH /api/v1/payments/{id} — reports settlement. Body is exactly { buyerDidToken, transactionId, tokenChanges }. On Testnet the transactionId must be a real transaction hash; the server verifies the Base Sepolia Transfer on-chain before settling (200), answers 202 with TEST_SETTLEMENT_PENDING while unindexed, and 422 if 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 same intentToken returns the same payment (200 instead of 201).
  • 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-Key header 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).

On this page