API Endpoints
The server exposes the following HTTP endpoints:
| Method | Path | Purpose |
|---|---|---|
GET | / | Dashboard landing page (HTML), only mounted when dashboards: is configured |
GET | /{slug}/... | Static files for an Evidence dashboard (one route per dashboards: entry) |
GET | /api/ | JSON discovery document — server info, endpoints, table summaries |
GET | /api/query?query=… | Submit a SQL query (paywalled per the table’s price tags) |
GET | /api/table/{name} | Full schema and pricing for a single table; optionally paywalled via MetadataPrice |
All paid endpoints follow the x402 V2 protocol. The Payment-Signature header carries the base64-encoded PaymentPayload; the server replies with a Payment-Required header on 402 alongside a JSON body.
GET /
When dashboards are configured, the root path serves an HTML landing page listing every enabled dashboard with its title, description, and tags. The page is generated by tiders-x402-server dashboard … into <dashboards_root>/index.html and re-served verbatim. If dashboards_root/index.html is missing, the server returns 503 with a hint to run the dashboard subcommand.
When no dashboards are configured, / is unmounted and falls through to a 404 from the (empty) dashboard sub-router.
GET /api/
Returns a JSON discovery document — the canonical machine-readable description of the server.
Request
curl http://localhost:4021/api/
Response (200 OK, application/json)
{
"server": {
"url": "http://localhost:4021/",
"version": "0.3.0",
"facilitator_url": "https://facilitator.x402.rs/"
},
"endpoints": {
"GET /api/": { "description": "This document." },
"GET /api/table/{name}": { "description": "...", "response_format": "application/json" },
"GET /api/query": { "description": "...", "response_format": "application/vnd.apache.arrow.stream" }
},
"tables": [
{
"name": "uniswap_v3_pool_swap",
"description": "Uniswap V3 pool swaps",
"requires_payment": true,
"details": "/api/table/uniswap_v3_pool_swap",
"pricing": [
{
"model": "PerRow",
"amount_per_item": "2000",
"token_address": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"chain": "84532",
"pay_to": "0xE7a820f9E05e4a456A7567B79e433cc64A058Ae7"
}
]
}
]
}
MetadataPrice tags are intentionally omitted from the pricing array here — discovering them is the job of GET /api/table/{name}.
GET /api/query
Executes a SQL query against the database. The SQL is passed via the query URL parameter (URL-encoded).
Queries must conform to a restricted SQL dialect (“Simplified SQL”) whose AST permits only SELECT statements against a single table, with a limited set of WHERE, ORDER BY, and LIMIT expressions. JOINs, subqueries, GROUP BY, CTEs, window functions, and aggregates are rejected. See the SQL Parser page for the full grammar.
Request
curl --get http://localhost:4021/api/query \
--data-urlencode "query=SELECT * FROM my_table WHERE col1 = 'value' LIMIT 10"
Query parameters
| Name | Description |
|---|---|
query | The SQL statement to execute, URL-encoded. |
200 OK — Arrow IPC
Binary Arrow IPC stream:
Content-Type: application/vnd.apache.arrow.stream
Parse with any Arrow library (PyArrow, apache-arrow in JS, arrow crate in Rust). See Response Formats for examples.
402 Payment Required
Returned when the table requires payment and no valid Payment-Signature header is present, or when verification/settlement fails.
Content-Type: application/json
Payment-Required: <base64-encoded JSON payload>
{
"x402Version": 2,
"error": "No crypto payment found. Implement x402 protocol...",
"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" }
}
]
}
Response Errors
400 Bad Request — missing/invalid query parameter, invalid SQL, unsupported table, or undecodable payment header. Plain text body.
500 Internal Server Error — database, serialization, or facilitator error. Plain text body.
Headers
| Header | Direction | Description |
|---|---|---|
Payment-Required | Response | Base64-encoded payment requirements on 402 |
Payment-Signature | Request | Base64-encoded PaymentPayload (Step 2 of the x402 flow) |
Content-Type: application/vnd.apache.arrow.stream | Response | Arrow IPC data on 200 |
GET /api/table/{name}
Returns full schema and payment-offer details for a specific table as JSON.
If the table has a MetadataPrice price tag, this endpoint requires payment via the x402 protocol before returning data — otherwise the metadata is returned freely.
Request
curl http://localhost:4021/api/table/my_table
Response (200 OK)
The serialized TablePaymentOffers:
{
"table_name": "my_table",
"price_tags": [
{
"pay_to": "0xE7a820f9E05e4a456A7567B79e433cc64A058Ae7",
"pricing": { "model": "PerRow", "amount_per_item": "2000", "min_items": null, "max_items": null, "min_total_amount": null },
"token": { "chain": "84532", "address": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", "decimals": 6, "transfer_method": "..." },
"is_default": true
}
],
"requires_payment": true,
"description": "My dataset",
"schema": { "fields": [ ... ] }
}
Response (402 Payment Required) — when the table has a MetadataPrice tag and no valid Payment-Signature is provided. Same body shape as the 402 from GET /api/query, but with mimeType: "application/json" and the resource.url pointing at the metadata endpoint.
Response (404 Not Found)
{ "error": "Table 'unknown_table' not found" }
Response (400 Bad Request) — when the Payment-Signature header cannot be decoded or parsed. Plain text body.
Response (500 Internal Server Error) — facilitator failure or no matching payment offer. Plain text body.
Headers
| Header | Direction | Description |
|---|---|---|
Payment-Required | Response | Base64-encoded payment requirements (on 402) |
Payment-Signature | Request | Base64-encoded PaymentPayload (required for paid metadata) |
Content-Type: application/json | Response | All success and 402 responses are JSON |