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/sqlGET /v1/files/{namespace}/{table_name}/{subfolder}/{stored_name}POST /v1/api/table-exportsGET /v1/api/table-exports/{job_id}GET /v1/table-exports/{export_id}POST /v1/api/table-importsGET /v1/api/table-imports/{job_id}
WebSocket
GET /v1/ws
Auth (Admin UI + token management)
POST /v1/api/auth/loginPOST /v1/api/auth/refreshPOST /v1/api/auth/logoutGET /v1/api/auth/mePOST /v1/api/auth/setupGET /v1/api/auth/status
Topic HTTP API
POST /v1/api/topics/consumePOST /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/sqlGET /v1/files/...POST /v1/api/table-exportsGET /v1/api/table-exports/{job_id}GET /v1/table-exports/{export_id}POST /v1/api/table-importsGET /v1/api/table-imports/{job_id}POST /v1/api/topics/consumePOST /v1/api/topics/ack
Cookie or Bearer endpoints
These endpoints accept bearer token in header, or auth cookie fallback:
POST /v1/api/auth/refreshGET /v1/api/auth/me
Public (no auth required)
POST /v1/api/auth/loginPOST /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/jsonormultipart/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 successfulUSE 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")orFILE('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_LEADERif 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_EXCEEDEDINVALID_PARAMETERBATCH_PARSE_ERROREMPTY_SQLPARAMS_WITH_BATCHSQL_EXECUTION_ERRORFORWARD_FAILEDNOT_LEADERINVALID_SQLTABLE_NOT_FOUNDPERMISSION_DENIEDCLUSTER_UNAVAILABLELEADER_NOT_AVAILABLEINTERNAL_ERRORINVALID_INPUTFILE_TOO_LARGETOO_MANY_FILESMISSING_FILEEXTRA_FILEFILE_NOT_FOUNDINVALID_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
dbaandsystemactors that are authorized for the target ID's cached role class
Behavior by table type
Usertable: downloads from the authenticated user scope by default.dbaandsystemactors may supplyuser_idfor an authorized target user scope.serviceactors can write user-scoped rows throughEXECUTE AS USER, but cannot directly download another user's FILE bytes withuser_id.Sharedtable: allowed only if shared access policy permits;user_idquery is rejectedStream/Systemtable: rejected (file storage not supported)
Validation
subfolderandstored_nameare path-validated (no traversal patterns)
Responses
200 OK: binary content with inferred content-type andContent-Disposition: inline400/403/404depending 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_idtable_nametable_type(userorshared)user_idfor user tables onlyfilecontaining 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:
- root user has no password yet
- 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 lowercaseoffsetkey)
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:
payloadis base64-encoded bytes- omit
group_idfor stateless inspection; stateless reads honorstarton every request and do not create group offsets - include
group_idfor durable group consumption; existing committed offsets resume beforestartis considered timeout_secondsis 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
authenticatemessage 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.