Query
Execute SQL queries against connected data sources. Use standard Postgres-compatible SQL syntax to query and join across multiple connections. Results are returned inline and also persisted asynchronously for later retrieval.
Execute SQL query
/v1/queryExecute a SQL query scoped to a database. A database is the only window into catalogs: the query sees only that database's auto default catalog plus any catalogs explicitly attached to it. Select the database with EITHER the X-Database-Id header OR the database_id body field (exactly one must be given; if both are sent and disagree, that's a 400). Use standard Postgres-compatible SQL; reference the default catalog as default.<schema>.<table> (or just <schema>.<table> / <table>) and attached catalogs by their alias. Results are returned inline and a result_id is provided for later retrieval via the Results API.
Set async: true to execute asynchronously — returns a query run ID for polling. Optionally set async_after_ms to attempt synchronous execution first, falling back to async if the query exceeds the timeout.
Headers
X-Database-Idstring,null— Database id to scope the query to. Required unless thedatabase_idbody field is set; if both are present they must match. Only that database's catalogs are visible during planning. A malformed value is a 400; an unknown database id is a 404.
Request body
asyncboolean— When true, execute the query asynchronously and return a query run ID for polling via GET /query-runs/{id}. The query results can be retrieved via GET /results/{id} once the query run status is "succeeded". Default:falseasync_after_msinteger,null— If set (requiresasync= true), first attempt the query synchronously and wait up to this many milliseconds: if it finishes in time the full result is returned, otherwise an async response (a run id to poll) is returned. Must be at least 1000 and at most the server's configured maximum; a value out of that range, or set withoutasync= true, is rejected with 400. Min:1000database_idstring,null— Database to scope the query to (its id). Alternative to theX-Database-Idheader — exactly one source must be provided. If both this field and the header are set and they disagree, the request is rejected with a 400.default_catalogstring,null— Catalog that unqualified table references resolve against within the query's database scope. Must name a catalog visible in the database (default, an attached catalog alias, or a system catalog). Defaults todefaultwhen omitted.default_schemastring,null— Schema that unqualified table references resolve against within the query's database scope. Defaults tomainwhen omitted. Existence is not validated up front — an unknown schema surfaces as a "table not found" error at planning time.dialectstring,null— SQL dialect thesqlfield is written in. One ofhotsql(the default),duckdb,postgres, orsnowflake. When set to anything other thanhotsql, the query is translated to HotSQL before it runs, so you can use idioms from that dialect (for example SnowflakeIFF(...)or PostgresMOD(a, b)). Only read-only queries are accepted. An unrecognized value is rejected with a 400.sqlstring— required
{
"async": false,
"database_id": "dbid6lguax1dxn9y1xj5gxnameyywl",
"default_catalog": "default",
"default_schema": "main",
"dialect": "hotsql",
"sql": "SELECT customer_id, sum(amount) AS total FROM orders GROUP BY customer_id ORDER BY total DESC LIMIT 10"
}
Response 200 — Query executed successfully
columnsstring[] — requiredexecution_time_msinteger— required. Min:0nullableboolean[] — required. Nullable flags for each column (parallel to columns vec). True if the column allows NULL values, false if NOT NULL.preview_row_countinteger— required. Number of rows in this response body. Always present. For a large result this is a bounded preview, not the grand total — seetotal_row_countandtruncated.query_run_idstring— required. Unique identifier for the query run record (qrun...).result_idstring,null— Unique identifier for retrieving this result via GET /results/{id}. When non-null, the result is being persisted asynchronously. Null only when the result fit entirely in this response (truncated: false) but could not be persisted for later retrieval — see thewarningfield. Atruncated: trueresponse ALWAYS carries a non-null, resolvableresult_id: a truncated result that cannot be persisted fails the request with a retryable HTTP 503 (PERSISTENCE_UNAVAILABLE, with aRetry-Afterheader) rather than returning a partial body with a dead ticket.row_countinteger— required. Deprecated — usepreview_row_count(rows in this body) andtotal_row_count(grand total) instead. Retained as a back-compat alias and always equal topreview_row_count; for a truncated result it is the preview count, not the grand total — readtotal_row_countfor that. Will be removed in a future release once clients migrate. Min:0rowsany[][] — required. Array of rows, where each row is an array of column values. Values can be strings, numbers, booleans, or null.total_row_countinteger,null— Grand total rows in the full result. Present (and equal topreview_row_count) when the whole result fit in this response;nullwhile a truncated result is still being persisted. Whennull, read the authoritative total fromGET /v1/query-runs/{id}(row_count) or theX-Total-Row-Countheader onGET /v1/results/{id}.truncatedboolean— required. True whenrowsis a bounded preview of a larger result. Fetch the full result viaresult_id.warningstring,null— Warning message if result persistence could not be initiated. Present only when the full result is returned inline (truncated: false) but could not be persisted:result_idis then null and the result cannot be re-fetched later, though every row is in this response. A truncated result never carries a warning — if it cannot be persisted the request fails with a retryable HTTP 503 (PERSISTENCE_UNAVAILABLE, with aRetry-Afterheader) instead.
{
"columns": [
"string"
],
"execution_time_ms": 0,
"nullable": [
true
],
"preview_row_count": 0,
"query_run_id": "string",
"result_id": "string",
"row_count": 0,
"rows": [
[
null
]
],
"total_row_count": 0,
"truncated": true,
"warning": "string"
}
Response 202 — Query submitted asynchronously
query_run_idstring— required. Unique identifier for the query run.reasonstring,null— Human-readable reason why the query went async (e.g., caching tables for the first time).statusstring— required. Current status of the query run.status_urlstring— required. URL to poll for query run status. Requires the sameX-Database-Idheader used to submit the query.
{
"query_run_id": "string",
"reason": "string",
"status": "string",
"status_url": "string"
}
Errors
| Status | Description |
|---|---|
400 | Invalid request (no database specified, or header/body database_id conflict) |
404 | Database not found |
429 | The engine was too busy to run this query right now — too many concurrent queries, or not enough memory available (often because of other queries running at the same time). Retry after the Retry-After delay; if it persists, narrowing the query (add a filter or LIMIT) may help. |
500 | Internal server error |
503 | Result store temporarily unavailable (a truncated result could not be persisted); retry after the Retry-After delay |