HeliosDB-Lite REST API Tutorial
Table of Contents
- Introduction
- Setup
- Authentication
- Branch Operations
- Data Operations
- SQL Execution
- Time-Travel via API
- Error Handling
- SDK Examples
- 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/v1Production: https://your-domain.com/v1Custom: http://<host>:<port>/v1All 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:
export HELIOSDB_JWT_SECRET="$(openssl rand -hex 32)"
# Persistent data, loopback only (default)heliosdb-lite start --data-dir ./mydb
# Custom HTTP portheliosdb-lite start --data-dir ./mydb --http-port 9090Configuration Options
Environment Variables
| Variable | Purpose |
|---|---|
HELIOSDB_JWT_SECRET | Required. Secret that signs and verifies tokens |
HELIOSDB_CORS_ALLOWED_ORIGINS | Comma-separated browser origins allowed to call the API |
HELIOSDB_METRICS_KEY | Unlocks /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:
# Health checkcurl http://localhost:8080/health
# Response (200): {"status":"ok"}
# Version informationcurl 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-apiand 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, uuidimport 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:
curl -H "Authorization: Bearer <your-jwt-token>" \ http://localhost:8080/v1/branchesToken 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:
curl -H "Authorization: Bearer <token>" \ http://localhost:8080/v1/branchesResponse (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:
curl -X POST \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "name": "feature-user-auth", "parent": "main" }' \ http://localhost:8080/v1/branchesResponse (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:
curl -H "Authorization: Bearer <token>" \ http://localhost:8080/v1/branches/feature-user-authResponse (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:
curl -X DELETE \ -H "Authorization: Bearer <token>" \ http://localhost:8080/v1/branches/feature-user-authResponse (204 No Content): Empty body with status 204
Errors:
400 Bad Request: Cannot delete main branch or branch has children404 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 valuesTheirs: Always use source branch valuesManual: Fail on conflicts (require manual resolution)
Request:
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/mergeResponse (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 found409 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:
curl -H "Authorization: Bearer <token>" \ http://localhost:8080/v1/branches/main/tablesResponse (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:
curl -H "Authorization: Bearer <token>" \ "http://localhost:8080/v1/branches/main/tables/users/data"Request - With Filter and Pagination:
curl -H "Authorization: Bearer <token>" \ "http://localhost:8080/v1/branches/main/tables/users/data?filter=age>25&page=1&limit=10"Request - Specific Columns:
curl -H "Authorization: Bearer <token>" \ "http://localhost:8080/v1/branches/main/tables/users/data?columns=id,name,email"Request - With Sorting:
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:
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/dataResponse (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:
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/dataResponse (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:
curl -X DELETE \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{"filter": "id = 100"}' \ http://localhost:8080/v1/branches/main/tables/users/dataResponse (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 integerint8: 64-bit integertext: Stringbool: Booleanfloat4: 32-bit floatfloat8: 64-bit floattimestamp: Unix timestamp
Request:
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/queryResponse (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:
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/executeResponse (200 OK):
{ "statement_type": "INSERT", "affected_rows": 1, "execution_time_ms": 23, "message": "INSERT statement executed successfully on branch 'main'"}Request - CREATE TABLE:
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/executeResponse (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:
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/queryTime-Travel in Data Query
Use the as_of query parameter:
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:
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/queryHistorical Data Access
Example: Compare data at two points in time
# Current datacurl -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/queryUse Cases for Time-Travel
- Audit and Compliance: Review data changes over time
- Debugging: Investigate issues by viewing past states
- Recovery: Retrieve accidentally deleted or modified data
- Reporting: Generate reports for specific time periods
- 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
| Result | Error Type | Description |
|---|---|---|
| 400 | BadRequest | Invalid request syntax or parameters |
| 401 | Unauthorized | Missing or invalid authentication |
| 403 | Forbidden | Insufficient permissions |
| 404 | NotFound | Resource does not exist |
| 408 | QueryTimeout | Query exceeded timeout limit |
| 409 | Conflict | Resource conflict (e.g., branch exists, merge conflict) |
| 422 | UnprocessableEntity | Valid syntax but semantic error |
| 429 | TooManyRequests | Rate limit exceeded |
| 500 | InternalServerError | Server error |
| 501 | NotImplemented | Feature 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 timeimport 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:
HTTP/1.1 429 Too Many RequestsX-RateLimit-Limit: 100X-RateLimit-Remaining: 0X-RateLimit-Reset: 1701446460Retry-After: 60Wait for the time specified in Retry-After header before retrying.
9. SDK Examples
curl Examples
List Branches
curl -H "Authorization: Bearer ${TOKEN}" \ http://localhost:8080/v1/branchesCreate Branch
curl -X POST \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: application/json" \ -d '{"name": "dev", "parent": "main"}' \ http://localhost:8080/v1/branchesExecute Query
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/queryPython requests Examples
import requestsimport 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 branchesbranches = client.list_branches()print(f"Found {branches['total']} branches")
# Create branchnew_branch = client.create_branch('feature-xyz')print(f"Created branch: {new_branch['name']}")
# Execute queryresults = client.execute_query( 'main', 'SELECT * FROM users WHERE age > $1', params=[{'type': 'int4', 'value': 25}])print(f"Query returned {results['row_count']} rows")
# Insert datainsert_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(); }}
// Usageconst client = new HeliosDBClient('http://localhost:8080', process.env.HELIOSDB_TOKEN);
// List branchesconst branches = await client.listBranches();console.log(`Found ${branches.total} branches`);
// Create branchconst newBranch = await client.createBranch('feature-xyz');console.log(`Created branch: ${newBranch.name}`);
// Execute queryconst results = await client.executeQuery( 'main', 'SELECT * FROM users WHERE age > $1', [{ type: 'int4', value: 25 }]);console.log(`Query returned ${results.row_count} rows`);
// Insert dataconst 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 requestsimport 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 workflowif __name__ == '__main__': # generate_token() is defined in section 3 (Authentication) workflow = HeliosDBWorkflow('http://localhost:8080', generate_token()) workflow.run()Quick Reference
Health & Version
GET /health # Health checkGET /version # Version infoBranches
GET /v1/branches # List branchesPOST /v1/branches # Create branchGET /v1/branches/{name} # Get branchDELETE /v1/branches/{name} # Delete branchPOST /v1/branches/{name}/merge # Merge branchSQL Execution
POST /v1/branches/{name}/query # Execute query (SELECT)POST /v1/branches/{name}/execute # Execute statement (DDL/DML)Data Operations
GET /v1/branches/{name}/tables # List tablesGET /v1/branches/{name}/tables/{table}/data # Query dataPOST /v1/branches/{name}/tables/{table}/data # Insert dataPUT /v1/branches/{name}/tables/{table}/data # Update dataDELETE /v1/branches/{name}/tables/{table}/data # Delete dataConclusion
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:
- Set up a development server
- Generate authentication credentials
- Try the example workflows
- Integrate with your application
- Implement proper error handling and retries