HTTP/REST API Examples
All examples use the default address http://127.0.0.1:8443 and read the API key
from HELIOSDB_API_KEY. The SQL endpoint takes one statement per request in the
query field.
Authentication
# Authorization headercurl -s -X POST http://127.0.0.1:8443/api/v1/query \ -H "Authorization: Bearer $HELIOSDB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "SELECT 1"}'
# X-API-Key headercurl -s -X POST http://127.0.0.1:8443/api/v1/query \ -H "X-API-Key: $HELIOSDB_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "SELECT 1"}'Without a valid key the gateway answers 401 with {"error":"unauthorized"}.
A small Python client
import osimport requests
BASE = os.environ.get("HELIOSDB_HTTP_URL", "http://127.0.0.1:8443")HEADERS = {"Authorization": f"Bearer {os.environ['HELIOSDB_API_KEY']}"}
class HeliosError(RuntimeError): pass
def query(sql: str, session: str | None = None) -> dict: headers = dict(HEADERS) if session: headers["X-HeliosDB-Session"] = session r = requests.post(f"{BASE}/api/v1/query", json={"query": sql}, headers=headers, timeout=30) if r.status_code == 429: raise HeliosError(f"rate limited, retry after {r.headers.get('Retry-After')} s") r.raise_for_status() body = r.json() if not body.get("success"): raise HeliosError(body.get("error")) return body
def rows_as_dicts(body: dict) -> list[dict]: names = [c["name"] for c in body["columns"]] return [dict(zip(names, row)) for row in body["rows"]]Create, insert, read, update, delete
query("CREATE TABLE IF NOT EXISTS users (id INT PRIMARY KEY, name TEXT, active BOOLEAN)")query("INSERT INTO users VALUES (1, 'Alice', true), (2, 'Bob', false)")
active = rows_as_dicts(query("SELECT id, name FROM users WHERE active ORDER BY id"))print(active) # [{'id': 1, 'name': 'Alice'}]
print(query("UPDATE users SET active = true WHERE id = 2")["row_count"]) # 1print(query("DELETE FROM users WHERE id = 2")["row_count"]) # 1The HTTP API has no bind parameters. Build statements only from values your application has validated (numbers parsed as numbers, strings checked against an allow list or quoted with doubled single quotes), or use a PostgreSQL driver when you need server-side parameter binding.
Transactions with a pinned session
Plain requests run on a pooled handler that is reset after each request, so a
transaction needs a pinned session (server started with --http-sessions):
s = requests.post(f"{BASE}/api/v1/sessions", headers=HEADERS, timeout=30).json()["session"]try: query("BEGIN", s) query("UPDATE accounts SET balance = balance - 100 WHERE id = 1", s) query("UPDATE accounts SET balance = balance + 100 WHERE id = 2", s) query("COMMIT", s)finally: requests.delete(f"{BASE}/api/v1/sessions", headers={**HEADERS, "X-HeliosDB-Session": s}, timeout=30)Ending a session with an open transaction rolls it back.
Pagination
def all_orders(page_size: int = 500): last_id = 0 while True: body = query(f"SELECT id, total FROM orders WHERE id > {int(last_id)} " f"ORDER BY id LIMIT {int(page_size)}") if not body["rows"]: return yield from body["rows"] last_id = body["rows"][-1][0]Keyset pagination (WHERE id > last) stays fast on large tables.
Error handling
| Situation | HTTP status | Body |
|---|---|---|
| SQL error | 200 | {"success": false, "error": "..."} |
| Missing or malformed body | 400 | {"error": "Missing 'query' field", "hint": "..."} |
| No or wrong API key | 401 | {"error": "unauthorized"} |
| Unknown path | 404 | {"error": "Not found", "path": "..."} |
| Rate limit reached | 429 | Retry-After header set |
Session route without --http-sessions | 501 | sessions_disabled |
Health checks
curl -fsS http://127.0.0.1:8443/health # {"status":"healthy","version":"..."}curl -fsS http://127.0.0.1:8443/ready # {"ready":true}# Kubernetes probes (the gateway port must be reachable inside the pod)livenessProbe: httpGet: { path: /health, port: 8443 }readinessProbe: httpGet: { path: /ready, port: 8443 }See the HTTP/REST API reference for every route.