Base URL examples:
- Local:
http://localhost:8000 - Containerized (mapped):
http://<host>:8000
Interactive OpenAPI:
/api/v1/docs/api/v1/redoc/api/v1/openapi.json
- Protected endpoints require
X-API-Keyonly whenQUERYSERVICE_AUTH_ENABLED=true. - Health endpoints do not require API key.
X-Request-IDis accepted and echoed in response headers.X-Client-Versionis accepted on requests and recorded in server logs.- Error envelopes usually include
request_idwhen available.
X-API-Version: 1is returned on API responses.X-RateLimit-Limit,X-RateLimit-Remaining, andX-RateLimit-Resetare returned when rate limiting is enabled for the endpoint.Retry-Afteris returned on429responses.
{
"date_range": {"type": "single", "date": "2026-03-01"},
"... endpoint-specific fields ..."
}date_range schema:
- Single day:
{"type": "single", "date": "YYYY-MM-DD"} - Inclusive range:
{"type": "range", "start": "YYYY-MM-DD", "end": "YYYY-MM-DD"}
date_range behavior by dataset:
- If dataset source defines a time filter column,
date_rangeis applied to that column. - If dataset source has no time filter,
date_rangeis accepted but ignored.
{
"code": "DATASET_NOT_FOUND",
"message": "Dataset not found: trades_v1",
"request_id": "trace-123",
"details": {"dataset_id": "trades_v1"}
}Process liveness probe.
Authentication: not required.
Response 200:
{"status": "ok"}Readiness probe (checks DuckDB health).
Authentication: not required.
Responses:
200:
{"status": "ok"}503:
{"status": "unavailable"}Returns registered datasets with summary metadata and discovery links.
Authentication: conditional API key.
Query parameters:
limit(1..200, default50)cursor(opaque pagination cursor)
Success 200 example:
{
"items": [
{
"id": "trades_v1",
"display_name": "Equity Trades",
"description": "Daily equity trade records",
"field_count": 3,
"row_count": 8,
"time_dimension": {
"field": "date",
"min": "2026-03-01",
"max": "2026-03-02",
"max_range_days": 365
},
"links": {
"self": "/api/v1/datasets/trades_v1",
"schema": "/api/v1/datasets/trades_v1/schema",
"tuples": "/api/v1/datasets/trades_v1/query/tuples",
"cells": "/api/v1/datasets/trades_v1/query/cells",
"members": "/api/v1/datasets/trades_v1/query/members"
}
}
],
"cursor": null,
"total_count": 1
}Returns full discovery metadata for one dataset.
Authentication: conditional API key.
Path parameters:
dataset_id(required, string)
Response headers:
ETagCache-Control: private, max-age=60
Success 200 example:
{
"id": "trades_v1",
"display_name": "Equity Trades",
"description": "Daily equity trade records",
"source_kind": "sample_table",
"row_count": 8,
"version": "v1-1234abcd",
"time_dimension": {
"field": "date",
"min": "2026-03-01",
"max": "2026-03-02",
"max_range_days": 365
},
"fields": [
{ "name": "date", "type": "date", "role": "dimension", "nullable": null, "distinct_count": 2 },
{ "name": "symbol", "type": "string", "role": "dimension", "nullable": null, "distinct_count": 5 },
{ "name": "volume", "type": "int64", "role": "measure", "nullable": null, "distinct_count": 8 }
],
"links": {
"self": "/api/v1/datasets/trades_v1",
"schema": "/api/v1/datasets/trades_v1/schema",
"tuples": "/api/v1/datasets/trades_v1/query/tuples",
"cells": "/api/v1/datasets/trades_v1/query/cells",
"members": "/api/v1/datasets/trades_v1/query/members"
}
}Conditional GET:
- Send
If-None-Matchwith the currentETagto receive304 Not Modified.
Returns schema metadata for a configured dataset.
Authentication: conditional API key.
Path parameters:
dataset_id(required, string)
Example:
curl 'http://localhost:8000/api/v1/datasets/trades_v1/schema'Success 200 example:
{
"dataset_id": "trades_v1",
"version": "v1-1234abcd",
"fields": [
{"name": "date", "type": "date", "role": "dimension"},
{"name": "symbol", "type": "string", "role": "dimension"},
{"name": "volume", "type": "int64", "role": "measure"}
]
}Error statuses:
404DATASET_NOT_FOUND401auth failure when auth enabled304whenIf-None-Matchmatches the currentETag
Returns distinct tuple values for selected fields.
Authentication: conditional API key.
Request body fields:
date_range(optional)fields(array of objects):field(string, required)sort(ASCorDESC, optional)derivation(optional)include_totals(optional)
filters(optional):field(string)operator(include,exclude,like,between,gt,gte,lt,lte,is_null,not_null; case-insensitive)values:- Required for:
include,exclude,like,between,gt,gte,lt,lte - Must be omitted or an empty array for:
is_null,not_null - Cardinality by operator:
gt,gte,lt,lte,like: exactly 1 valuebetween: exactly 2 valuesinclude,exclude: between 1 and 1000 values
- Required for:
paging(optional):limit(1..10000, default from config)offset(>=0)
Request example:
{
"date_range": {"type": "single", "date": "2026-03-01"},
"fields": [{"field": "symbol", "sort": "ASC"}],
"filters": [{"field": "symbol", "operator": "INCLUDE", "values": ["AAPL", "GOOG"]}],
"paging": {"limit": 10, "offset": 0}
}Success 200 example:
{
"total_count": 2,
"items": [{"symbol": "AAPL"}, {"symbol": "GOOG"}],
"paging": {"limit": 10, "offset": 0, "returned": 2},
"meta": {
"execution_ms": 4.1,
"cache_status": "miss",
"request_id": "srv-tuples123"
}
}Error statuses:
404DATASET_NOT_FOUND409DATASET_UNAVAILABLE422VALIDATION_ERROR401auth failure when auth enabled429if rate limiting enabled and exceeded
Returns aggregated cells grouped by row/column axes.
Authentication: conditional API key.
Request body fields:
date_range(optional)axes.rows(array of{ "field": string })axes.columns(array of{ "field": string })axes.measures(array):field(string)aggregation- Supported:
sum,avg,min,max,count,distinct_count,median,mode,stdev,variance,geomean,entropy,kurtosis,skewness,mad,and,or,count_if_true,count_if_false,list,unique_list,first,last - Explicitly rejected:
histogram(AGGREGATION_NOT_SUPPORTED)
- Supported:
alias(string, optional)sort_by(string, required only forfirstandlast)
window.rowsandwindow.columns(optional):offset(>=0)limit(>=1)
filters(optional)filters[*].operatorsupports the same 10 case-insensitive operators as tuples/cells
Request example:
{
"date_range": {"type": "single", "date": "2026-03-01"},
"window": {
"rows": {"offset": 0, "limit": 10}
},
"axes": {
"rows": [{"field": "symbol"}],
"columns": [],
"measures": [{"field": "volume", "aggregation": "sum", "alias": "sum_volume"}]
}
}Success 200 example:
{
"rows": [
{ "symbol": "AAPL" },
{ "symbol": "GOOG" }
],
"columns": [
{}
],
"cells": [
{ "row": 0, "col": 0, "sum_volume": 1500 },
{ "row": 1, "col": 0, "sum_volume": 2200 }
],
"window": {
"rows": { "offset": 0, "limit": 10, "total": 2 },
"columns": { "offset": 0, "limit": 1, "total": 1 }
},
"meta": {
"execution_ms": 12.4,
"cache_status": "miss",
"request_id": "srv-abc123"
}
}Error statuses:
400CELLS_WINDOW_TOO_LARGE404DATASET_NOT_FOUND409DATASET_UNAVAILABLE422VALIDATION_ERROR422SORT_BY_REQUIRED422AGGREGATION_NOT_SUPPORTED401auth failure when auth enabled429if rate limiting enabled and exceeded
Returns distinct values for one field, typically used for filter UIs.
Authentication: conditional API key.
Request body fields:
date_range(optional)field(string)search(string, optional;*is translated to SQL%wildcard)filters(optional)paging(optional)
Request example:
{
"date_range": {"type": "range", "start": "2026-03-01", "end": "2026-03-02"},
"field": "symbol",
"search": "A*",
"paging": {"limit": 10, "offset": 0}
}Success 200 example:
{
"field": "symbol",
"total_count": 2,
"items": [{"value": "AAPL", "count": 2}, {"value": "AMZN", "count": 1}],
"paging": {"limit": 10, "offset": 0, "returned": 2},
"meta": {
"execution_ms": 3.8,
"cache_status": "hit",
"request_id": "srv-members123"
}
}Error statuses:
404DATASET_NOT_FOUND409DATASET_UNAVAILABLE422VALIDATION_ERROR401auth failure when auth enabled429if rate limiting enabled and exceeded
Submits async export job and returns job id immediately.
Authentication: conditional API key.
Path parameters:
dataset_id(string, required)
Request body fields:
date_range(required)query.axes(optional; rows/columns/measures)query.filters(optional)query.max_rows(1..100000, default10000)query.format(parquet,csv,csv_with_bom,ndjson,sqlite, orduckdb; defaultparquet)
Request example:
{
"date_range": {"type": "single", "date": "2026-03-01"},
"query": {
"axes": {
"rows": [{"field": "symbol"}],
"measures": [{"field": "volume", "aggregation": "sum", "alias": "total_volume"}]
},
"max_rows": 1000,
"format": "parquet"
}
}Success 202 example:
{
"export_id": "exp-1234abcd",
"dataset_id": "trades_v1",
"status": "pending",
"links": {
"self": "/api/v1/exports/exp-1234abcd",
"file": "/api/v1/exports/exp-1234abcd/file"
}
}Error statuses:
404DATASET_NOT_FOUND409DATASET_UNAVAILABLE429TOO_MANY_EXPORTS422VALIDATION_ERROR401auth failure when auth enabled429if rate limiting enabled and exceeded
Returns export job status.
Authentication: conditional API key.
Path parameters:
export_id(string)
Success 200 example:
{
"export_id": "exp-1234abcd",
"dataset_id": "trades_v1",
"status": "complete",
"format": "parquet",
"created_at": "2026-03-10T01:00:00Z",
"expires_at": "2026-03-10T02:00:00Z",
"download_url": "/api/v1/exports/exp-1234abcd/file",
"size_bytes": 2183640,
"completed_at": "2026-03-10T01:00:12Z",
"links": {
"self": "/api/v1/exports/exp-1234abcd",
"file": "/api/v1/exports/exp-1234abcd/file"
}
}Other statuses include pending, processing, failed, expired, and cancelled.
Error statuses:
404EXPORT_NOT_FOUND401auth failure when auth enabled
Lists export jobs newest-first.
Authentication: conditional API key.
Query parameters:
limit(default20, max100)cursor(opaque pagination cursor)status(optional job-state filter)
Success 200 example:
{
"items": [
{
"export_id": "exp-1234abcd",
"dataset_id": "trades_v1",
"status": "complete",
"format": "parquet",
"row_count": 15420,
"size_bytes": 2183640,
"created_at": "2026-03-10T01:00:00Z",
"expires_at": "2026-03-10T02:00:00Z",
"links": {
"self": "/api/v1/exports/exp-1234abcd",
"file": "/api/v1/exports/exp-1234abcd/file"
}
}
],
"cursor": null
}Downloads completed export artifact.
Authentication: conditional API key.
Path parameters:
export_id(string)
Success 200:
- Includes
Content-Length,ETag, andLast-Modifiedresponse headers - For parquet export: binary file,
Content-Disposition: attachment; filename="<id>.parquet" - For csv / csv_with_bom export:
text/csv,Content-Disposition: attachment; filename="<id>.csv" - For ndjson export:
application/x-ndjson,Content-Disposition: attachment; filename="<id>.ndjson"
Error statuses:
404EXPORT_NOT_FOUND409EXPORT_NOT_READY404EXPORT_FILE_NOT_FOUND401auth failure when auth enabled
Returns the same file metadata headers as GET /api/v1/exports/{export_id}/file without a response body.
Cancels active export jobs or deletes terminal jobs and their files.
Success 204 with no response body.
Error statuses:
404EXPORT_NOT_FOUND401auth failure when auth enabled
- Pagination:
query.paging.limitandquery.paging.offseton tuples/picklist- Defaults:
tuples_default_limit,picklist_default_limit
- Filtering operators:
INCLUDE,EXCLUDE,LIKE,BETWEEN
- Sorting:
query.fields[].sorton tuples (ASC/DESC)
- Cells windows:
query.rows/query.columnswithstart_indexandcount- Limited by
max_axis_cardinalityandmax_cells_per_response
- Disabled by default (
QUERYSERVICE_RATE_LIMIT_ENABLED=false) - Query endpoints use
QUERYSERVICE_RATE_LIMIT_QUERY - Export submission uses
QUERYSERVICE_RATE_LIMIT_EXPORT - Exceeded limits return
429; clients should honorRetry-After