Documentation menu

Queries API#

Submit a read-only SQL query, poll it by id, and collect the results — as signed
Parquet download URLs by default, or inline JSON rows when that's more
convenient. Everything on this page works with curl.

All requests are authenticated with a bearer credential — a personal access
token (pat_…), created in your dashboard and shown once at creation:

bash
Authorization: Bearer pat_...

Keep tokens server-side; they grant access to your data.

The lifecycle#

Queries run asynchronously. You submit SQL and immediately get back a query
object with status: "running" and an id. Poll it every 1–2 seconds (backing
off for long-running queries — polls count against your rate limit) until
status becomes succeeded or failed.

Submit a query#

POST/queries

Returns 202 with the running query; the Location header points at the
query's URL.

Body fieldType
sqlstringRequired. One read-only statement — see restrictions below.
bash
curl https://api.supernova.ai/queries \
  -H "Authorization: Bearer pat_..." \
  -H "Content-Type: application/json" \
  -d '{"sql": "select currency, count(*) as n from stripe.customers group by currency"}'
json
{
  "object": "query",
  "id": "qr_...",
  "status": "running",
  "created": 1751731200,
  "sql": "select currency, count(*) as n ...",
  "livemode": true
}

Retries are safe with an Idempotency-Key header: replaying the same key
returns the original query object instead of running it again, marked with an
Idempotent-Replayed: true header. A key is bound to the exact sql it first
ran — reusing it with different SQL is rejected with `409
idempotency_key_reused`, so an edited retry can never silently return the old
query's results. Keys expire after 24 hours.

Fetch status and results#

GET/queries/{id}

The query's status and, once succeeded, its results.

Query paramType
formatstringfiles (default) returns signed Parquet download URLs; inline returns rows in the response.
limitintegerinline only. Rows per page — default 100, max 1000.
starting_afterstringinline only. The next_cursor from the previous page.
bash
curl https://api.supernova.ai/queries/qr_... \
  -H "Authorization: Bearer pat_..."
json
{
  "object": "query",
  "id": "qr_...",
  "status": "succeeded",
  "created": 1751731200,
  "duration_ms": 812,
  "row_count": null,
  "files": [
    { "url": "https://storage.googleapis.com/...", "bytes": 18734, "expires": 1751734800 }
  ]
}

duration_ms is execution time, excluding time before execution. With format=inline,
rows come back paginated in the response — row_count is the total across all
pages:

json
{
  "object": "query",
  "id": "qr_...",
  "status": "succeeded",
  "created": 1751731200,
  "duration_ms": 812,
  "columns": ["currency", "n"],
  "row_count": 3,
  "data": [
    { "currency": "usd", "n": 1230 },
    { "currency": "eur", "n": 88 },
    { "currency": "gbp", "n": 41 }
  ],
  "has_more": false,
  "next_cursor": null
}

A failed query carries a structured error instead of results:

json
{
  "object": "query",
  "id": "qr_...",
  "status": "failed",
  "created": 1751731200,
  "duration_ms": 64,
  "error": { "message": "Binder Error: Table \"custmers\" does not exist" }
}

Result files#

The signed URLs need no authentication — anyone holding one can download the
file until it expires, so treat them like the data itself. URLs are valid for 1
hour; every poll mints fresh ones. Read files as a list even though results
are a single file today — large results may split in the future. The URLs
support HTTP range requests, so Parquet readers fetch selectively:

sql
-- DuckDB, in your own environment
select * from read_parquet('https://storage.googleapis.com/...')
text
# pandas
df = pd.read_parquet("https://storage.googleapis.com/...")

File-only responses don't scan the Parquet, so row_count is null there;
the exact count is in the Parquet metadata, or in any inline page.

Fetch results promptly: queries and their results are retained for at least 24
hours after submission.

SQL restrictions#

A query is rejected up front (HTTP 400) if it has more than one statement
(multiple_statements), isn't read-only — write, DDL, and session keywords are
rejected, though they're fine inside string literals, so `where type =
'DELETE' works (write_not_allowed`) — calls a file or storage function such
as read_parquet or read_csv, even inside string literals
(forbidden_function), or exceeds 100,000 characters (sql_too_long).
Storage URIs (gs://, s3://, file://) are rejected anywhere in the query
text; to filter a column holding them, match a substring without the scheme:
where path like '%bucket/x%'.

Errors#

Errors return the matching HTTP status and a JSON body; every response carries
a Request-Id header — include it when contacting support. Errors tied to a
specific input name it in param, and doc_url links the code's explanation:

json
{
  "error": {
    "type": "invalid_request_error",
    "code": "write_not_allowed",
    "message": "Only read-only queries are allowed. ...",
    "doc_url": "https://api.supernova.ai/docs#write_not_allowed",
    "request_id": "req_..."
  }
}

The codes this resource returns:

StatusCodeMeaning
400parameter_missingA required parameter (e.g. sql) was absent
400write_not_allowedThe query contained a write, DDL, or session keyword
400multiple_statementsMore than one ;-separated statement
400forbidden_functionA file or storage function was referenced
400sql_too_longsql exceeded 100,000 characters
400invalid_formatformat was not files or inline
400invalid_cursorstarting_after was not a cursor from a previous page
404query_not_foundNo query with that id under this account
409idempotency_key_reusedThe Idempotency-Key was already used with different sql
429rate_limitedRequest rate limit exceeded — honor Retry-After
429too_many_queriesToo many queries in flight for the account

Rate limits#

Each caller may make up to 120 requests per minute, and each account up to 600
per minute across all its callers; over either, requests return 429 with a
Retry-After header in seconds. There's also a ceiling on concurrently
in-flight queries per account (too_many_queries). At the recommended 1–2
second poll cadence, one caller comfortably polls a handful of queries at once
— back off if you run many in parallel.