HeliosDB Nano REST API Tutorial
HeliosDB Nano 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 Nano 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/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
Basic Server Setup
use heliosdb_nano::{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_nano::{ 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_nano::{ 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_nano::{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 configurationexport HELIOSDB_API_HOST="0.0.0.0"export HELIOSDB_API_PORT="8080"
# Rate limitingexport HELIOSDB_RATE_LIMIT_MAX_REQUESTS="100"export HELIOSDB_RATE_LIMIT_WINDOW_SECS="60"Rate Limit Configurations
HeliosDB Nano 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 limitlet 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 checkcurl http://localhost:8080/health
# Response: "OK" with 200 status
# Version informationcurl http://localhost:8080/version
# Response:# {# "name": "HeliosDB Nano",# "version": "0.2.0",# "api_version": "v1"# }3. Authentication
HeliosDB Nano 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_nano::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:
curl -H "Authorization: Bearer <your-jwt-token>" \ http://localhost:8080/v1/branchesToken Scopes
Default scopes:
sync:read- Read operationssync: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_nano::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:
curl -H "X-API-Key: sk_test_abc123xyz" \ http://localhost:8080/v1/branchesAuthentication Priority
When both authentication methods are provided:
- JWT Bearer token is checked first
- If JWT fails or is missing, API key is checked
- 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:
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 Nano’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 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
{ "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, 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()
# Usageclient = HeliosDBClient('http://localhost:8080', 'sk_test_abc123')
# 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, 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(); }}
// Usageconst client = new HeliosDBClient('http://localhost:8080', 'sk_test_abc123');
// 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")
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 Nano 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:
X-RateLimit-Limit: 100X-RateLimit-Remaining: 95X-RateLimit-Reset: 1701446460Limits 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 RequestsX-RateLimit-Limit: 100X-RateLimit-Remaining: 0X-RateLimit-Reset: 1701446460Retry-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 requestsimport 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
# Usageclient = RateLimitedClient('http://localhost:8080', 'sk_test_abc123')
# Make many requests safelyfor 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 requestsimport 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 Nano 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__': workflow = HeliosDBWorkflow('http://localhost:8080', 'sk_test_abc123') 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 Nano 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:
- Set up a development server
- Generate authentication credentials
- Try the example workflows
- Integrate with your application
- Implement proper error handling and rate limiting
Happy coding with HeliosDB Nano!