HeliosDB-Lite REST API Tutorial¶
Version: 1.0.0 Last Updated: 2025-12-01 Status: Complete
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 comprehensive 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
- Rate limiting
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¶
Basic Server Setup¶
use heliosdb_lite::{EmbeddedDatabase, api::ApiServer};
use std::sync::Arc;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create database instance
let db = Arc::new(EmbeddedDatabase::new("./data/mydb.db")?);
// Configure server address
let addr = "127.0.0.1:8080".parse()?;
// Create and start API server
let server = ApiServer::new(addr, db);
server.serve().await?;
Ok(())
}
Server with Authentication¶
use heliosdb_lite::{
EmbeddedDatabase,
api::{ApiServer, AuthMiddleware},
};
use std::sync::Arc;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let db = Arc::new(EmbeddedDatabase::new("./data/mydb.db")?);
let addr = "127.0.0.1:8080".parse()?;
// Configure authentication
let auth = AuthMiddleware::from_env_or_default();
let server = ApiServer::new(addr, db)
.with_auth(auth);
server.serve().await?;
Ok(())
}
Server with Rate Limiting¶
use heliosdb_lite::{
EmbeddedDatabase,
api::{ApiServer, RateLimitMiddleware, RateLimitConfig},
};
use std::sync::Arc;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let db = Arc::new(EmbeddedDatabase::new("./data/mydb.db")?);
let addr = "127.0.0.1:8080".parse()?;
// Configure rate limiting: 100 requests per minute
let rate_limiter = RateLimitMiddleware::new(
RateLimitConfig::authenticated()
);
let server = ApiServer::new(addr, db)
.with_rate_limiting(rate_limiter);
server.serve().await?;
Ok(())
}
Server with Graceful Shutdown¶
use heliosdb_lite::{EmbeddedDatabase, api::ApiServer};
use std::sync::Arc;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let db = Arc::new(EmbeddedDatabase::new("./data/mydb.db")?);
let addr = "127.0.0.1:8080".parse()?;
let server = ApiServer::new(addr, db);
// Shutdown on Ctrl+C
server.serve_with_shutdown(async {
tokio::signal::ctrl_c()
.await
.expect("Failed to listen for Ctrl+C");
println!("Shutting down gracefully...");
}).await?;
Ok(())
}
Configuration Options¶
Environment Variables¶
# JWT Secret (required for authentication)
export HELIOSDB_JWT_SECRET="your-secret-key-change-in-production"
# Server configuration
export HELIOSDB_API_HOST="0.0.0.0"
export HELIOSDB_API_PORT="8080"
# Rate limiting
export HELIOSDB_RATE_LIMIT_MAX_REQUESTS="100"
export HELIOSDB_RATE_LIMIT_WINDOW_SECS="60"
Rate Limit Configurations¶
HeliosDB-Lite provides predefined rate limit configurations:
| Configuration | Requests/Minute | Burst Capacity | Use Case |
|---|---|---|---|
public() |
30 | 5 | Public endpoints |
authenticated() |
100 | 10 | Standard authenticated users |
heavy_operations() |
10 | 2 | Heavy queries/operations |
| Custom | Configurable | Configurable | Custom requirements |
// Custom rate limit
let config = RateLimitConfig::new(
150, // max requests
60, // window in seconds
15 // burst capacity
);
Health Check Endpoints¶
Verify server status with built-in health endpoints:
# Health check
curl http://localhost:8080/health
# Response: "OK" with 200 status
# Version information
curl http://localhost:8080/version
# Response:
# {
# "name": "HeliosDB-Lite",
# "version": "0.2.0",
# "api_version": "v1"
# }
3. Authentication¶
HeliosDB-Lite supports two authentication methods: JWT Bearer tokens and API keys.
JWT Authentication¶
JWT (JSON Web Token) is recommended for user-based authentication with time-limited sessions.
Token Structure¶
JWT tokens contain: - Subject (sub): User ID - Tenant ID: For multi-tenancy - Client ID: Unique client identifier - Expiration (exp): Token expiry timestamp - Scopes: Permissions array
Generating JWT Tokens¶
use heliosdb_lite::sync::auth::{JwtManager, Claims};
use chrono::Duration;
use uuid::Uuid;
fn generate_token() -> String {
let jwt_manager = JwtManager::new(b"your-secret-key");
jwt_manager.generate_token(
"user123".to_string(), // user_id
"tenant456".to_string(), // tenant_id
Uuid::new_v4(), // client_id
).unwrap()
}
Using JWT in Requests¶
Include the JWT token in the Authorization header:
Token Scopes¶
Default scopes:
- sync:read - Read operations
- sync:write - Write operations
Custom scopes can be added when creating Claims.
API Key Authentication¶
API keys provide simple, long-lived authentication ideal for service accounts and automation.
Creating an API Key¶
use heliosdb_lite::api::AuthMiddleware;
use uuid::Uuid;
let auth = AuthMiddleware::new(b"jwt-secret")
.with_api_key(
"sk_test_abc123xyz".to_string(), // API key
"user123".to_string(), // user_id
"tenant456".to_string(), // tenant_id
Uuid::new_v4(), // client_id
vec![ // scopes
"read".to_string(),
"write".to_string()
],
"Production API Key".to_string() // name/description
);
Using API Keys in Requests¶
Include the API key in the X-API-Key header:
Authentication Priority¶
When both authentication methods are provided: 1. JWT Bearer token is checked first 2. If JWT fails or is missing, API key is checked 3. If both fail, request is rejected with 401 Unauthorized
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:
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:
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:
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:
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:
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:
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:
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:
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/data
Response (201 Created):
Update Data¶
Update existing rows in a table.
Endpoint: PUT /v1/branches/{name}/tables/{table}/data
Request Body:
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/data
Response (200 OK):
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:
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/data
Response (200 OK):
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:
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:
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:
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:
Good - Safe Parameterized Query:
Query Timeout¶
Set timeout to prevent long-running queries:
Default timeout: 30000ms (30 seconds)
Timeout Error (408 Request Timeout):
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/query
Time-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:
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/query
Historical Data Access¶
Example: Compare data at two points in time
# 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¶
- 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 Status Codes¶
| Status | 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¶
401 Unauthorized¶
404 Not Found¶
408 Request Timeout¶
409 Conflict¶
429 Too Many Requests¶
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:
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¶
Create Branch¶
curl -X POST \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{"name": "dev", "parent": "main"}' \
http://localhost:8080/v1/branches
Execute 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/query
Python requests Examples¶
import requests
import json
class HeliosDBClient:
def __init__(self, base_url, api_key):
self.base_url = base_url
self.headers = {
'X-API-Key': api_key,
'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
client = HeliosDBClient('http://localhost:8080', 'sk_test_abc123')
# 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, apiKey) {
this.baseUrl = baseUrl;
this.headers = {
'X-API-Key': apiKey,
'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', 'sk_test_abc123');
// 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"
)
type HeliosDBClient struct {
BaseURL string
APIKey string
Client *http.Client
}
func NewClient(baseURL, apiKey string) *HeliosDBClient {
return &HeliosDBClient{
BaseURL: baseURL,
APIKey: apiKey,
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("X-API-Key", c.APIKey)
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", "sk_test_abc123")
// 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¶
Understanding Rate Limits¶
HeliosDB-Lite uses a token bucket algorithm for rate limiting:
- Limit: Maximum requests allowed in the time window
- Remaining: Requests available in current window
- Reset: Unix timestamp when the limit resets
- Retry-After: Seconds to wait before retrying (when limited)
Rate Limit Headers¶
All responses include rate limit information:
Limits by Endpoint¶
Default limits (configurable):
| Endpoint Type | Requests/Minute | Burst |
|---|---|---|
| Public | 30 | 5 |
| Authenticated | 100 | 10 |
| Heavy Operations | 10 | 2 |
Heavy operations include: - Large data queries - Merge operations - Complex SQL queries
Handling 429 Responses¶
When rate limited, you receive:
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1701446460
Retry-After: 30
{
"error": "TooManyRequests",
"message": "Rate limit exceeded"
}
Best Practices:
- Check rate limit headers before hitting the limit
- Use Retry-After header for wait time
- Implement exponential backoff for retries
- Cache responses when appropriate
- Batch operations to reduce request count
Rate Limit Example Implementation¶
import requests
import time
class RateLimitedClient:
def __init__(self, base_url, api_key):
self.base_url = base_url
self.api_key = api_key
self.remaining = None
self.reset_at = None
def _update_rate_limits(self, response):
"""Update rate limit info from response headers"""
self.remaining = int(response.headers.get('X-RateLimit-Remaining', 0))
self.reset_at = int(response.headers.get('X-RateLimit-Reset', 0))
def _should_wait(self):
"""Check if we should wait before making request"""
if self.remaining is not None and self.remaining < 5:
if self.reset_at:
wait_time = max(0, self.reset_at - time.time())
if wait_time > 0:
print(f"Approaching rate limit. Waiting {wait_time:.1f}s")
time.sleep(wait_time)
return True
return False
def request(self, method, path, **kwargs):
"""Make rate-limited request"""
self._should_wait()
headers = kwargs.get('headers', {})
headers['X-API-Key'] = self.api_key
kwargs['headers'] = headers
response = requests.request(method, self.base_url + path, **kwargs)
self._update_rate_limits(response)
if response.status_code == 429:
retry_after = int(response.headers.get('Retry-After', 60))
print(f"Rate limited. Retrying after {retry_after}s")
time.sleep(retry_after)
return self.request(method, path, **kwargs)
response.raise_for_status()
return response
# Usage
client = RateLimitedClient('http://localhost:8080', 'sk_test_abc123')
# Make many requests safely
for i in range(200):
response = client.request('GET', '/v1/branches')
print(f"Request {i+1}: {client.remaining} remaining")
Appendix¶
Complete Working Example¶
This example demonstrates a complete workflow using the REST API:
import requests
import time
class HeliosDBWorkflow:
def __init__(self, base_url, api_key):
self.base_url = base_url
self.headers = {
'X-API-Key': api_key,
'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__':
workflow = HeliosDBWorkflow('http://localhost:8080', 'sk_test_abc123')
workflow.run()
Quick Reference¶
Health & Version
Branches
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
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 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 and API keys
- Branch management operations
- Data CRUD operations
- SQL query execution
- Time-travel queries
- Error handling and retry strategies
- Rate limiting
- SDK examples in multiple languages
For more information:
- API Specification: /docs/api/BRANCH_REST_API_SPECIFICATION.md
- HTTP Sync API: /docs/guides/api/HTTP_SYNC_API.md
- Examples: /examples/ directory
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 rate limiting
Happy coding with HeliosDB-Lite!