---
title: "HTTP/REST API Examples"
description: "HTTP/REST examples for HeliosDB Full: each targets the default gateway address on port 8443 and reads the API key from HELIOSDB_API_KEY."
---

# 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

```bash
# Authorization header
curl -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 header
curl -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

```python
import os
import 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

```python
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"])   # 1
print(query("DELETE FROM users WHERE id = 2")["row_count"])                # 1
```

The 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`):

```python
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

```python
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

```bash
curl -fsS http://127.0.0.1:8443/health   # {"status":"healthy","version":"..."}
curl -fsS http://127.0.0.1:8443/ready    # {"ready":true}
```

```yaml
# 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](/docs/full/reference/api/http_rest_api/) for every
route.
