HTTP SQL API
Run SQL over HTTPS with streamed or asynchronous statements, sessions, safe retries and stable error codes.
The HTTP SQL API is a complete SQL interface over HTTPS. Use it from scripts, services and any language that has an HTTP client. It runs the same SQL, permissions and warehouses as Flight SQL and Spark Connect.
| Request | Behavior |
|---|---|
POST /v1/sql | Runs one statement and streams its result in the requested format. |
POST /v1/sql?query=INSERT INTO t FORMAT <format> | Inserts the request body (CSV, JSONEachRow, Parquet or Arrow). |
POST /v1/statements | Submits a statement. Returns the result when it is ready within wait_timeout, otherwise a statement ID. |
GET /v1/statements/{id} | Returns the statement's state, progress and result. |
DELETE /v1/statements/{id} | Cancels the statement. |
DELETE /v1/sessions/{id} | Closes a session, cancelling its active statement and rolling back an open transaction. |
GET /openapi.json | Describes the requests, states, errors, result schemas and the limits in force. |
Every request carries a credential (Authorization: Bearer <token> or X-API-Key: <api key>) and the X-Vega-Organization and X-Vega-Workspace headers. See authentication.
Stream a result
Send the SQL as the request body, or in the query parameter.
curl --request POST "https://<warehouse endpoint>/v1/sql?format=CSV&warehouse=bi" \
--header "Authorization: Bearer $VEGADB_TOKEN" \
--header "X-Vega-Organization: <organization id>" \
--header "X-Vega-Workspace: <workspace id>" \
--header "Accept-Encoding: zstd" \
--data-binary @daily_revenue.sql \
--output daily_revenue.csv.zst| Parameter | Meaning |
|---|---|
format | JSON (default), JSONEachRow, CSV, TSV, Arrow (Arrow IPC stream) or Parquet. |
warehouse | The warehouse to run on, when the workspace has more than one. |
database | The default schema, as schema or catalog.schema. |
session_id, create_session | Run in a named session, or create one. See sessions. |
request_id (or X-Request-Id) | A UUID that makes a retry safe. See retries. |
query_id | A UUID for the query; defaults to the request ID or a generated UUID. |
read_only | Refuse any statement that changes data or session state. |
timeout_ms | Statement timeout, including time spent queued and waiting for the warehouse. |
settings | A JSON object of session settings, such as application_name. |
gzip and zstd compression follow Accept-Encoding. Response headers carry X-Vega-Query-Id, X-Vega-Statement-Id and, in a session, X-Vega-Session-Id. Progress and a summary (rows, bytes, elapsed time and warehouse wait) arrive in headers and trailers.
A complete result always says so
A streamed result can fail after its first byte. Every format therefore ends with an explicit marker, and the response ends with an x-vega-status trailer:
| Format | End of a complete result |
|---|---|
JSON | The document's complete field, or its error. |
JSONEachRow | Each data row is wrapped as {"row": {...}}; the last record has status and complete, or error. |
CSV, TSV | A final #vega_status record. |
Arrow | The Arrow IPC end-of-stream marker. |
Parquet | The Parquet footer. |
Treat a response without its marker as incomplete. Because data rows are wrapped, a column named status or complete cannot be mistaken for the end of the result.
JSON, JSONEachRow, CSV and TSV render values as text or null and report each column's SQL type. Arrow and Parquet keep the native types, including nested ones; use them for exports.
Insert data
curl --request POST "https://<warehouse endpoint>/v1/sql?query=INSERT%20INTO%20sales.orders%20FORMAT%20CSV" \
--header "X-API-Key: $VEGADB_API_KEY" \
--header "X-Vega-Organization: <organization id>" \
--header "X-Vega-Workspace: <workspace id>" \
--header "X-Request-Id: 6f1c2b8e-3f43-4a51-9f39-2a1d7c0e5b10" \
--data-binary @orders.csvThe body goes through the ordinary write path, with the same permissions and constraints as an INSERT statement. A request_id makes the load safe to retry.
Asynchronous statements
POST /v1/statements takes a JSON body:
{
"sql": "select region, sum(amount) as amount from sales.orders group by region",
"warehouse": "bi",
"wait_timeout": 10
}The request waits up to wait_timeout seconds (0–50, default 0). A small result that is ready in time comes back inline; otherwise the response carries the statement id to poll.
{
"id": "<statement id>",
"query_id": "1b0d9c62-6c4e-4b0e-9f0c-0e8f5f3e2a71",
"state": "running",
"progress": {"elapsed_ms": 1840, "rows": 0, "bytes": 0, "warehouse_wait_ms": 1200}
}| State | Meaning |
|---|---|
queued | Waiting for warehouse capacity. |
resuming | Waiting while the warehouse starts. |
running | Executing. |
succeeded | Finished; result or affected_rows is present. |
failed | error says why. |
cancelled | Cancelled by DELETE or by its timeout. |
A succeeded result has columns (name, SQL type, nullability) and either inline rows or links. Results larger than the inline limits are returned as links to Arrow IPC chunks, each with its row and byte counts, a SHA-256 and an expiry. Download the chunks in order. Links expire within a minute; poll the statement again for fresh links. Every download checks the caller's current permissions.
With truncate: true, a result over the inline limits returns its first rows with complete: false instead.
A statement can also be polled or cancelled by its query_id together with the X-Vega-Session-Id header. A cancellation that arrives before its statement is kept, and cancels the statement when it starts.
Sessions
Requests are independent by default: each one runs in its own short-lived context. To keep state across requests, set create_session: true on the first one and pass the returned session_id (also sent as X-Vega-Session-Id) on the next ones.
A session keeps USE, SET application_name, SET query_timeout_ms, the request's database, warehouse and settings, temporary views and an open transaction. One statement runs in a session at a time. Idle sessions close after the session timeout. A credential can use a session only if it carries at least the session's roles and scopes.
VEGA=(--header "X-API-Key: $VEGADB_API_KEY"
--header "X-Vega-Organization: <organization id>"
--header "X-Vega-Workspace: <workspace id>"
--header "Content-Type: application/json")
# BEGIN in a new session; the response carries "session_id".
curl --request POST "https://<warehouse endpoint>/v1/statements" "${VEGA[@]}" \
--data '{"sql": "BEGIN", "create_session": true}'
curl --request POST "https://<warehouse endpoint>/v1/statements" "${VEGA[@]}" \
--data '{"sql": "UPDATE sales.orders SET status = '\''paid'\'' WHERE order_id = 42", "session_id": "<session id>", "request_id": "<uuid>"}'
curl --request POST "https://<warehouse endpoint>/v1/statements" "${VEGA[@]}" \
--data '{"sql": "COMMIT", "session_id": "<session id>", "request_id": "<uuid>"}'
# Close the session.
curl --request DELETE "https://<warehouse endpoint>/v1/sessions/<session id>" "${VEGA[@]}"EXPLAIN ANALYZE and a second BEGIN inside an open transaction return TRANSACTION_ACTIVE. See transactions and parameters.
Retries
Give each write a UUID request_id (or X-Request-Id) and keep the same SQL, input and context when you retry it.
- A repeated request returns the recorded outcome of the first; the write is not applied twice.
- The same ID with different SQL or input returns
REQUEST_CONFLICT. WRITE_CONFLICTandOUTCOME_UNKNOWNcan be resubmitted with the same ID; VegaDB resolves whether the earlier attempt committed.ROLLBACKdiscards the outcomes recorded for that transaction's statements, including itsBEGIN. Those IDs then returnTRANSACTION_ROLLED_BACK; use fresh IDs to run the work again.
Errors
A refused request returns an HTTP error status and {"error": {"code": "...", "message": "..."}}. A statement that fails reports error in its state. A write that committed but whose result could not be delivered reports result_error instead, so a client never mistakes a committed write for a failed one.
Codes are stable; messages are descriptive and can change.
| Code | Meaning |
|---|---|
SQL_SYNTAX | The statement does not parse. |
SQL_CATALOG | A catalog, schema, table or other object does not exist or already exists. |
SQL_BINDING | A column, function or type does not resolve. |
SQL_CONSTRAINT | A constraint such as NOT NULL or PRIMARY KEY failed. |
SQL_CONVERSION, SQL_RANGE | A value cannot be converted, or is out of range. |
DATA_ACCESS_DENIED | The identity lacks permission on an object the statement uses. |
QUERY_CANCELLED | The statement was cancelled. |
QUERY_TIMEOUT | The statement passed its timeout. |
READ_ONLY_VIOLATION | A read_only request tried to change data or session state. |
TRANSACTION_ACTIVE | The statement is not allowed inside an open transaction. |
TRANSACTION_ROLLED_BACK | The request ID belongs to a rolled-back transaction. |
WRITE_CONFLICT | A concurrent write conflicted; retry with the same request ID. |
OUTCOME_UNKNOWN | Resubmit with the same request ID to resolve the outcome. |
REQUEST_CONFLICT | The request ID was used for a different request. |
SESSION_BUSY | Another statement is running in the session. |
SESSION_UNAVAILABLE | The session no longer exists; start a new one. |
WAREHOUSE_SUSPENDED | The warehouse was stopped explicitly and does not start on a query; start it first. |
WAREHOUSE_RESUME_TIMEOUT | The warehouse did not start within its resume timeout. |
Limits
GET /openapi.json lists the limits in force, under x-vega-limits: request body and statement size, inline result rows and bytes, export bytes for Arrow streams, concurrent statements, and connection limits. A result has at most 256 columns, and a text cell in JSON, JSONEachRow, CSV or TSV at most 1 MiB. Statement timeouts include time spent queued and waiting for the warehouse.
Warehouse control
Start, stop and inspect warehouses under /api/v1/vegadb/orgs/{org_id}/workspaces/{workspace_id}/warehouses/{warehouse_id}. See warehouses and scaling.