Skip to content

HeliosDB-Lite REST API Tutorial

Version: 1.0.0 Last Updated: 2025-12-01 Status: Complete


Table of Contents

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

1. Introduction

REST API Overview

HeliosDB-Lite provides a 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:

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

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:

curl -H "X-API-Key: sk_test_abc123xyz" \
  http://localhost:8080/v1/branches

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:

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

Response (200 OK):

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

Create Branch

Create a new branch from an existing branch or snapshot.

Endpoint: POST /v1/branches

Request Body:

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

Request:

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:

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

Response (200 OK):

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

Errors: - 404 Not Found: Branch does not exist

Delete Branch

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

Endpoint: DELETE /v1/branches/{name}

Request:

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

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

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

Merge Branches

Merge changes from one branch into another.

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

Request Body:

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

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

Request:

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:

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

Response (200 OK):

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

Query Data

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

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

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

Request - All Data:

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):

{
  "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/data

Response (200 OK):

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

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

Delete Data

Delete rows from a table.

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

Request Body:

{
  "filter": "id = 100"
}

Request:

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

Response (200 OK):

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

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


6. SQL Execution

Execute raw SQL queries and statements for maximum flexibility.

Execute Query (SELECT)

Execute read-only SELECT queries.

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

Request Body:

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

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

Request:

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:

{
  "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/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:

{
  "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/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

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

8. Error Handling

Error Response Format

All errors follow a consistent JSON structure:

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

HTTP 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 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

curl -H "Authorization: Bearer ${TOKEN}" \
  http://localhost:8080/v1/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:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1701446460

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:

  1. Check rate limit headers before hitting the limit
  2. Use Retry-After header for wait time
  3. Implement exponential backoff for retries
  4. Cache responses when appropriate
  5. 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

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

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!