Payment Flow
The server implements a two-step HTTP payment flow based on the x402 protocol (V2). The exact flow differs slightly depending on whether a table uses per-row, fixed, or metadata pricing. This document describes the flow from the server’s side.
Clients calling x402-gated APIs must also implement x402 logic to react to a 402 Payment Required response. Out-of-the-box client implementations are available in the official x402-foundation GitHub repository.
Pricing Flow

Step 1: Estimation
When a client sends a query to GET /api/query?query=… (or GET /api/table/{name} for metadata) without a Payment-Signature header:
- The server parses and validates the SQL.
- For per-row tables: it wraps the query in
SELECT COUNT(*) FROM (...)to estimate the row count, then computes the applicable pricing tiers. For fixed-price and metadata-price tables: this step is skipped (the price doesn’t depend on row count). - It returns HTTP 402 Payment Required with:
- A JSON body containing the error message,
resourceinfo, and all applicable payment options underaccepts. - A
Payment-Requiredheader carrying the same payload, base64-encoded (so SDKs that read headers, e.g.x402-fetch, can pick it up directly).
- A JSON body containing the error message,
{
"x402Version": 2,
"error": "No crypto payment found. Implement x402 protocol...",
"resource": {
"url": "http://server:4021/api/query?query=SELECT%20*%20FROM%20uniswap_v3_pool_swap%20LIMIT%202",
"description": "Uniswap v3 swaps - 2 rows",
"mimeType": "application/vnd.apache.arrow.stream"
},
"accepts": [
{
"scheme": "exact",
"network": "eip155:84532",
"amount": "4000",
"payTo": "0xE7a820f9E05e4a456A7567B79e433cc64A058Ae7",
"maxTimeoutSeconds": 300,
"asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"extra": { "name": "USDC", "version": "2" }
}
]
}
Each entry in accepts represents a valid payment option. If multiple pricing tiers apply (different tokens, networks, or bulk tiers), multiple options are returned and the client picks one.
Step 2: Execution and Settlement
The client resubmits the same request with a Payment-Signature header (base64-encoded PaymentPayload JSON). The server’s behaviour depends on the pricing model:
Per-Row Tables
- Decode and deserialize the payment payload into a V2
PaymentPayload. - Execute the actual query to get the real row count.
- Match the payload’s
acceptedfield against the generated payment requirements. - Send a verify request to the facilitator to confirm the payment is valid and funded.
- If verified, send a settle request to execute the on-chain transfer.
- Return the query results as Arrow IPC with HTTP 200.
Fixed-Price Tables
- Decode and deserialize the payment payload into a V2
PaymentPayload. - Match the payload’s
acceptedfield against the generated payment requirements (row count is irrelevant). - Send a verify request to the facilitator.
- Only after verification succeeds, execute the actual query.
- Send a settle request to execute the on-chain transfer.
- Return the query results as Arrow IPC with HTTP 200.
This ordering is intentional: it prevents an attacker from triggering expensive queries with bogus payment headers, since the database is never touched until the facilitator has approved the payment.
Metadata-Priced Tables (GET /api/table/{name})
The same verify-before-act flow as fixed-price tables. The “work” being protected is just serializing the table’s TablePaymentOffers to JSON, but the structure is identical: match → verify → return data → settle.
Error Cases
| Scenario | Response |
|---|---|
| Table not found | 400 Bad Request (/api/query) or 404 Not Found (/api/table/{name}) |
| Invalid SQL | 400 Bad Request |
| Malformed payment header | 400 Bad Request |
| No matching payment offer | 500 Internal Server Error |
| Payment verification fails | 402 Payment Required (with reason and updated options) |
| Payment settlement fails | 402 Payment Required (with reason and updated options) |
| Facilitator unreachable | 500 Internal Server Error |