---
title: "HeliosDB HTTP/REST API Reference"
---

# HeliosDB HTTP/REST API Reference

HeliosDB Full includes an HTTP gateway next to its wire-protocol listeners. It runs
SQL over HTTP and returns JSON, so scripts, serverless functions and tools that
cannot load a database driver can still reach the database. The gateway runs inside
the `heliosdb-full` server process; there is nothing extra to install.

This page describes the endpoints the `heliosdb-full` server serves, how to change
the port and bind address, and how requests are authenticated.

The HTTP gateway has no S3/object endpoints (planned): an S3-compatible object API
for HeliosDB Full is on the roadmap and is not part of the current release. The
`/snowflake/`, `/dbsql/` and `/pinecone/` paths answer `501 Not Implemented`; those
interfaces are planned as well.

---

## Default address

| Setting | Default | Change it with |
|---------|---------|----------------|
| Port | **8443** | `--http-port <port>` or `HELIOSDB_HTTP_PORT` |
| Bind address | `127.0.0.1` | `--bind-addr <address>` or `HELIOSDB_BIND_ADDR` |
| Enabled | yes | `--no-http` turns the gateway off |

The listener speaks **plain HTTP** on port 8443; the port number is only the default,
not an indication of TLS. For connections that leave the host, put the gateway behind
a TLS-terminating reverse proxy or load balancer (nginx, HAProxy, Envoy, a cloud load
balancer) and keep the gateway itself on a private address.

```bash
# Local only (default): http://127.0.0.1:8443
heliosdb-full

# Another port
heliosdb-full --http-port 9443
# or
HELIOSDB_HTTP_PORT=9443 heliosdb-full
```

### Binding to a non-loopback address

`--bind-addr` applies to every listener the server opens (PostgreSQL, HTTP and any
protocol you enable). Binding to anything other than a loopback address requires an
explicit opt-in, and the server refuses to start without it:

```bash
heliosdb-full --bind-addr 0.0.0.0 --allow-external
# or
HELIOSDB_BIND_ADDR=0.0.0.0 HELIOSDB_ALLOW_EXTERNAL=true heliosdb-full
```

`--allow-external` accepts `1/0`, `true/false`, `yes/no` and `on/off`. Configure an API
key (below) and a firewall before you expose the gateway.

### Containers

The HeliosDB Full container image (provided during onboarding; it is not on a public
registry) already binds `0.0.0.0` inside the container and sets
`HELIOSDB_ALLOW_EXTERNAL=true`. Publish the port you need:

```bash
docker run -d --name heliosdb \
  --env-file /etc/heliosdb/heliosdb.env \
  -p 127.0.0.1:8443:8443 -p 127.0.0.1:5432:5432 \
  -v heliosdb-data:/var/lib/heliosdb/data \
  <heliosdb-full-image>
```

`/etc/heliosdb/heliosdb.env` holds `HELIOSDB_SUPERUSER_PASSWORD` and
`HELIOSDB_API_KEY` and is readable only by root. To serve HTTP on another port inside
the container, add `-e HELIOSDB_HTTP_PORT=9443` and publish `-p 127.0.0.1:9443:9443`.

---

## Authentication

| Route | Authentication |
|-------|----------------|
| `GET /`, `GET /health`, `GET /healthz`, `GET /ready`, `GET /readyz`, `GET /status`, `GET /api/v1/status` | None (probes and server information) |
| Every other route | API key |

Set the key on the server with the `HELIOSDB_API_KEY` environment variable. Clients
send it in either header:

```
Authorization: Bearer <api-key>
X-API-Key: <api-key>
```

The key is compared in constant time. A request without a valid key receives:

```http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer
Content-Type: application/json

{"error":"unauthorized"}
```

When `HELIOSDB_API_KEY` is not set, every non-public route returns 401. The only way
to call them without a key is `--auth-mode trust`, which the server accepts on a
loopback bind only (it refuses to start with `trust` on an external address). Use it
for local development.

The HTTP gateway has one shared key, not per-user accounts: SQL sent over HTTP is not
tied to a database role, so treat the key like a superuser password. For per-user
logins, connect with a PostgreSQL client and a database role instead.

Keep the key out of shell history and process listings: export it from a secrets
manager or an environment file readable only by the service account, for example
`EnvironmentFile=` in a systemd unit or `--env-file` with Docker.

---

## Endpoints

| Method | Path | Purpose |
|--------|------|---------|
| GET | `/health`, `/healthz` | Liveness |
| GET | `/ready`, `/readyz` | Readiness |
| GET | `/status`, `/api/v1/status` | Server status |
| GET | `/` | Server name, version and route list |
| POST | `/api/v1/query`, `/query` | Run one SQL statement |
| POST, DELETE | `/api/v1/sessions` | Pinned sessions (opt-in, see below) |
| POST | `/api/v1/chat/completions`, `/v1/chat/completions` | Chat completion through a configured AI provider |
| POST | `/api/v1/embeddings`, `/v1/embeddings` | Embeddings through a configured AI provider |
| POST | `/api/v1/audio/transcribe`, `/api/v1/audio/transcriptions`, `/v1/audio/transcriptions` | Speech-to-text through a configured AI provider |
| POST | `/api/v1/vectors/stores` | Create a vector store |
| POST | `/api/v1/vectors/stores/{name}/texts` | Add texts to a vector store |
| POST | `/api/v1/vectors/stores/{name}/search/hybrid` | Hybrid (vector + keyword) search |
| GET, POST | `/api/v1/agents/memory` | List or create agent memory sessions |
| GET, DELETE | `/api/v1/agents/memory/{id}` | Read or delete a memory session |
| GET | `/api/v1/agents/memory/{id}/messages` | Read messages |
| POST | `/api/v1/agents/memory/{id}/messages/batch` | Append messages |
| POST | `/api/v1/agents/memory/{id}/clear`, `/summarize`, `/search` | Clear, summarize or search a session |
| GET, POST | `/api/v1/documents` (also `POST /api/v1/documents/ingest`) | List or ingest documents |
| GET, DELETE | `/api/v1/documents/{id}` | Read or delete a document |
| POST | `/api/v1/documents/search` | Search documents |

Any other path returns `404` with `{"error":"Not found","path":"..."}`.

### Health and status

```bash
curl -s http://127.0.0.1:8443/health
# {"status":"healthy","version":"x.y.z"}

curl -s http://127.0.0.1:8443/ready
# {"ready":true}

curl -s http://127.0.0.1:8443/status
# {"status":"running","version":"x.y.z","protocols":["postgresql","mysql","http"]}
```

`/health` and `/ready` need no key, so you can use them directly as container
health checks and load-balancer probes. The `protocols` list in `/status` is a fixed
summary; the server log and `SHOW PROTOCOLS` (from `psql`) show which listeners are
actually open.

### Run SQL: `POST /api/v1/query`

Send one statement in the `query` field of a JSON body:

```bash
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 id, name FROM customers WHERE id = 1"}'
```

Successful result:

```json
{
  "success": true,
  "columns": [
    {"name": "id", "type": "integer"},
    {"name": "name", "type": "text"}
  ],
  "rows": [[1, "Acme"]],
  "row_count": 1
}
```

Column `type` is one of `integer`, `float`, `boolean` or `text`; other SQL types are
reported as `text` and their values are returned as JSON strings. `NULL` is JSON
`null`. DDL and DML return an empty `columns`/`rows` and the affected row count in
`row_count`.

A SQL error is returned with HTTP status **200** and `success: false`, so check the
`success` field rather than only the status code:

```json
{"success": false, "error": "relation \"no_such_table\" does not exist"}
```

A missing or malformed body returns `400` with a hint:

```json
{"error": "Missing 'query' field", "hint": "Send JSON body: {\"query\": \"SELECT 1\"}"}
```

Notes:

- The body field is `query`. Bind parameters are not part of the HTTP API; build
  statements with values your application has validated, or use a PostgreSQL driver
  when you need server-side parameter binding.
- Each request runs on a pooled PostgreSQL handler that is reset afterwards, so
  `BEGIN`, temporary tables and `SET` do not carry over to the next request. A `BEGIN`
  over plain HTTP is refused. Use a pinned session (next section) for multi-statement
  transactions.
- Responses carry `Access-Control-Allow-Origin: *`.
- The pool size is set with `--http-pg-pool-size` / `HELIOSDB_HTTP_PG_POOL_SIZE`
  (default 16; `0` builds a handler per request).

### Pinned sessions: `/api/v1/sessions`

A pinned session keeps one dedicated PostgreSQL session for an HTTP client across
requests, so transactions, temporary tables and `SET` work over HTTP. Start the server
with `--http-sessions` to enable it; without it these routes return `501`
(`sessions_disabled`).

```bash
# 1. Create a session
curl -s -X POST http://127.0.0.1:8443/api/v1/sessions \
  -H "Authorization: Bearer $HELIOSDB_API_KEY"
# {"session":"<node>.<token>","node":"<node>","expires_in_secs":300,
#  "idle_in_transaction_timeout_secs":60,"max_lifetime_secs":3600,"in_transaction":false}

# 2. Run statements on it
curl -s -X POST http://127.0.0.1:8443/api/v1/query \
  -H "Authorization: Bearer $HELIOSDB_API_KEY" \
  -H "X-HeliosDB-Session: $SESSION" \
  -d '{"query": "BEGIN"}'
# ... more statements with the same header ...
curl -s -X POST http://127.0.0.1:8443/api/v1/query \
  -H "Authorization: Bearer $HELIOSDB_API_KEY" \
  -H "X-HeliosDB-Session: $SESSION" \
  -d '{"query": "COMMIT"}'

# 3. End it (an open transaction is rolled back)
curl -s -X DELETE http://127.0.0.1:8443/api/v1/sessions \
  -H "Authorization: Bearer $HELIOSDB_API_KEY" \
  -H "X-HeliosDB-Session: $SESSION"
```

Responses on a pinned session add `"in_transaction": true|false`. The token travels
only in the `X-HeliosDB-Session` header, never in the URL or body, and the server
stores only its SHA-256 hash.

| Flag (environment variable) | Default | Meaning |
|------|---------|---------|
| `--http-sessions` | off | Enable pinned sessions |
| `--http-max-sessions` (`HELIOSDB_HTTP_MAX_SESSIONS`) | 256 | Sessions per node |
| `--http-max-sessions-per-principal` (`HELIOSDB_HTTP_MAX_SESSIONS_PER_PRINCIPAL`) | 32 | Sessions per API key |
| `--http-session-idle-timeout-secs` (`HELIOSDB_HTTP_SESSION_IDLE_TIMEOUT_SECS`) | 300 | Idle time before the session ends |
| `--http-session-idle-in-txn-timeout-secs` (`HELIOSDB_HTTP_SESSION_IDLE_IN_TXN_TIMEOUT_SECS`) | 60 | Idle time while a transaction is open (work is rolled back) |
| `--http-session-max-lifetime-secs` (`HELIOSDB_HTTP_SESSION_MAX_LIFETIME_SECS`) | 3600 | Maximum session age |
| `--http-session-reap-interval-ms` (`HELIOSDB_HTTP_SESSION_REAP_INTERVAL_MS`) | 1000 | Reaper interval; also the `Retry-After` of a 429 |
| `--http-session-concurrency wait\|reject` (`HELIOSDB_HTTP_SESSION_CONCURRENCY`) | `wait` | A second concurrent request on one token waits, or gets 409 |
| `--http-session-bind-ip` | off | Also bind each session to the client IP that created it |
| `--node-id` (`HELIOSDB_NODE_ID`) | hostname | Node id carried in each token, for load-balancer routing |

Session errors:

| Status | Code | Meaning |
|--------|------|---------|
| 400 | `session_malformed` | The token is not well formed |
| 403 | `session_forbidden` | Token used with another API key, or from another IP with `--http-session-bind-ip` |
| 404 | `session_unknown` | No such session |
| 409 | `session_not_local` | The token belongs to another node (the body names it) |
| 409 | `session_busy` | Another request is running on the token and concurrency is `reject` |
| 410 | `session_expired` | The session timed out |
| 429 | `too_many_sessions` | A session limit was reached (`Retry-After` is set) |
| 500 | `session_lost` | The session ended because of an internal error |
| 501 | `sessions_disabled` | The server was started without `--http-sessions` |

Behind a load balancer, route requests that carry `X-HeliosDB-Session` to the node
named in the token (the part before the first `.`), or use sticky sessions.

### AI routes

The chat, embeddings and audio routes forward requests to an AI provider configured
on the server. The vector store, agent memory and document routes store their data in
HeliosDB and call the provider for embeddings.

| Environment variable | Purpose |
|----------------------|---------|
| `HELIOSDB_AI_PROVIDER` | `openai` (default), `anthropic` or `ollama` |
| `HELIOSDB_AI_API_KEY` | Provider API key |
| `HELIOSDB_AI_API_BASE` | Provider base URL (defaults to the provider's public endpoint) |
| `HELIOSDB_AI_CHAT_MODEL`, `HELIOSDB_AI_EMBED_MODEL` | Model names |
| `HELIOSDB_AI_ALLOW_PRIVATE_NETWORK` | `1` allows an `http://` or private-network base URL, for a local Ollama |
| `HELIOSDB_AI_HOST_ALLOWLIST` | Comma-separated host names allowed as the base URL |
| `HELIOSDB_AI_RATE_LIMIT`, `HELIOSDB_AI_RATE_WINDOW_SECS` | Requests per tenant per window (default 60 per 60 s; `0` disables) |

Requests are counted per tenant, taken from the `X-HeliosDB-Tenant` header (`default`
when absent). Over the limit, the route returns `429` with `Retry-After`. The chat
route accepts the OpenAI-style body (`messages`, optional `model`, `temperature`,
`max_tokens`, `stream`); with `"stream": true` the response is server-sent events.

```bash
curl -s -X POST http://127.0.0.1:8443/api/v1/chat/completions \
  -H "Authorization: Bearer $HELIOSDB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"messages":[{"role":"user","content":"Summarise last week'\''s orders"}]}'
```

### Snowflake, Databricks and Pinecone paths

Requests under `/snowflake/`, `/dbsql/` and `/pinecone/` are answered with `501 Not
Implemented` by the `heliosdb-full` HTTP listener. Use `POST /api/v1/query` or a
PostgreSQL client for SQL access.

---

## Rate limits

| Limit | Default | Setting |
|-------|---------|---------|
| New connections per client IP | 500 per minute (burst 100), all listeners | `--conn-rate-limit <per-minute>` (`HELIOSDB_CONN_RATE_LIMIT`) |
| SQL queries per client IP | not limited | `--query-rate-limit <per-second>` (`HELIOSDB_QUERY_RATE_LIMIT`); applies to `POST /api/v1/query` and the PostgreSQL wire |
| AI requests per tenant | 60 per 60 s | `HELIOSDB_AI_RATE_LIMIT`, `HELIOSDB_AI_RATE_WINDOW_SECS` |
| Concurrent connections, all protocols | 10000 | `--max-connections` (`HELIOSDB_MAX_CONNECTIONS`) |

A query over the limit returns `429 Too Many Requests` with a `Retry-After` header.
Setting the environment variable `HELIOSDB_RATE_LIMITING=false` turns the connection
and query limiters off (not recommended for production). The `--rate-limiting`
command-line flag can only switch them on, which is already the default:
`--rate-limiting false` and `--rate-limiting=false` are rejected by the argument
parser.

---

## Client examples

### Python

```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']}"}

def query(sql: str) -> dict:
    r = requests.post(f"{BASE}/api/v1/query", json={"query": sql}, headers=HEADERS, timeout=30)
    r.raise_for_status()
    body = r.json()
    if not body.get("success"):
        raise RuntimeError(body.get("error"))
    return body

result = query("SELECT COUNT(*) AS n FROM orders")
print(result["rows"][0][0])
```

### JavaScript / TypeScript

```typescript
const BASE = process.env.HELIOSDB_HTTP_URL ?? "http://127.0.0.1:8443";

async function query(sql: string) {
  const res = await fetch(`${BASE}/api/v1/query`, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.HELIOSDB_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ query: sql }),
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  const body = await res.json();
  if (!body.success) throw new Error(body.error);
  return body;
}
```

### Shell health check

```bash
curl -fsS http://127.0.0.1:8443/health > /dev/null || echo "HeliosDB is down"
```

---

## Related

- [Connecting to HeliosDB](/docs/full/guides/user/connecting/) — PostgreSQL, MySQL and
  other wire-protocol clients
- [Configuration reference](/docs/full/reference/configuration/)
- Run `heliosdb-full --help` for every flag and its environment variable, and
  `heliosdb-full --show-features` for the listeners and features in your build
