KalamDB API Reference

May 28, 2026 ยท View on GitHub

Base URL: http://<host>:2900
Version prefix: /v1

This reference is aligned with the current route + handler implementations.

1) Route Map

Health and status

  • GET /health (localhost-only)
  • GET /v1/api/healthcheck (localhost-only)
  • GET /v1/api/cluster/health (localhost-only)

SQL and files

  • POST /v1/api/sql
  • GET /v1/files/{namespace}/{table_name}/{subfolder}/{stored_name}
  • POST /v1/api/table-exports
  • GET /v1/api/table-exports/{job_id}
  • GET /v1/table-exports/{export_id}
  • POST /v1/api/table-imports
  • GET /v1/api/table-imports/{job_id}

WebSocket

  • GET /v1/ws

Auth (Admin UI + token management)

  • POST /v1/api/auth/login
  • POST /v1/api/auth/refresh
  • POST /v1/api/auth/logout
  • GET /v1/api/auth/me
  • POST /v1/api/auth/setup
  • GET /v1/api/auth/status

Topic HTTP API

  • POST /v1/api/topics/consume
  • POST /v1/api/topics/ack

2) Authentication Rules by Endpoint

Bearer-token-only endpoints

These endpoints use AuthSessionExtractor and require:

Authorization: Bearer <JWT_TOKEN>

Basic auth is rejected on these routes.

Direct user / password credentials are only accepted on POST /v1/api/auth/login.

  • POST /v1/api/sql
  • GET /v1/files/...
  • POST /v1/api/table-exports
  • GET /v1/api/table-exports/{job_id}
  • GET /v1/table-exports/{export_id}
  • POST /v1/api/table-imports
  • GET /v1/api/table-imports/{job_id}
  • POST /v1/api/topics/consume
  • POST /v1/api/topics/ack

These endpoints accept bearer token in header, or auth cookie fallback:

  • POST /v1/api/auth/refresh
  • GET /v1/api/auth/me

Public (no auth required)

  • POST /v1/api/auth/login
  • POST /v1/api/auth/logout (just clears cookie)
  • POST /v1/api/auth/setup (localhost-only unless remote setup allowed)
  • GET /v1/api/auth/status (localhost-only unless remote setup allowed)
  • GET /health (localhost-only)
  • GET /v1/api/healthcheck (localhost-only)
  • GET /v1/api/cluster/health (localhost-only)

WebSocket auth

GET /v1/ws upgrades unauthenticated; authentication is done by an in-band WebSocket message:

{"type":"authenticate","method":"jwt","token":"..."}

See WebSocket Protocol.

3) SQL API

POST /v1/api/sql

Execute SQL using JSON or multipart payload.

Request headers

  • Authorization: Bearer <JWT_TOKEN> (required)
  • Content-Type: application/json or multipart/form-data

JSON body

{
  "sql": "SELECT * FROM default.users WHERE id = \$1",
  "params": [123],
  "namespace_id": "default"
}

Fields:

  • sql (required string)
  • params (optional array)
  • namespace_id (optional string): request-scoped default namespace for unqualified table names. Interactive clients can send this on follow-up requests after a successful USE namespace.

Multipart body (FILE datatype path)

Expected form parts:

  • sql (required)
  • params (optional; JSON array string)
  • namespace_id (optional request-scoped default namespace)
  • file:<placeholder> one or more file parts

Placeholder mapping:

  • SQL uses FILE("name") or FILE('name')
  • Multipart part must be named file:name

Example:

INSERT INTO app.docs (id, attachment)
VALUES ('d1', FILE("contract"));

Multipart must include part name file:contract.

SQL execution behavior

  • Multiple statements separated by ; are supported
  • Parameters with multi-statement batches are rejected (PARAMS_WITH_BATCH)
  • File uploads require exactly one SQL statement
  • SQL length is bounded by MAX_SQL_QUERY_LENGTH
  • In cluster mode:
    • write operations on followers are forwarded to leader
    • file uploads must be sent to leader (NOT_LEADER if not)

Success response shape

{
  "status": "success",
  "results": [
    {
      "schema": [{"name":"id","data_type":"BigInt","index":0}],
      "rows": [[1]],
      "row_count": 1,
      "as_user": "alice"
    }
  ],
  "took": 12.34
}

results[] fields:

  • schema (omitted when empty)
  • rows (omitted when none)
  • row_count (always present)
  • message (optional; e.g. DDL/DML summary)
  • as_user (always present)

Error response shape

{
  "status": "error",
  "results": [],
  "took": 1.23,
  "error": {
    "code": "INVALID_SQL",
    "message": "...",
    "details": null
  }
}

SQL error codes

Current error.code values include:

  • RATE_LIMIT_EXCEEDED
  • INVALID_PARAMETER
  • BATCH_PARSE_ERROR
  • EMPTY_SQL
  • PARAMS_WITH_BATCH
  • SQL_EXECUTION_ERROR
  • FORWARD_FAILED
  • NOT_LEADER
  • INVALID_SQL
  • TABLE_NOT_FOUND
  • PERMISSION_DENIED
  • CLUSTER_UNAVAILABLE
  • LEADER_NOT_AVAILABLE
  • INTERNAL_ERROR
  • INVALID_INPUT
  • FILE_TOO_LARGE
  • TOO_MANY_FILES
  • MISSING_FILE
  • EXTRA_FILE
  • FILE_NOT_FOUND
  • INVALID_MIME_TYPE

4) File Download API

GET /v1/files/{namespace}/{table_name}/{subfolder}/{stored_name}

Download previously stored file bytes.

Auth

  • Bearer token required

Query params

  • user_id (optional)
    • Only meaningful for user tables
    • Cross-user raw-byte downloads are limited to dba and system actors that are authorized for the target ID's cached role class

Behavior by table type

  • User table: downloads from the authenticated user scope by default. dba and system actors may supply user_id for an authorized target user scope. service actors can write user-scoped rows through EXECUTE AS USER, but cannot directly download another user's FILE bytes with user_id.
  • Shared table: allowed only if shared access policy permits; user_id query is rejected
  • Stream/System table: rejected (file storage not supported)

Validation

  • subfolder and stored_name are path-validated (no traversal patterns)

Responses

  • 200 OK: binary content with inferred content-type and Content-Disposition: inline
  • 400/403/404 depending on validation/permission/not-found conditions

5) Table Transfer API

Table transfer endpoints are used by the Admin UI table editor for user/shared table data movement. They require bearer auth and role in {service, dba, system}.

Table exports flush hot RocksDB rows first, then write a ZIP containing committed Parquet segments and a KalamDB table-export metadata file. Table imports accept only that table-export ZIP format, require the target table to already exist with matching column definitions, and register imported Parquet files through the target table manifest.

POST /v1/api/table-exports

Start a table export job.

{
  "namespace_id": "app",
  "table_name": "events",
  "table_type": "user",
  "user_id": "alice"
}

For shared tables, omit user_id.

Response:

{
  "job_id": "TE-...",
  "export_id": "table-export-...",
  "status": "queued",
  "download_url": "/v1/table-exports/table-export-..."
}

GET /v1/api/table-exports/{job_id}

Poll export status. When status is completed, download_url is present.

GET /v1/table-exports/{export_id}

Download the completed table export ZIP. This route also requires bearer auth and role in {service, dba, system}.

POST /v1/api/table-imports

Upload a table export ZIP and start an import job. The request is multipart/form-data with:

  • namespace_id
  • table_name
  • table_type (user or shared)
  • user_id for user tables only
  • file containing the ZIP

Response:

{
  "job_id": "TI-...",
  "import_id": "table-import-...",
  "status": "queued"
}

GET /v1/api/table-imports/{job_id}

Poll import status.

6) Health Endpoints

GET /health and GET /v1/api/healthcheck

Both return the same payload and are localhost-only.

Success:

{
  "status": "healthy",
  "version": "...",
  "api_version": "v1",
  "build_date": "..."
}

Remote callers receive 403.

GET /v1/api/cluster/health

Localhost-only cluster/raft health summary.

Response shape:

{
  "status": "healthy|degraded|unhealthy",
  "version": "...",
  "build_date": "...",
  "is_cluster_mode": true,
  "cluster_id": "...",
  "node_id": 1,
  "is_leader": true,
  "total_groups": 3,
  "groups_leading": 2,
  "current_term": 42,
  "last_applied": 1234,
  "millis_since_quorum_ack": 10,
  "nodes": [
    {
      "node_id": 1,
      "role": "Leader",
      "status": "Active",
      "api_addr": "127.0.0.1:2900",
      "is_self": true,
      "is_leader": true,
      "replication_lag": 0,
      "catchup_progress_pct": null
    }
  ]
}

7) Auth Endpoints

POST /v1/api/auth/login

Authenticates user / password, returns an access/refresh token pair, and sets the HttpOnly auth cookie.

Request:

{
  "user": "alice",
  "password": "Secret123!"
}

Constraints:

  • user max length: 128
  • password max length: 256

Success response:

{
  "user": {
    "id": "u_...",
    "role": "dba",
    "email": null,
    "created_at": "...",
    "updated_at": "..."
  },
  "admin_ui_access": true,
  "expires_at": "...",
  "access_token": "...",
  "refresh_token": "...",
  "refresh_expires_at": "..."
}

Only this endpoint accepts direct user/password credentials. Protected SQL, topic, refresh, /me, and WebSocket auth flows use bearer tokens or cookies instead.

POST /v1/api/auth/refresh

Accepts bearer token header or auth cookie, validates token, issues new access + refresh pair, and resets auth cookie.

Direct user/password auth is rejected on this endpoint.

Returns same shape as login.

POST /v1/api/auth/logout

Clears auth cookie.

Response:

{"message":"Logged out successfully"}

GET /v1/api/auth/me

Returns current user info plus admin_ui_access (same user object shape as login, without token fields).

POST /v1/api/auth/setup

Initial server bootstrap endpoint.

Allowed only when:

  1. root user has no password yet
  2. request is localhost (unless auth.allow_remote_setup = true)

Request:

{
  "user": "admin",
  "password": "AdminPass123!",
  "root_password": "RootPass123!",
  "email": "admin@example.com"
}

Behavior:

  • sets root password
  • creates new DBA user
  • does not log in automatically

GET /v1/api/auth/status

Localhost-only (unless remote setup allowed). Returns setup status:

{
  "needs_setup": true,
  "message": "Server requires initial setup..."
}

8) Topic HTTP API

Both topic endpoints require bearer auth and role in {service, dba, system}.

POST /v1/api/topics/consume

Request:

{
  "topic_id": "orders_topic",
  "group_id": "worker_group",
  "start": "Latest",
  "limit": 100,
  "partition_id": 0,
  "timeout_seconds": 10
}

start accepted forms:

  • "Latest" (default)
  • "Earliest"
  • { "Offset": 123 } (also accepts lowercase offset key)

Response:

{
  "messages": [
    {
      "topic_id": "orders_topic",
      "partition_id": 0,
      "offset": 10,
      "payload": "<base64>",
      "key": "optional-key",
      "timestamp_ms": 1730000000000,
      "user": "alice",
      "op": "Insert"
    }
  ],
  "next_offset": 11,
  "has_more": false
}

Notes:

  • payload is base64-encoded bytes
  • omit group_id for stateless inspection; stateless reads honor start on every request and do not create group offsets
  • include group_id for durable group consumption; existing committed offsets resume before start is considered
  • timeout_seconds is accepted in request but is not currently used by handler logic

POST /v1/api/topics/ack

Request:

{
  "topic_id": "orders_topic",
  "group_id": "worker_group",
  "partition_id": 0,
  "upto_offset": 10
}

Response:

{
  "success": true,
  "acknowledged_offset": 10
}

Topic error shape:

{
  "error": "...",
  "code": "FORBIDDEN|NOT_FOUND|INTERNAL_ERROR"
}

8) WebSocket Entry Point

GET /v1/ws

  • Performs upgrade if origin/security checks pass
  • Connection then requires authenticate message using JWT
  • Subscription/change/error payloads are documented in WebSocket Protocol

9) Notes on Non-routed Handlers

/healthz and /readyz handlers exist in source but are not currently wired into the route configuration. The active health endpoints are /health and /v1/api/healthcheck.