---
title: "HeliosDB-Lite REST API Tutorial"
---

# HeliosDB-Lite REST API Tutorial

---

## Table of Contents

1. [Introduction](#1-introduction)
2. [Setup](#2-setup)
3. [Authentication](#3-authentication)
4. [Branch Operations](#4-branch-operations)
5. [Data Operations](#5-data-operations)
6. [SQL Execution](#6-sql-execution)
7. [Time-Travel via API](#7-time-travel-via-api)
8. [Error Handling](#8-error-handling)
9. [SDK Examples](#9-sdk-examples)
10. [Rate Limiting](#10-rate-limiting)

---

## 1. Introduction

### REST API Overview

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

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

### When to Use the API

Use the REST API when you need to:

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

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

### Base URL Configuration

The API base URL depends on your deployment:

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

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

---

## 2. Setup

### Starting the API Server

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

```bash
export HELIOSDB_JWT_SECRET="$(openssl rand -hex 32)"

# Persistent data, loopback only (default)
heliosdb-lite start --data-dir ./mydb

# Custom HTTP port
heliosdb-lite start --data-dir ./mydb --http-port 9090
```

### Configuration Options

#### Environment Variables

| Variable | Purpose |
|----------|---------|
| `HELIOSDB_JWT_SECRET` | Required. Secret that signs and verifies tokens |
| `HELIOSDB_CORS_ALLOWED_ORIGINS` | Comma-separated browser origins allowed to call the API |
| `HELIOSDB_METRICS_KEY` | Unlocks `/metrics` and the detailed `/health` body |

#### Rate Limiting

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

### Health Check Endpoints

Verify Server state with built-in health endpoints:

```bash
# Health check
curl http://localhost:8080/health

# Response (200): {"status":"ok"}

# Version information
curl http://localhost:8080/version

# Response:
# {
#   "name": "HeliosDB-Lite",
#   "version": "<build version>",
#   "api_version": "v1"
# }
```

---

## 3. Authentication

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

### JWT Authentication

#### Token Structure

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

#### Generating JWT Tokens

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

```python
import os, time, uuid
import jwt  # pip install PyJWT

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

See the [JWT Authentication Quick Reference](/docs/lite/guides/quickstart/jwt_auth_quick_reference/).

#### Using JWT in Requests

Include the JWT token in the `Authorization` header:

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

#### Token Scopes

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

### Authentication Errors

```json
{
  "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**:
```bash
curl -H "Authorization: Bearer <token>" \
  http://localhost:8080/v1/branches
```

**Response** (200 OK):
```json
{
  "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**:
```json
{
  "name": "feature-user-auth",
  "parent": "main",
  "snapshot_id": 1701446400,
  "options": {
    "read_only": false,
    "isolated": true
  }
}
```

**Request**:
```bash
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):
```json
{
  "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**:
```bash
curl -H "Authorization: Bearer <token>" \
  http://localhost:8080/v1/branches/feature-user-auth
```

**Response** (200 OK):
```json
{
  "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**:
```bash
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**:
```json
{
  "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**:
```bash
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):
```json
{
  "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):
```json
{
  "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**:
```bash
curl -H "Authorization: Bearer <token>" \
  http://localhost:8080/v1/branches/main/tables
```

**Response** (200 OK):
```json
{
  "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**:
```bash
curl -H "Authorization: Bearer <token>" \
  "http://localhost:8080/v1/branches/main/tables/users/data"
```

**Request - With Filter and Pagination**:
```bash
curl -H "Authorization: Bearer <token>" \
  "http://localhost:8080/v1/branches/main/tables/users/data?filter=age>25&page=1&limit=10"
```

**Request - Specific Columns**:
```bash
curl -H "Authorization: Bearer <token>" \
  "http://localhost:8080/v1/branches/main/tables/users/data?columns=id,name,email"
```

**Request - With Sorting**:
```bash
curl -H "Authorization: Bearer <token>" \
  "http://localhost:8080/v1/branches/main/tables/users/data?order_by=created_at+DESC&limit=20"
```

**Response** (200 OK):
```json
{
  "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**:
```json
{
  "rows": [
    {
      "id": 100,
      "name": "Charlie",
      "email": "charlie@example.com",
      "age": 35
    },
    {
      "id": 101,
      "name": "Diana",
      "email": "diana@example.com",
      "age": 27
    }
  ]
}
```

**Request**:
```bash
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):
```json
{
  "inserted": 1,
  "execution_time_ms": 12
}
```

### Update Data

Update existing rows in a table.

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

**Request Body**:
```json
{
  "values": {
    "email": "newemail@example.com",
    "updated_at": 1701470000
  },
  "filter": "id = 100"
}
```

**Request**:
```bash
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):
```json
{
  "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**:
```json
{
  "filter": "id = 100"
}
```

**Request**:
```bash
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):
```json
{
  "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**:
```json
{
  "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**:
```bash
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):
```json
{
  "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**:
```json
{
  "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**:
```bash
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):
```json
{
  "statement_type": "INSERT",
  "affected_rows": 1,
  "execution_time_ms": 23,
  "message": "INSERT statement executed successfully on branch 'main'"
}
```

**Request - CREATE TABLE**:
```bash
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):
```json
{
  "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**:
```json
{
  "sql": "SELECT * FROM users WHERE name = '" + userInput + "'"
}
```

**Good - Safe Parameterized Query**:
```json
{
  "sql": "SELECT * FROM users WHERE name = $1",
  "params": [{"type": "text", "value": "Alice"}]
}
```

### Query Timeout

Set timeout to prevent long-running queries:

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

Default timeout: 30000ms (30 seconds)

**Timeout Error** (408 Request Timeout):
```json
{
  "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**:
```json
{
  "sql": "SELECT * FROM users WHERE id = $1",
  "params": [{"type": "int4", "value": 1}],
  "as_of": {
    "type": "timestamp",
    "value": 1701446400
  }
}
```

**Request**:
```bash
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:

```bash
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:

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

**Request**:
```bash
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**

```bash
# 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:

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

### HTTP Response Codes

| Result | Error Type | Description |
|--------|-----------|-------------|
| 400 | BadRequest | Invalid request syntax or parameters |
| 401 | Unauthorized | Missing or invalid authentication |
| 403 | Forbidden | Insufficient permissions |
| 404 | NotFound | Resource does not exist |
| 408 | QueryTimeout | Query exceeded timeout limit |
| 409 | Conflict | Resource conflict (e.g., branch exists, merge conflict) |
| 422 | UnprocessableEntity | Valid syntax but semantic error |
| 429 | TooManyRequests | Rate limit exceeded |
| 500 | InternalServerError | Server error |
| 501 | NotImplemented | Feature not enabled |

### Common Error Examples

#### 400 Bad Request
```json
{
  "error": "BadRequest",
  "message": "Invalid SQL syntax: unexpected token 'FROM'"
}
```

#### 401 Unauthorized
```json
{
  "error": "Unauthorized",
  "message": "Missing or invalid authentication credentials"
}
```

#### 404 Not Found
```json
{
  "error": "NotFound",
  "message": "Branch 'feature-xyz' not found"
}
```

#### 408 Request Timeout
```json
{
  "error": "QueryTimeout",
  "message": "Query exceeded timeout of 5000ms"
}
```

#### 409 Conflict
```json
{
  "error": "Conflict",
  "message": "Branch 'development' already exists"
}
```

#### 429 Too Many Requests
```json
{
  "error": "TooManyRequests",
  "message": "Rate limit exceeded"
}
```

### Retry Strategies

#### Exponential Backoff

```python
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:

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

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

#### Execute Query
```bash
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

```python
import requests
import json

class HeliosDBClient:
    def __init__(self, base_url, token):
        self.base_url = base_url
        self.headers = {
            'Authorization': f'Bearer {token}',
            'Content-Type': 'application/json'
        }

    def list_branches(self):
        """List all branches"""
        response = requests.get(
            f"{self.base_url}/v1/branches",
            headers=self.headers
        )
        response.raise_for_status()
        return response.json()

    def create_branch(self, name, parent='main'):
        """Create a new branch"""
        response = requests.post(
            f"{self.base_url}/v1/branches",
            headers=self.headers,
            json={'name': name, 'parent': parent}
        )
        response.raise_for_status()
        return response.json()

    def execute_query(self, branch, sql, params=None):
        """Execute a SQL query"""
        payload = {'sql': sql}
        if params:
            payload['params'] = params

        response = requests.post(
            f"{self.base_url}/v1/branches/{branch}/query",
            headers=self.headers,
            json=payload
        )
        response.raise_for_status()
        return response.json()

    def insert_data(self, branch, table, rows):
        """Insert data into a table"""
        response = requests.post(
            f"{self.base_url}/v1/branches/{branch}/tables/{table}/data",
            headers=self.headers,
            json={'rows': rows}
        )
        response.raise_for_status()
        return response.json()

# Usage
# generate_token() is defined in section 3 (Authentication)
client = HeliosDBClient('http://localhost:8080', generate_token())

# List branches
branches = client.list_branches()
print(f"Found {branches['total']} branches")

# Create branch
new_branch = client.create_branch('feature-xyz')
print(f"Created branch: {new_branch['name']}")

# Execute query
results = client.execute_query(
    'main',
    'SELECT * FROM users WHERE age > $1',
    params=[{'type': 'int4', 'value': 25}]
)
print(f"Query returned {results['row_count']} rows")

# Insert data
insert_result = client.insert_data(
    'main',
    'users',
    [{'id': 500, 'name': 'Alice', 'email': 'alice@example.com'}]
)
print(f"Inserted {insert_result['inserted']} rows")
```

### JavaScript fetch Examples

```javascript
class HeliosDBClient {
    constructor(baseUrl, token) {
        this.baseUrl = baseUrl;
        this.headers = {
            'Authorization': `Bearer ${token}`,
            'Content-Type': 'application/json'
        };
    }

    async listBranches() {
        const response = await fetch(`${this.baseUrl}/v1/branches`, {
            headers: this.headers
        });

        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }

        return await response.json();
    }

    async createBranch(name, parent = 'main') {
        const response = await fetch(`${this.baseUrl}/v1/branches`, {
            method: 'POST',
            headers: this.headers,
            body: JSON.stringify({ name, parent })
        });

        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }

        return await response.json();
    }

    async executeQuery(branch, sql, params = null) {
        const payload = { sql };
        if (params) {
            payload.params = params;
        }

        const response = await fetch(
            `${this.baseUrl}/v1/branches/${branch}/query`,
            {
                method: 'POST',
                headers: this.headers,
                body: JSON.stringify(payload)
            }
        );

        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }

        return await response.json();
    }

    async insertData(branch, table, rows) {
        const response = await fetch(
            `${this.baseUrl}/v1/branches/${branch}/tables/${table}/data`,
            {
                method: 'POST',
                headers: this.headers,
                body: JSON.stringify({ rows })
            }
        );

        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }

        return await response.json();
    }
}

// Usage
const client = new HeliosDBClient('http://localhost:8080', process.env.HELIOSDB_TOKEN);

// List branches
const branches = await client.listBranches();
console.log(`Found ${branches.total} branches`);

// Create branch
const newBranch = await client.createBranch('feature-xyz');
console.log(`Created branch: ${newBranch.name}`);

// Execute query
const results = await client.executeQuery(
    'main',
    'SELECT * FROM users WHERE age > $1',
    [{ type: 'int4', value: 25 }]
);
console.log(`Query returned ${results.row_count} rows`);

// Insert data
const insertResult = await client.insertData(
    'main',
    'users',
    [{ id: 500, name: 'Alice', email: 'alice@example.com' }]
);
console.log(`Inserted ${insertResult.inserted} rows`);
```

### Go net/http Example

```go
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
)

type HeliosDBClient struct {
    BaseURL string
    Token   string
    Client  *http.Client
}

func NewClient(baseURL, token string) *HeliosDBClient {
    return &HeliosDBClient{
        BaseURL: baseURL,
        Token:   token,
        Client:  &http.Client{},
    }
}

func (c *HeliosDBClient) request(method, path string, body interface{}) (*http.Response, error) {
    var bodyReader io.Reader
    if body != nil {
        jsonBody, err := json.Marshal(body)
        if err != nil {
            return nil, err
        }
        bodyReader = bytes.NewBuffer(jsonBody)
    }

    req, err := http.NewRequest(method, c.BaseURL+path, bodyReader)
    if err != nil {
        return nil, err
    }

    req.Header.Set("Authorization", "Bearer "+c.Token)
    req.Header.Set("Content-Type", "application/json")

    return c.Client.Do(req)
}

func (c *HeliosDBClient) ListBranches() (map[string]interface{}, error) {
    resp, err := c.request("GET", "/v1/branches", nil)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    var result map[string]interface{}
    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
        return nil, err
    }

    return result, nil
}

func (c *HeliosDBClient) ExecuteQuery(branch, sql string) (map[string]interface{}, error) {
    payload := map[string]interface{}{
        "sql": sql,
    }

    resp, err := c.request("POST", "/v1/branches/"+branch+"/query", payload)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    var result map[string]interface{}
    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
        return nil, err
    }

    return result, nil
}

func main() {
    client := NewClient("http://localhost:8080", os.Getenv("HELIOSDB_TOKEN"))

    // List branches
    branches, err := client.ListBranches()
    if err != nil {
        panic(err)
    }
    fmt.Printf("Found %v branches\n", branches["total"])

    // Execute query
    results, err := client.ExecuteQuery("main", "SELECT * FROM users LIMIT 10")
    if err != nil {
        panic(err)
    }
    fmt.Printf("Query returned %v rows\n", results["row_count"])
}
```

---

## 10. Rate Limiting

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

---

## Appendix

### Complete Example

This example demonstrates a complete workflow using the REST API:

```python
import requests
import time

class HeliosDBWorkflow:
    def __init__(self, base_url, token):
        self.base_url = base_url
        self.headers = {
            'Authorization': f'Bearer {token}',
            'Content-Type': 'application/json'
        }

    def run(self):
        print("=== HeliosDB-Lite REST API Workflow ===\n")

        # 1. Create a feature branch
        print("1. Creating feature branch...")
        branch = self._create_branch('feature-demo')
        print(f"   Created: {branch['name']}\n")

        # 2. Create a table
        print("2. Creating products table...")
        self._execute_statement(
            'feature-demo',
            '''CREATE TABLE products (
                id INTEGER PRIMARY KEY,
                name TEXT NOT NULL,
                price REAL,
                created_at INTEGER
            )'''
        )
        print("   Table created\n")

        # 3. Insert data
        print("3. Inserting sample data...")
        result = self._insert_data('feature-demo', 'products', [
            {'id': 1, 'name': 'Widget', 'price': 9.99, 'created_at': int(time.time())},
            {'id': 2, 'name': 'Gadget', 'price': 19.99, 'created_at': int(time.time())},
            {'id': 3, 'name': 'Doohickey', 'price': 14.99, 'created_at': int(time.time())},
        ])
        print(f"   Inserted {result['inserted']} rows\n")

        # 4. Query data
        print("4. Querying data...")
        results = self._execute_query(
            'feature-demo',
            'SELECT * FROM products WHERE price > $1 ORDER BY price DESC',
            [{'type': 'float8', 'value': 10.0}]
        )
        print(f"   Found {results['row_count']} products over $10:")
        for row in results['rows']:
            print(f"   - {row['name']}: ${row['price']}")
        print()

        # 5. Merge to main
        print("5. Merging feature-demo into main...")
        merge_result = self._merge_branch('feature-demo', 'main')
        print(f"   {merge_result['message']}\n")

        # 6. Verify in main branch
        print("6. Verifying data in main branch...")
        results = self._execute_query('main', 'SELECT COUNT(*) as total FROM products')
        print(f"   Total products in main: {results['rows'][0]['total']}\n")

        # 7. Cleanup
        print("7. Cleaning up feature branch...")
        self._delete_branch('feature-demo')
        print("   Branch deleted\n")

        print("=== Workflow Complete ===")

    def _create_branch(self, name):
        response = requests.post(
            f"{self.base_url}/v1/branches",
            headers=self.headers,
            json={'name': name, 'parent': 'main'}
        )
        response.raise_for_status()
        return response.json()

    def _execute_statement(self, branch, sql):
        response = requests.post(
            f"{self.base_url}/v1/branches/{branch}/execute",
            headers=self.headers,
            json={'sql': sql}
        )
        response.raise_for_status()
        return response.json()

    def _insert_data(self, branch, table, rows):
        response = requests.post(
            f"{self.base_url}/v1/branches/{branch}/tables/{table}/data",
            headers=self.headers,
            json={'rows': rows}
        )
        response.raise_for_status()
        return response.json()

    def _execute_query(self, branch, sql, params=None):
        payload = {'sql': sql}
        if params:
            payload['params'] = params

        response = requests.post(
            f"{self.base_url}/v1/branches/{branch}/query",
            headers=self.headers,
            json=payload
        )
        response.raise_for_status()
        return response.json()

    def _merge_branch(self, source, target):
        response = requests.post(
            f"{self.base_url}/v1/branches/{source}/merge",
            headers=self.headers,
            json={'target': target, 'strategy': 'Auto'}
        )
        response.raise_for_status()
        return response.json()

    def _delete_branch(self, name):
        response = requests.delete(
            f"{self.base_url}/v1/branches/{name}",
            headers=self.headers
        )
        response.raise_for_status()

# Run the workflow
if __name__ == '__main__':
    # generate_token() is defined in section 3 (Authentication)
    workflow = HeliosDBWorkflow('http://localhost:8080', generate_token())
    workflow.run()
```

### Quick Reference

**Health & Version**
```bash
GET  /health              # Health check
GET  /version             # Version info
```

**Branches**
```bash
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**
```bash
POST /v1/branches/{name}/query   # Execute query (SELECT)
POST /v1/branches/{name}/execute # Execute statement (DDL/DML)
```

**Data Operations**
```bash
GET    /v1/branches/{name}/tables                   # List tables
GET    /v1/branches/{name}/tables/{table}/data      # Query data
POST   /v1/branches/{name}/tables/{table}/data      # Insert data
PUT    /v1/branches/{name}/tables/{table}/data      # Update data
DELETE /v1/branches/{name}/tables/{table}/data      # Delete data
```

---

## Conclusion

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

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

For more information:
- [Branch REST API Specification](/docs/lite/reference/api/branch_rest_api_specification/)
- [JWT Authentication Quick Reference](/docs/lite/guides/quickstart/jwt_auth_quick_reference/)

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

