Execute API
Run SQL queries programmatically. One endpoint, one API key, instant results.
Quick Start
curl -X POST https://api.rawquery.dev/api/v1/execute \ -H "X-API-Key: rq_your_api_key" \ -H "Content-Type: application/json" \ -d '{"sql": "SELECT 1 AS num"}'curl -X POST https://api.rawquery.dev/api/v1/execute \ -H "X-API-Key: rq_your_api_key" \ -H "Content-Type: application/json" \ -d '{"sql": "SELECT 1 AS num"}'Authentication
The Execute API uses API keys via the X-API-Key header. This is separate from JWT Bearer tokens used by the dashboard.
Getting an API Key
rq api-keys create --name my-keyrq api-keys create --name my-keyOr go to Settings > API Keys in the dashboard. The key starts with rq_.
Using the API Key
X-API-Key: rq_your_api_key_hereX-API-Key: rq_your_api_key_hereOr via the Authorization: Bearer header if you prefer (both work with rq_ keys).
POST /api/v1/execute
Execute a SQL query and return results. Supports Postgres-compatible SQL syntax.
Request Body
{ "sql": "SELECT * FROM my_stripe.customers LIMIT 10", "format": "json", "workspace_id": "optional-uuid"}{ "sql": "SELECT * FROM my_stripe.customers LIMIT 10", "format": "json", "workspace_id": "optional-uuid"}| Field | Type | Required | Description |
|---|---|---|---|
sql | string | Yes | SQL query (DuckDB syntax) |
format | string | No | json (default), csv, or objects |
workspace_id | uuid | No | Target workspace. Defaults to your first workspace. |
Response (JSON)
{ "columns": [ { "name": "id", "type": "VARCHAR" }, { "name": "email", "type": "VARCHAR" } ], "rows": [ ["cus_abc123", "alice@example.com"], ["cus_def456", "bob@example.com"] ], "row_count": 2, "execution_time_ms": 45.12, "metadata": { "bytes_scanned": 1024, "cost_eur": 0.00001 }}{ "columns": [ { "name": "id", "type": "VARCHAR" }, { "name": "email", "type": "VARCHAR" } ], "rows": [ ["cus_abc123", "alice@example.com"], ["cus_def456", "bob@example.com"] ], "row_count": 2, "execution_time_ms": 45.12, "metadata": { "bytes_scanned": 1024, "cost_eur": 0.00001 }}Response (Objects)
When format: "objects", returns an array of objects - the developer-friendly format:
{ "data": [ { "id": "cus_abc123", "email": "alice@example.com" }, { "id": "cus_def456", "email": "bob@example.com" } ], "row_count": 2, "execution_time_ms": 45.12, "metadata": { "bytes_scanned": 1024, "cost_eur": 0.00001 }}{ "data": [ { "id": "cus_abc123", "email": "alice@example.com" }, { "id": "cus_def456", "email": "bob@example.com" } ], "row_count": 2, "execution_time_ms": 45.12, "metadata": { "bytes_scanned": 1024, "cost_eur": 0.00001 }}Use format=objects when you want to consume results directly without mapping columns to rows.
Response (CSV)
When format: "csv", returns text/csv with a Content-Disposition header:
id,emailcus_abc123,alice@example.comcus_def456,bob@example.comid,emailcus_abc123,alice@example.comcus_def456,bob@example.comGET /api/v1/execute/tables
List all tables available for querying.
Response
{ "tables": [ { "name": "customers", "schema_name": "my_stripe", "row_count": 1234, "size_bytes": 524288, "column_count": 12 } ]}{ "tables": [ { "name": "customers", "schema_name": "my_stripe", "row_count": 1234, "size_bytes": 524288, "column_count": 12 } ]}Error Handling
Every error includes a code, message, and optional hint to help you fix it.
{ "detail": { "error": { "code": "SYNTAX_ERROR", "message": "Syntax error at line 1: unexpected token 'FORM'", "hint": "Check your SQL syntax. rawquery uses Postgres-compatible SQL." } }}{ "detail": { "error": { "code": "SYNTAX_ERROR", "message": "Syntax error at line 1: unexpected token 'FORM'", "hint": "Check your SQL syntax. rawquery uses Postgres-compatible SQL." } }}| HTTP Status | Error Code | Meaning |
|---|---|---|
401 | MISSING_API_KEY | No X-API-Key header provided |
401 | INVALID_API_KEY | API key not found or invalid format |
401 | EXPIRED_API_KEY | API key has expired |
400 | SYNTAX_ERROR | Invalid SQL syntax |
400 | UNDEFINED_TABLE | Table doesn't exist |
400 | UNDEFINED_COLUMN | Column doesn't exist |
400 | QUERY_TOO_LARGE | Query scan exceeds plan limit |
408 | QUERY_TIMEOUT | Query exceeded 30s timeout |
400 | SNAPSHOT_NOT_FOUND | No data snapshot exists at the requested time (time travel) |
400 | NO_WORKSPACE | No workspace found for your account |
403 | WORKSPACE_ACCESS_DENIED | Not a member of the specified workspace |
500 | INTERNAL_ERROR | Unexpected server error |
Rate Limits
Rate limits are applied per workspace based on your plan. Limits are counted over a 60-second sliding window.
| Plan | Queries / minute |
|---|---|
| Free | 10 |
| Team | 60 |
| Business | 100 |
Examples
curl
# JSON result (columns + rows)curl -X POST https://api.rawquery.dev/api/v1/execute \ -H "X-API-Key: rq_your_api_key" \ -H "Content-Type: application/json" \ -d '{"sql": "SELECT * FROM my_stripe.customers LIMIT 5"}'
# Objects format (array of dicts - easiest to consume)curl -X POST https://api.rawquery.dev/api/v1/execute \ -H "X-API-Key: rq_your_api_key" \ -H "Content-Type: application/json" \ -d '{"sql": "SELECT * FROM my_stripe.customers LIMIT 5", "format": "objects"}'
# CSV downloadcurl -X POST https://api.rawquery.dev/api/v1/execute \ -H "X-API-Key: rq_your_api_key" \ -H "Content-Type: application/json" \ -d '{"sql": "SELECT * FROM my_stripe.customers", "format": "csv"}' \ -o customers.csv
# List tablescurl https://api.rawquery.dev/api/v1/execute/tables \ -H "X-API-Key: rq_your_api_key"# JSON result (columns + rows)curl -X POST https://api.rawquery.dev/api/v1/execute \ -H "X-API-Key: rq_your_api_key" \ -H "Content-Type: application/json" \ -d '{"sql": "SELECT * FROM my_stripe.customers LIMIT 5"}'
# Objects format (array of dicts - easiest to consume)curl -X POST https://api.rawquery.dev/api/v1/execute \ -H "X-API-Key: rq_your_api_key" \ -H "Content-Type: application/json" \ -d '{"sql": "SELECT * FROM my_stripe.customers LIMIT 5", "format": "objects"}'
# CSV downloadcurl -X POST https://api.rawquery.dev/api/v1/execute \ -H "X-API-Key: rq_your_api_key" \ -H "Content-Type: application/json" \ -d '{"sql": "SELECT * FROM my_stripe.customers", "format": "csv"}' \ -o customers.csv
# List tablescurl https://api.rawquery.dev/api/v1/execute/tables \ -H "X-API-Key: rq_your_api_key"Python
import requests
API_KEY = "rq_your_api_key"BASE_URL = "https://api.rawquery.dev/api/v1"
response = requests.post( f"{BASE_URL}/execute", headers={"X-API-Key": API_KEY}, json={"sql": "SELECT * FROM my_stripe.customers LIMIT 5"},)
data = response.json()for row in data["rows"]: print(row)
# With pandasimport pandas as pd
df = pd.DataFrame( data["rows"], columns=[c["name"] for c in data["columns"]],)print(df)import requests
API_KEY = "rq_your_api_key"BASE_URL = "https://api.rawquery.dev/api/v1"
response = requests.post( f"{BASE_URL}/execute", headers={"X-API-Key": API_KEY}, json={"sql": "SELECT * FROM my_stripe.customers LIMIT 5"},)
data = response.json()for row in data["rows"]: print(row)
# With pandasimport pandas as pd
df = pd.DataFrame( data["rows"], columns=[c["name"] for c in data["columns"]],)print(df)Time Travel
Query historical data using FOR SYSTEM_TIME AS OF:
# Query data as it was on January 15, 2024curl -X POST https://api.rawquery.dev/api/v1/execute \ -H "X-API-Key: rq_your_api_key" \ -H "Content-Type: application/json" \ -d '{"sql": "SELECT * FROM my_stripe.charges FOR SYSTEM_TIME AS OF '\''2024-01-15'\'' LIMIT 10"}'# Query data as it was on January 15, 2024curl -X POST https://api.rawquery.dev/api/v1/execute \ -H "X-API-Key: rq_your_api_key" \ -H "Content-Type: application/json" \ -d '{"sql": "SELECT * FROM my_stripe.charges FOR SYSTEM_TIME AS OF '\''2024-01-15'\'' LIMIT 10"}'JavaScript / TypeScript
const API_KEY = "rq_your_api_key";const BASE_URL = "https://api.rawquery.dev/api/v1";
const response = await fetch(`${BASE_URL}/execute`, { method: "POST", headers: { "X-API-Key": API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ sql: "SELECT * FROM my_stripe.customers LIMIT 5", }),});
const data = await response.json();console.log(data.columns.map(c => c.name));console.log(data.rows);const API_KEY = "rq_your_api_key";const BASE_URL = "https://api.rawquery.dev/api/v1";
const response = await fetch(`${BASE_URL}/execute`, { method: "POST", headers: { "X-API-Key": API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ sql: "SELECT * FROM my_stripe.customers LIMIT 5", }),});
const data = await response.json();console.log(data.columns.map(c => c.name));console.log(data.rows);