Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Response Formats

Arrow IPC (Success)

Successful queries to GET /api/query return data in Apache Arrow IPC streaming format.

Content-Type: application/vnd.apache.arrow.stream

Arrow IPC is a binary columnar format that is significantly more efficient than JSON for structured data. It preserves type information and supports zero-copy reads.

Reading in TypeScript

import * as arrow from 'apache-arrow';

const response = await fetch("http://localhost:4021/api/query?query=" + encodeURIComponent(sql));
const arrayBuffer = await response.arrayBuffer();
const table = arrow.tableFromIPC(arrayBuffer);

for (const row of table) {
  console.log(row.toJSON());
}

Reading in Python

import pyarrow as pa

reader = pa.ipc.open_stream(response_bytes)
table = reader.read_all()
print(table.to_pandas())

Reading in Rust

#![allow(unused)]
fn main() {
use arrow::ipc::reader::StreamReader;
use std::io::Cursor;

let reader = StreamReader::try_new(Cursor::new(bytes), None)?;
let batches: Vec<RecordBatch> = reader.collect::<Result<_, _>>()?;
}

Payment Required (402)

When payment is needed, the response is JSON following the x402 V2 specification, with the same payload duplicated as a base64-encoded Payment-Required HTTP header so SDKs that read headers can pick it up directly.

The x402 foundation maintains official client implementations that handle the full payment flow automatically — including TypeScript (x402-fetch, x402-axios) and Python clients. Using one of these is the recommended way to interact with any x402-enabled server without writing payment logic by hand.

For example, with x402-fetch:

Content-Type: application/json
Payment-Required: <base64>
{
  "x402Version": 2,
  "error": "No crypto payment found...",
  "resource": {
    "url": "http://localhost:4021/api/query?query=SELECT%20*%20FROM%20uniswap_v3_pool_swap%20LIMIT%202",
    "description": "Uniswap v3 pool 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" }
    }
  ]
}

Top-level fields

FieldDescription
x402VersionProtocol version (2)
errorHuman-readable explanation of why payment is required
resource.urlURL of the resource being paid for
resource.descriptionHuman-readable description (often includes the row count for per-row pricing)
resource.mimeTypeContent type of the successful response (application/vnd.apache.arrow.stream for queries, application/json for metadata)
acceptsList of PaymentRequirements. The client picks one and signs it

PaymentRequirements fields

FieldDescription
schemePayment scheme. Always "exact" today
networkEIP-155 chain identifier (e.g. "eip155:84532" for Base Sepolia)
amountTotal price in the token’s smallest unit (USDC has 6 decimals — "4000" = $0.004)
payToRecipient wallet address
maxTimeoutSecondsHow long this offer is valid for
assetERC-20 token contract address
extraToken metadata (name, version) used by the client to construct the EIP-712 domain when signing

For per-row pricing, multiple accepts entries may be returned (e.g., a default tier plus a bulk-discount tier whose min_items is satisfied by the estimated row count). The client is free to pay any of them.

Error Responses

Errors are returned as plain text:

Content-Type: text/plain
StatusCause
400Invalid SQL, unsupported table, or malformed payment header
404Table not found (only on GET /api/table/{name})
500Database errors, facilitator communication failures, serialization errors
503Dashboard configured but no index.html built yet (only on GET / and /<slug>/)