Base URL: http://<host>:2900
Version prefix: /v1
This reference is aligned with the current route + handler implementations.
GET /health(localhost-only)GET /v1/api/healthcheck(localhost-only)GET /v1/api/cluster/health(localhost-only)
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}
GET /v1/ws
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
POST /v1/api/topics/consumePOST /v1/api/topics/ack
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
These endpoints accept bearer token in header, or auth cookie fallback:
POST /v1/api/auth/refreshGET /v1/api/auth/me
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)
GET /v1/ws upgrades unauthenticated; authentication is done by an in-band WebSocket message:
{"type":"authenticate","method":"jwt","token":"..."}See WebSocket Protocol.
Execute SQL using JSON or multipart payload.
Authorization: Bearer <JWT_TOKEN>(required)Content-Type: application/jsonormultipart/form-data
{
"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.
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.
- 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)
{
"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)
{
"status": "error",
"results": [],
"took": 1.23,
"error": {
"code": "INVALID_SQL",
"message": "...",
"details": null
}
}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
Download previously stored file bytes.
- Bearer token required
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
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)
subfolderandstored_nameare path-validated (no traversal patterns)
200 OK: binary content with inferred content-type andContent-Disposition: inline400/403/404depending on validation/permission/not-found conditions
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.
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-..."
}Poll export status. When status is completed, download_url is present.
Download the completed table export ZIP. This route also requires bearer auth and role in
{service, dba, system}.
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"
}Poll import status.
Both return the same payload and are localhost-only.
Success:
{
"status": "healthy",
"version": "...",
"api_version": "v1",
"build_date": "..."
}Remote callers receive 403.
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
}
]
}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.
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.
Clears auth cookie.
Response:
{"message":"Logged out successfully"}Returns current user info plus admin_ui_access (same user object shape as login, without token fields).
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
Localhost-only (unless remote setup allowed). Returns setup status:
{
"needs_setup": true,
"message": "Server requires initial setup..."
}Both topic endpoints require bearer auth and role in {service, dba, system}.
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
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"
}- Performs upgrade if origin/security checks pass
- Connection then requires
authenticatemessage using JWT - Subscription/change/error payloads are documented in WebSocket Protocol
/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.