Skip to content

HeliosDB-Lite REST API Tutorial


Table of Contents

  1. Introduction
  2. Setup
  3. Authentication
  4. Branch Operations
  5. Data Operations
  6. SQL Execution
  7. Time-Travel via API
  8. Error Handling
  9. SDK Examples
  10. Rate Limiting

1. Introduction

REST API Overview

HeliosDB-Lite provides a RESTful HTTP API for database operations, enabling you to interact with your database through standard HTTP requests. The API supports:

  • Branch management (create, list, merge, delete)
  • SQL query execution
  • Data CRUD operations
  • Time-travel queries
  • Authentication and authorization

When to Use the API

Use the REST API when you need to:

  • Access HeliosDB from any programming language - HTTP clients are available in all major languages
  • Build web applications - Integrate directly with frontend frameworks
  • Implement microservices - Decouple database logic from application services
  • Automate database operations - Script common tasks using curl or HTTP clients
  • Integrate with external tools - Connect BI tools, data pipelines, or monitoring systems

The REST API is ideal for stateless, request-response patterns. For persistent connections or PostgreSQL compatibility, consider the PostgreSQL wire protocol instead.

Base URL Configuration

The API base URL depends on your deployment:

Development: http://localhost:8080/v1
Production: https://your-domain.com/v1
Custom: http://<host>:<port>/v1

All endpoints are versioned under /v1 to ensure backward compatibility.


2. Setup

Starting the API Server

heliosdb-lite start serves the REST API on the HTTP port (default 8080) next to the PostgreSQL port (default 5432). Export the token-signing secret first; the server refuses to start without it:

Terminal window
export HELIOSDB_JWT_SECRET="$(openssl rand -hex 32)"
# Persistent data, loopback only (default)
heliosdb-lite start --data-dir ./mydb
# Custom HTTP port
heliosdb-lite start --data-dir ./mydb --http-port 9090

Configuration Options

Environment Variables

VariablePurpose
HELIOSDB_JWT_SECRETRequired. Secret that signs and verifies tokens
HELIOSDB_CORS_ALLOWED_ORIGINSComma-separated browser origins allowed to call the API
HELIOSDB_METRICS_KEYUnlocks /metrics and the detailed /health body

Rate Limiting

Built-in rate limiting for the REST API is planned. Until it is available, put the API behind a reverse proxy or API gateway that enforces request limits.

Health Check Endpoints

Verify Server state with built-in health endpoints:

Terminal window
# Health check
curl http://localhost:8080/health
# Response (200): {"status":"ok"}
# Version information
curl http://localhost:8080/version
# Response:
# {
# "name": "HeliosDB-Lite",
# "version": "<build version>",
# "api_version": "v1"
# }

3. Authentication

HeliosDB-Lite authenticates REST requests with JWT Bearer tokens. API-key authentication is planned.

JWT Authentication

Token Structure

JWT tokens contain:

  • Subject (sub): User the request runs as
  • Tenant ID (tenant_id): For multi-tenancy
  • Client ID (client_id): Unique client identifier (UUID)
  • Expiration (exp), not before (nbf), issued at (iat) and token ID (jti)
  • Issuer (iss) heliosdb-api and audience (aud) heliosdb-client
  • Scopes: Permissions array

Generating JWT Tokens

Sign tokens with HS256 and the server’s HELIOSDB_JWT_SECRET, for example with PyJWT:

import os, time, uuid
import jwt # pip install PyJWT
def generate_token(user="app_rw", tenant="default", ttl=3600):
now = int(time.time())
return jwt.encode(
{
"sub": user, "tenant_id": tenant, "client_id": str(uuid.uuid4()),
"iat": now, "nbf": now, "exp": now + ttl, "jti": str(uuid.uuid4()),
"iss": "heliosdb-api", "aud": "heliosdb-client", "scopes": [],
},
os.environ["HELIOSDB_JWT_SECRET"],
algorithm="HS256",
)

See the JWT Authentication Quick Reference.

Using JWT in Requests

Include the JWT token in the Authorization header:

Terminal window
curl -H "Authorization: Bearer <your-jwt-token>" \
http://localhost:8080/v1/branches

Token Scopes

Scopes are free-form strings carried in the token. The admin scope lets a caller cancel queries started by other users.

Authentication Errors

{
"status": 401,
"error": "Unauthorized",
"message": "Missing or invalid authentication credentials"
}

Common authentication errors:

  • 401 Unauthorized: Missing or invalid credentials
  • 403 Forbidden: Insufficient permissions (valid auth, wrong scopes)

4. Branch Operations

Branch operations allow you to create, manage, and merge isolated database environments.

List Branches

Retrieve all branches in the database.

Endpoint: GET /v1/branches

Request:

Terminal window
curl -H "Authorization: Bearer <token>" \
http://localhost:8080/v1/branches

Response (200 OK):

{
"total": 3,
"branches": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "main",
"parent": null,
"created_at": 1701446400,
"snapshot_id": 1701446400,
"state": "Active",
"stats": {
"total_keys": 1250,
"size_bytes": 524288,
"last_modified": 1701446400
}
},
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"name": "development",
"parent": "main",
"created_at": 1701450000,
"snapshot_id": 1701450000,
"state": "Active",
"stats": {
"total_keys": 1275,
"size_bytes": 535552,
"last_modified": 1701452000
}
}
]
}

Create Branch

Create a new branch from an existing branch or snapshot.

Endpoint: POST /v1/branches

Request Body:

{
"name": "feature-user-auth",
"parent": "main",
"snapshot_id": 1701446400,
"options": {
"read_only": false,
"isolated": true
}
}

Request:

Terminal window
curl -X POST \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"name": "feature-user-auth",
"parent": "main"
}' \
http://localhost:8080/v1/branches

Response (201 Created):

{
"id": "770e8400-e29b-41d4-a716-446655440002",
"name": "feature-user-auth",
"parent": "main",
"created_at": 1701455000,
"snapshot_id": 1701446400,
"state": "Active",
"stats": {
"total_keys": 1250,
"size_bytes": 524288,
"last_modified": 1701455000
}
}

Parameters:

  • name (required): Branch name (alphanumeric, hyphens, underscores)
  • parent (optional): Parent branch name (defaults to “main”)
  • snapshot_id (optional): Timestamp to branch from (defaults to current)
  • options (optional): Branch configuration

Get Branch Details

Retrieve detailed information about a specific branch.

Endpoint: GET /v1/branches/{name}

Request:

Terminal window
curl -H "Authorization: Bearer <token>" \
http://localhost:8080/v1/branches/feature-user-auth

Response (200 OK):

{
"id": "770e8400-e29b-41d4-a716-446655440002",
"name": "feature-user-auth",
"parent": "main",
"created_at": 1701455000,
"snapshot_id": 1701446400,
"state": "Active",
"stats": {
"total_keys": 1280,
"size_bytes": 540672,
"last_modified": 1701460000
}
}

Errors:

  • 404 Not Found: Branch does not exist

Delete Branch

Delete a branch. Cannot delete the main branch or branches with children.

Endpoint: DELETE /v1/branches/{name}

Request:

Terminal window
curl -X DELETE \
-H "Authorization: Bearer <token>" \
http://localhost:8080/v1/branches/feature-user-auth

Response (204 No Content): Empty body with status 204

Errors:

  • 400 Bad Request: Cannot delete main branch or branch has children
  • 404 Not Found: Branch does not exist

Merge Branches

Merge changes from one branch into another.

Endpoint: POST /v1/branches/{source}/merge

Request Body:

{
"target": "main",
"strategy": "Auto"
}

Merge Strategies:

  • Auto: Automatically resolve conflicts (prefer newer values)
  • Ours: Always keep target branch values
  • Theirs: Always use source branch values
  • Manual: Fail on conflicts (require manual resolution)

Request:

Terminal window
curl -X POST \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"target": "main",
"strategy": "Auto"
}' \
http://localhost:8080/v1/branches/feature-user-auth/merge

Response (200 OK):

{
"merge_timestamp": 1701465000,
"merged_keys": 30,
"conflicts": [],
"completed": true,
"message": "Successfully merged 30 keys from 'feature-user-auth' into 'main' with no conflicts"
}

Response with Conflicts (200 OK):

{
"merge_timestamp": 1701465000,
"merged_keys": 28,
"conflicts": [
{
"key": "users:123",
"source_value": "Alice Smith",
"target_value": "Alice Johnson",
"resolution": "Auto",
"resolved_value": "Alice Smith"
}
],
"completed": true,
"message": "Successfully merged 28 keys from 'feature-user-auth' into 'main' with 2 conflicts resolved"
}

Errors:

  • 404 Not Found: Source or target branch not found
  • 409 Conflict: Unresolved conflicts (when strategy is “Manual”)
  • 422 Unprocessable Entity: Invalid merge state

5. Data Operations

Data operations provide high-level CRUD functionality for working with table data.

List Tables

List all tables in a branch.

Endpoint: GET /v1/branches/{name}/tables

Request:

Terminal window
curl -H "Authorization: Bearer <token>" \
http://localhost:8080/v1/branches/main/tables

Response (200 OK):

{
"tables": [
{
"name": "users",
"row_count": 1500,
"size_bytes": 245760,
"created_at": 1701440000
},
{
"name": "orders",
"row_count": 5200,
"size_bytes": 983040,
"created_at": 1701441000
}
]
}

Query Data

Retrieve data from a table with filtering, pagination, and column selection.

Endpoint: GET /v1/branches/{name}/tables/{table}/data

Query Parameters:

  • filter: WHERE clause condition (e.g., age > 25)
  • columns: Comma-separated column names (default: all)
  • page: Page number, 1-based (default: 1)
  • limit: Results per page (default: 100, max: 1000)
  • order_by: ORDER BY clause (e.g., created_at DESC)
  • as_of: Time-travel timestamp

Request - All Data:

Terminal window
curl -H "Authorization: Bearer <token>" \
"http://localhost:8080/v1/branches/main/tables/users/data"

Request - With Filter and Pagination:

Terminal window
curl -H "Authorization: Bearer <token>" \
"http://localhost:8080/v1/branches/main/tables/users/data?filter=age>25&page=1&limit=10"

Request - Specific Columns:

Terminal window
curl -H "Authorization: Bearer <token>" \
"http://localhost:8080/v1/branches/main/tables/users/data?columns=id,name,email"

Request - With Sorting:

Terminal window
curl -H "Authorization: Bearer <token>" \
"http://localhost:8080/v1/branches/main/tables/users/data?order_by=created_at+DESC&limit=20"

Response (200 OK):

{
"columns": ["id", "name", "email", "age"],
"rows": [
{
"id": 1,
"name": "Alice",
"email": "alice@example.com",
"age": 30
},
{
"id": 2,
"name": "Bob",
"email": "bob@example.com",
"age": 28
}
],
"total": 150,
"page": 1,
"limit": 10,
"has_more": true
}

Insert Data

Insert new rows into a table.

Endpoint: POST /v1/branches/{name}/tables/{table}/data

Request Body:

{
"rows": [
{
"id": 100,
"name": "Charlie",
"email": "charlie@example.com",
"age": 35
},
{
"id": 101,
"name": "Diana",
"email": "diana@example.com",
"age": 27
}
]
}

Request:

Terminal window
curl -X POST \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"rows": [
{
"id": 100,
"name": "Charlie",
"email": "charlie@example.com"
}
]
}' \
http://localhost:8080/v1/branches/main/tables/users/data

Response (201 Created):

{
"inserted": 1,
"execution_time_ms": 12
}

Update Data

Update existing rows in a table.

Endpoint: PUT /v1/branches/{name}/tables/{table}/data

Request Body:

{
"values": {
"email": "newemail@example.com",
"updated_at": 1701470000
},
"filter": "id = 100"
}

Request:

Terminal window
curl -X PUT \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"values": {"email": "newemail@example.com"},
"filter": "id = 100"
}' \
http://localhost:8080/v1/branches/main/tables/users/data

Response (200 OK):

{
"updated": 1,
"execution_time_ms": 8
}

Warning: Always include a filter to avoid updating all rows.

Delete Data

Delete rows from a table.

Endpoint: DELETE /v1/branches/{name}/tables/{table}/data

Request Body:

{
"filter": "id = 100"
}

Request:

Terminal window
curl -X DELETE \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"filter": "id = 100"}' \
http://localhost:8080/v1/branches/main/tables/users/data

Response (200 OK):

{
"deleted": 1,
"execution_time_ms": 10
}

Warning: Always include a filter to avoid deleting all rows.


6. SQL Execution

Execute raw SQL queries and statements for maximum flexibility.

Execute Query (SELECT)

Execute read-only SELECT queries.

Endpoint: POST /v1/branches/{name}/query

Request Body:

{
"sql": "SELECT * FROM users WHERE age > $1 ORDER BY created_at DESC",
"params": [
{
"type": "int4",
"value": 25
}
],
"timeout_ms": 5000
}

Parameter Types:

  • int4: 32-bit integer
  • int8: 64-bit integer
  • text: String
  • bool: Boolean
  • float4: 32-bit float
  • float8: 64-bit float
  • timestamp: Unix timestamp

Request:

Terminal window
curl -X POST \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"sql": "SELECT id, name, email FROM users WHERE age > $1",
"params": [{"type": "int4", "value": 25}]
}' \
http://localhost:8080/v1/branches/main/query

Response (200 OK):

{
"columns": ["id", "name", "email"],
"column_types": ["int4", "text", "text"],
"rows": [
{
"id": 1,
"name": "Alice",
"email": "alice@example.com"
},
{
"id": 3,
"name": "Charlie",
"email": "charlie@example.com"
}
],
"row_count": 2,
"execution_time_ms": 42
}

Execute Statement (DDL/DML)

Execute data modification statements (INSERT, UPDATE, DELETE, CREATE TABLE, etc.).

Endpoint: POST /v1/branches/{name}/execute

Request Body:

{
"sql": "INSERT INTO users (id, name, email) VALUES ($1, $2, $3)",
"params": [
{"type": "int4", "value": 200},
{"type": "text", "value": "Eve"},
{"type": "text", "value": "eve@example.com"}
],
"timeout_ms": 5000
}

Request - INSERT:

Terminal window
curl -X POST \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"sql": "INSERT INTO users (id, name, email) VALUES ($1, $2, $3)",
"params": [
{"type": "int4", "value": 200},
{"type": "text", "value": "Eve"},
{"type": "text", "value": "eve@example.com"}
]
}' \
http://localhost:8080/v1/branches/main/execute

Response (200 OK):

{
"statement_type": "INSERT",
"affected_rows": 1,
"execution_time_ms": 23,
"message": "INSERT statement executed successfully on branch 'main'"
}

Request - CREATE TABLE:

Terminal window
curl -X POST \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"sql": "CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, price REAL)"
}' \
http://localhost:8080/v1/branches/main/execute

Response (200 OK):

{
"statement_type": "CREATE_TABLE",
"affected_rows": 0,
"execution_time_ms": 15,
"message": "CREATE_TABLE statement executed successfully on branch 'main'"
}

Parameterized Queries

Always use parameterized queries to prevent SQL injection:

Bad - SQL Injection Vulnerable:

{
"sql": "SELECT * FROM users WHERE name = '" + userInput + "'"
}

Good - Safe Parameterized Query:

{
"sql": "SELECT * FROM users WHERE name = $1",
"params": [{"type": "text", "value": "Alice"}]
}

Query Timeout

Set timeout to prevent long-running queries:

{
"sql": "SELECT * FROM large_table",
"timeout_ms": 10000
}

Default timeout: 30000ms (30 seconds)

Timeout Error (408 Request Timeout):

{
"error": "QueryTimeout",
"message": "Query exceeded timeout of 10000ms"
}

7. Time-Travel via API

HeliosDB-Lite’s time-travel feature allows querying historical data using timestamps.

Time-Travel in Query Endpoint

Request Body:

{
"sql": "SELECT * FROM users WHERE id = $1",
"params": [{"type": "int4", "value": 1}],
"as_of": {
"type": "timestamp",
"value": 1701446400
}
}

Request:

Terminal window
curl -X POST \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"sql": "SELECT * FROM users WHERE id = $1",
"params": [{"type": "int4", "value": 1}],
"as_of": {"type": "timestamp", "value": 1701446400}
}' \
http://localhost:8080/v1/branches/main/query

Time-Travel in Data Query

Use the as_of query parameter:

Terminal window
curl -H "Authorization: Bearer <token>" \
"http://localhost:8080/v1/branches/main/tables/users/data?as_of=1701446400"

SQL AS OF Syntax

You can also use SQL’s AS OF clause:

{
"sql": "SELECT * FROM users AS OF SYSTEM TIME 1701446400 WHERE age > 25"
}

Request:

Terminal window
curl -X POST \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"sql": "SELECT * FROM users AS OF SYSTEM TIME 1701446400"
}' \
http://localhost:8080/v1/branches/main/query

Historical Data Access

Example: Compare data at two points in time

Terminal window
# Current data
curl -X POST \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"sql": "SELECT COUNT(*) as total FROM users"}' \
http://localhost:8080/v1/branches/main/query
# Historical data (1 hour ago)
TIMESTAMP=$(date -d '1 hour ago' +%s)
curl -X POST \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d "{\"sql\": \"SELECT COUNT(*) as total FROM users\", \"as_of\": {\"type\": \"timestamp\", \"value\": $TIMESTAMP}}" \
http://localhost:8080/v1/branches/main/query

Use Cases for Time-Travel

  1. Audit and Compliance: Review data changes over time
  2. Debugging: Investigate issues by viewing past states
  3. Recovery: Retrieve accidentally deleted or modified data
  4. Reporting: Generate reports for specific time periods
  5. Comparison: Analyze data evolution and trends

8. Error Handling

Error Response Format

All errors follow a consistent JSON structure:

{
"error": "ErrorType",
"message": "Human-readable error description",
"details": "Optional additional information"
}

HTTP Response Codes

ResultError TypeDescription
400BadRequestInvalid request syntax or parameters
401UnauthorizedMissing or invalid authentication
403ForbiddenInsufficient permissions
404NotFoundResource does not exist
408QueryTimeoutQuery exceeded timeout limit
409ConflictResource conflict (e.g., branch exists, merge conflict)
422UnprocessableEntityValid syntax but semantic error
429TooManyRequestsRate limit exceeded
500InternalServerErrorServer error
501NotImplementedFeature not enabled

Common Error Examples

400 Bad Request

{
"error": "BadRequest",
"message": "Invalid SQL syntax: unexpected token 'FROM'"
}

401 Unauthorized

{
"error": "Unauthorized",
"message": "Missing or invalid authentication credentials"
}

404 Not Found

{
"error": "NotFound",
"message": "Branch 'feature-xyz' not found"
}

408 Request Timeout

{
"error": "QueryTimeout",
"message": "Query exceeded timeout of 5000ms"
}

409 Conflict

{
"error": "Conflict",
"message": "Branch 'development' already exists"
}

429 Too Many Requests

{
"error": "TooManyRequests",
"message": "Rate limit exceeded"
}

Retry Strategies

Exponential Backoff

import time
import requests
def retry_with_backoff(url, max_retries=5):
for attempt in range(max_retries):
try:
response = requests.get(url)
if response.status_code == 429:
# Rate limited - use Retry-After header
retry_after = int(response.headers.get('Retry-After', 1))
time.sleep(retry_after)
continue
if response.status_code >= 500:
# Server error - exponential backoff
wait_time = min(2 ** attempt, 60)
time.sleep(wait_time)
continue
return response
except requests.RequestException as e:
if attempt == max_retries - 1:
raise
time.sleep(2 ** attempt)
raise Exception("Max retries exceeded")

Rate Limit Handling

When you receive a 429 response:

Terminal window
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1701446460
Retry-After: 60

Wait for the time specified in Retry-After header before retrying.


9. SDK Examples

curl Examples

List Branches

Terminal window
curl -H "Authorization: Bearer ${TOKEN}" \
http://localhost:8080/v1/branches

Create Branch

Terminal window
curl -X POST \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{"name": "dev", "parent": "main"}' \
http://localhost:8080/v1/branches

Execute Query

Terminal window
curl -X POST \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"sql": "SELECT * FROM users LIMIT 10"
}' \
http://localhost:8080/v1/branches/main/query

Python requests Examples

import requests
import json
class HeliosDBClient:
def __init__(self, base_url, token):
self.base_url = base_url
self.headers = {
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json'
}
def list_branches(self):
"""List all branches"""
response = requests.get(
f"{self.base_url}/v1/branches",
headers=self.headers
)
response.raise_for_status()
return response.json()
def create_branch(self, name, parent='main'):
"""Create a new branch"""
response = requests.post(
f"{self.base_url}/v1/branches",
headers=self.headers,
json={'name': name, 'parent': parent}
)
response.raise_for_status()
return response.json()
def execute_query(self, branch, sql, params=None):
"""Execute a SQL query"""
payload = {'sql': sql}
if params:
payload['params'] = params
response = requests.post(
f"{self.base_url}/v1/branches/{branch}/query",
headers=self.headers,
json=payload
)
response.raise_for_status()
return response.json()
def insert_data(self, branch, table, rows):
"""Insert data into a table"""
response = requests.post(
f"{self.base_url}/v1/branches/{branch}/tables/{table}/data",
headers=self.headers,
json={'rows': rows}
)
response.raise_for_status()
return response.json()
# Usage
# generate_token() is defined in section 3 (Authentication)
client = HeliosDBClient('http://localhost:8080', generate_token())
# List branches
branches = client.list_branches()
print(f"Found {branches['total']} branches")
# Create branch
new_branch = client.create_branch('feature-xyz')
print(f"Created branch: {new_branch['name']}")
# Execute query
results = client.execute_query(
'main',
'SELECT * FROM users WHERE age > $1',
params=[{'type': 'int4', 'value': 25}]
)
print(f"Query returned {results['row_count']} rows")
# Insert data
insert_result = client.insert_data(
'main',
'users',
[{'id': 500, 'name': 'Alice', 'email': 'alice@example.com'}]
)
print(f"Inserted {insert_result['inserted']} rows")

JavaScript fetch Examples

class HeliosDBClient {
constructor(baseUrl, token) {
this.baseUrl = baseUrl;
this.headers = {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
};
}
async listBranches() {
const response = await fetch(`${this.baseUrl}/v1/branches`, {
headers: this.headers
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
}
async createBranch(name, parent = 'main') {
const response = await fetch(`${this.baseUrl}/v1/branches`, {
method: 'POST',
headers: this.headers,
body: JSON.stringify({ name, parent })
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
}
async executeQuery(branch, sql, params = null) {
const payload = { sql };
if (params) {
payload.params = params;
}
const response = await fetch(
`${this.baseUrl}/v1/branches/${branch}/query`,
{
method: 'POST',
headers: this.headers,
body: JSON.stringify(payload)
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
}
async insertData(branch, table, rows) {
const response = await fetch(
`${this.baseUrl}/v1/branches/${branch}/tables/${table}/data`,
{
method: 'POST',
headers: this.headers,
body: JSON.stringify({ rows })
}
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
}
}
// Usage
const client = new HeliosDBClient('http://localhost:8080', process.env.HELIOSDB_TOKEN);
// List branches
const branches = await client.listBranches();
console.log(`Found ${branches.total} branches`);
// Create branch
const newBranch = await client.createBranch('feature-xyz');
console.log(`Created branch: ${newBranch.name}`);
// Execute query
const results = await client.executeQuery(
'main',
'SELECT * FROM users WHERE age > $1',
[{ type: 'int4', value: 25 }]
);
console.log(`Query returned ${results.row_count} rows`);
// Insert data
const insertResult = await client.insertData(
'main',
'users',
[{ id: 500, name: 'Alice', email: 'alice@example.com' }]
);
console.log(`Inserted ${insertResult.inserted} rows`);

Go net/http Example

package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
type HeliosDBClient struct {
BaseURL string
Token string
Client *http.Client
}
func NewClient(baseURL, token string) *HeliosDBClient {
return &HeliosDBClient{
BaseURL: baseURL,
Token: token,
Client: &http.Client{},
}
}
func (c *HeliosDBClient) request(method, path string, body interface{}) (*http.Response, error) {
var bodyReader io.Reader
if body != nil {
jsonBody, err := json.Marshal(body)
if err != nil {
return nil, err
}
bodyReader = bytes.NewBuffer(jsonBody)
}
req, err := http.NewRequest(method, c.BaseURL+path, bodyReader)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+c.Token)
req.Header.Set("Content-Type", "application/json")
return c.Client.Do(req)
}
func (c *HeliosDBClient) ListBranches() (map[string]interface{}, error) {
resp, err := c.request("GET", "/v1/branches", nil)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
return result, nil
}
func (c *HeliosDBClient) ExecuteQuery(branch, sql string) (map[string]interface{}, error) {
payload := map[string]interface{}{
"sql": sql,
}
resp, err := c.request("POST", "/v1/branches/"+branch+"/query", payload)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
return result, nil
}
func main() {
client := NewClient("http://localhost:8080", os.Getenv("HELIOSDB_TOKEN"))
// List branches
branches, err := client.ListBranches()
if err != nil {
panic(err)
}
fmt.Printf("Found %v branches\n", branches["total"])
// Execute query
results, err := client.ExecuteQuery("main", "SELECT * FROM users LIMIT 10")
if err != nil {
panic(err)
}
fmt.Printf("Query returned %v rows\n", results["row_count"])
}

10. Rate Limiting

Built-in rate limiting for the REST API is planned. Until it is available, enforce request limits in a reverse proxy or API gateway in front of the server, and handle any 429 Too Many Requests it returns with exponential backoff (see Retry Strategies).


Appendix

Complete Example

This example demonstrates a complete workflow using the REST API:

import requests
import time
class HeliosDBWorkflow:
def __init__(self, base_url, token):
self.base_url = base_url
self.headers = {
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json'
}
def run(self):
print("=== HeliosDB-Lite REST API Workflow ===\n")
# 1. Create a feature branch
print("1. Creating feature branch...")
branch = self._create_branch('feature-demo')
print(f" Created: {branch['name']}\n")
# 2. Create a table
print("2. Creating products table...")
self._execute_statement(
'feature-demo',
'''CREATE TABLE products (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
price REAL,
created_at INTEGER
)'''
)
print(" Table created\n")
# 3. Insert data
print("3. Inserting sample data...")
result = self._insert_data('feature-demo', 'products', [
{'id': 1, 'name': 'Widget', 'price': 9.99, 'created_at': int(time.time())},
{'id': 2, 'name': 'Gadget', 'price': 19.99, 'created_at': int(time.time())},
{'id': 3, 'name': 'Doohickey', 'price': 14.99, 'created_at': int(time.time())},
])
print(f" Inserted {result['inserted']} rows\n")
# 4. Query data
print("4. Querying data...")
results = self._execute_query(
'feature-demo',
'SELECT * FROM products WHERE price > $1 ORDER BY price DESC',
[{'type': 'float8', 'value': 10.0}]
)
print(f" Found {results['row_count']} products over $10:")
for row in results['rows']:
print(f" - {row['name']}: ${row['price']}")
print()
# 5. Merge to main
print("5. Merging feature-demo into main...")
merge_result = self._merge_branch('feature-demo', 'main')
print(f" {merge_result['message']}\n")
# 6. Verify in main branch
print("6. Verifying data in main branch...")
results = self._execute_query('main', 'SELECT COUNT(*) as total FROM products')
print(f" Total products in main: {results['rows'][0]['total']}\n")
# 7. Cleanup
print("7. Cleaning up feature branch...")
self._delete_branch('feature-demo')
print(" Branch deleted\n")
print("=== Workflow Complete ===")
def _create_branch(self, name):
response = requests.post(
f"{self.base_url}/v1/branches",
headers=self.headers,
json={'name': name, 'parent': 'main'}
)
response.raise_for_status()
return response.json()
def _execute_statement(self, branch, sql):
response = requests.post(
f"{self.base_url}/v1/branches/{branch}/execute",
headers=self.headers,
json={'sql': sql}
)
response.raise_for_status()
return response.json()
def _insert_data(self, branch, table, rows):
response = requests.post(
f"{self.base_url}/v1/branches/{branch}/tables/{table}/data",
headers=self.headers,
json={'rows': rows}
)
response.raise_for_status()
return response.json()
def _execute_query(self, branch, sql, params=None):
payload = {'sql': sql}
if params:
payload['params'] = params
response = requests.post(
f"{self.base_url}/v1/branches/{branch}/query",
headers=self.headers,
json=payload
)
response.raise_for_status()
return response.json()
def _merge_branch(self, source, target):
response = requests.post(
f"{self.base_url}/v1/branches/{source}/merge",
headers=self.headers,
json={'target': target, 'strategy': 'Auto'}
)
response.raise_for_status()
return response.json()
def _delete_branch(self, name):
response = requests.delete(
f"{self.base_url}/v1/branches/{name}",
headers=self.headers
)
response.raise_for_status()
# Run the workflow
if __name__ == '__main__':
# generate_token() is defined in section 3 (Authentication)
workflow = HeliosDBWorkflow('http://localhost:8080', generate_token())
workflow.run()

Quick Reference

Health & Version

Terminal window
GET /health # Health check
GET /version # Version info

Branches

Terminal window
GET /v1/branches # List branches
POST /v1/branches # Create branch
GET /v1/branches/{name} # Get branch
DELETE /v1/branches/{name} # Delete branch
POST /v1/branches/{name}/merge # Merge branch

SQL Execution

Terminal window
POST /v1/branches/{name}/query # Execute query (SELECT)
POST /v1/branches/{name}/execute # Execute statement (DDL/DML)

Data Operations

Terminal window
GET /v1/branches/{name}/tables # List tables
GET /v1/branches/{name}/tables/{table}/data # Query data
POST /v1/branches/{name}/tables/{table}/data # Insert data
PUT /v1/branches/{name}/tables/{table}/data # Update data
DELETE /v1/branches/{name}/tables/{table}/data # Delete data

Conclusion

This tutorial covered the complete HeliosDB-Lite REST API, including:

  • Server setup and configuration
  • Authentication with JWT bearer tokens
  • Branch management operations
  • Data CRUD operations
  • SQL query execution
  • Time-travel queries
  • Error handling and retry strategies
  • SDK examples in multiple languages

For more information:

Next Steps:

  1. Set up a development server
  2. Generate authentication credentials
  3. Try the example workflows
  4. Integrate with your application
  5. Implement proper error handling and retries