Results
Retrieve persisted query results. Every query execution persists its results asynchronously. Results transition through statuses: processing → ready (or failed). Once ready, the full result data can be retrieved by ID.
List results
/v1/resultsList stored results for the database named by the required X-Database-Id header.
Query parameters
limitinteger— Maximum number of results (default: 100, max: 1000)offsetinteger— Pagination offset (default: 0)
Headers
X-Database-Idstring— required. Database to scope the results to (required)
Response 200 — List of results
countinteger— required. Number of results returned in this response. Min:0has_moreboolean— required. Whether there are more results available after this pagelimitinteger— required. Limit used for this request. Min:0offsetinteger— required. Pagination offset used for this request. Min:0resultsResultInfo[] — requiredcreated_atstring— requirederror_messagestring,nullidstring— requiredstatusstring— required
{
"count": 0,
"has_more": true,
"limit": 0,
"offset": 0,
"results": [
{
"created_at": "2026-01-01T00:00:00Z",
"error_message": "string",
"id": "string",
"status": "string"
}
]
}
Errors
| Status | Description |
|---|---|
400 | Missing or malformed X-Database-Id header |
404 | Database not found |
Get result
/v1/results/{id}Retrieve a persisted query result by ID. The response format for the ready state is selected by Accept header or ?format= query param; non-ready states use the same status codes and JSON body shape regardless of format.
| Result status | Status × body |
|---|---|
ready + JSON | 200 application/json — GetResultResponse with columns, rows, etc. |
ready + Arrow | 200 application/vnd.apache.arrow.stream — schema, RecordBatches, EOS |
ready + CSV | 200 text/csv; charset=utf-8 — single header row, streamed batch-by-batch |
ready + Markdown | 200 text/markdown; charset=utf-8 — GitHub-flavored pipe table, streamed |
ready + Parquet | 200 application/vnd.apache.parquet — raw parquet bytes (no conversion) |
pending/processing | 202 application/json {status, result_id} + Retry-After |
failed | 409 application/json {status, result_id, error_message} |
| not found | 404 application/json (ApiErrorResponse) |
?format= accepts arrow, json, csv, md, parquet and takes precedence over Accept. markdown is accepted as a runtime alias for md. Use ?offset=N&limit=M to slice the result; offset defaults to 0 and limit is unbounded by default. Both must be non-negative; invalid values return 400. When a finite limit doesn't reach the end of the result, a Link header with rel="next" points at the following page. ?offset/?limit are ignored for format=parquet since that path returns the underlying file unchanged.
JSON is the only format with a size limit. Building a JSON body requires holding the whole slice in memory at once, so a request whose JSON body would exceed the instance's per-fetch memory budget is refused with 413 and the error code RESULT_TOO_LARGE, and a request that would push the total across concurrent JSON fetches over that budget is refused with 429. If the result's size cannot be determined at all, the fetch is refused with 503 rather than served unchecked. None of the three returns partial data. To read a result of any size, request it as ?format=arrow, csv, md, or parquet — those stream and are never size-limited. Paging with offset/limit is not an alternative: the rows come back in no guaranteed order, so pages are not a reliable way to read one result in parts.
Ready responses (Arrow, CSV, Markdown, JSON) carry X-Total-Row-Count (the full result row count, independent of offset/limit); the JSON body repeats it as total_row_count, so a client that cannot read response headers can still compare it against row_count to tell a complete result from a windowed one. Responses are streamed end-to-end, so a client can disconnect at any time and the server stops reading.
IEEE special floats (±Inf, NaN) have no canonical JSON representation. For cross-format consistency the JSON, CSV, and Markdown paths emit them as null / empty cells, and JSON nullable[] is widened to match. The Arrow IPC and Parquet bodies are binary round-trip formats and preserve the raw IEEE values; callers cross-checking a result across CSV and Parquet should not byte-compare those slots.
Path parameters
idstring— required. Result ID
Query parameters
offsetinteger— Rows to skip (default: 0)limitinteger— Maximum rows to return (default: unbounded)formatResultsFormatQuery—arrow,json,csv,md, orparquet— overrides theAcceptheader.markdownis also accepted at runtime as an alias formd.
Headers
X-Database-Idstring— required. Database the result belongs to (required)
Response 200 — Result data. The body depends on the negotiated format: JSON callers receive GetResultResponse; Arrow callers receive an Arrow IPC stream; CSV callers receive comma-separated text (LF-terminated, double-quote escaped, RFC 4180-style quoting but not RFC 4180-strict on line endings); Markdown callers receive a single GitHub-flavored pipe table; Parquet callers receive the raw parquet bytes, served as-is. Non-finite floats (±Inf, NaN) render as null (JSON) or empty cells (CSV, Markdown) for cross-format consistency. Accept is treated as a flat content-type list — q= quality values are ignored; use ?format= to disambiguate.
columnsstring[] |nullerror_messagestring,nullnullableboolean[] |nullresult_idstring— requiredrow_countinteger,nullrowsany[][] |null— Array of rows, where each row is an array of column values.statusstring— requiredtotal_row_countinteger,null— Grand total rows in the full result, ignoringoffsetandlimit. Present whenever the result isready, and carrying the same value as theX-Total-Row-Countresponse header. Compare it againstrow_countto tell whether this body is the whole result:row_count < total_row_countmeans the rest is still there, one page further on. Without it a windowed fetch cannot tell a full result from a truncated one from the body alone.
{
"columns": [
"string"
],
"error_message": "string",
"nullable": [
true
],
"result_id": "string",
"row_count": 0,
"rows": [
[
null
]
],
"status": "string",
"total_row_count": 0
}
Response 202 — Result is still being computed (pending or processing). Poll the same URL.
columnsstring[] |nullerror_messagestring,nullnullableboolean[] |nullresult_idstring— requiredrow_countinteger,nullrowsany[][] |null— Array of rows, where each row is an array of column values.statusstring— requiredtotal_row_countinteger,null— Grand total rows in the full result, ignoringoffsetandlimit. Present whenever the result isready, and carrying the same value as theX-Total-Row-Countresponse header. Compare it againstrow_countto tell whether this body is the whole result:row_count < total_row_countmeans the rest is still there, one page further on. Without it a windowed fetch cannot tell a full result from a truncated one from the body alone.
{
"columns": [
"string"
],
"error_message": "string",
"nullable": [
true
],
"result_id": "string",
"row_count": 0,
"rows": [
[
null
]
],
"status": "string",
"total_row_count": 0
}
Errors
| Status | Description |
|---|---|
400 | Invalid offset, limit, or format. |
404 | Result not found. |
409 | Result computation failed. Body carries error_message describing the failure. |
413 | The requested slice is too large to return as JSON (error code RESULT_TOO_LARGE). No partial data is returned. Retrying the same request will fail again; request the result as ?format=arrow, csv, md, or parquet instead, which stream and have no size limit. JSON only. |
429 | Concurrent JSON fetches have already committed the instance's per-fetch memory budget. Transient — retry shortly, or request the result in a streaming format, which has no size limit. JSON only. |
503 | The result's size could not be determined, so it could not be checked against the memory budget and was not served. Transient — retry shortly, or request the result in a streaming format, which needs no size check. JSON only. |