HTTP API
The native JSON API for namespaces, streams, CloudEvents and links.
The native API addresses namespaces, streams and links by name. Request and response bodies are JSON; record keys, values and header values are base64. Request bodies are limited to 16 MiB.
Scope of this page
This page covers the namespace, stream, CloudEvents and link endpoints. The same port also serves
the collection, document, hybrid query (POST /v1/namespaces/{ns}/query) and SQL
(POST /v1/namespaces/{ns}/sql) routes and the Connect RPCs; see the API reference.
Loams does not emulate Neo4j Bolt (decision D44).
With LOAMS_AUTH_MODE set, every route except /health and /ready needs a credential, such as
Authorization: Bearer lk_… for an API key. See the CLI guide.
Health
| Method and path | Answers |
|---|---|
GET /health | 200 while the process is up |
GET /ready | 200 once the metastore has a leader, 503 before |
Namespaces
POST /v1/namespaces
{ "name": "acme" }Answers 201 with {"id": 1}.
Streams
POST /v1/namespaces/{ns}/streams
{
"name": "events",
"partitions": 2,
"retention": { "max_age_ms": 604800000, "max_bytes": 1073741824 }
}retention and each of its fields are optional; without them, records are kept forever. Streams created here use the standard WAL class. Answers 201 with {"id": 1}.
GET /v1/namespaces/{ns}/streams
Lists the namespace's streams, sorted by name. retention appears only when one is set.
{ "streams": [{ "name": "events", "partitions": 2 }] }GET /v1/namespaces/{ns}/streams/{stream}
{
"id": 1,
"partitions": [
{ "partition": 0, "log_start_offset": 0, "high_watermark": 2 },
{ "partition": 1, "log_start_offset": 0, "high_watermark": 1 }
],
"retention": { "max_age_ms": null, "max_bytes": null }
}high_watermark is the offset the next record will get. log_start_offset rises as retention trims the partition.
Records
POST /v1/namespaces/{ns}/streams/{stream}/partitions/{partition}/records
Appends records to one partition, atomically.
{
"records": [
{
"key": "YQ==",
"value": "NQ==",
"headers": [{ "key": "source", "value": "YXBw" }],
"timestamp_ms": 1790272455527
}
]
}Every field of a record is optional. Without timestamp_ms, the writer's clock is used. The reply comes once the records are durable:
{
"base_offset": 0,
"last_offset": 0,
"token": [{ "stream": 1, "partition": 0, "offset": 0 }]
}GET /v1/namespaces/{ns}/streams/{stream}/partitions/{partition}/records
| Query parameter | Default | Meaning |
|---|---|---|
offset | required | The first offset to read |
max_bytes | 1 MiB | Upper bound on the response; capped at 16 MiB |
max_wait_ms | 0 | Long-poll for up to this long when no records are ready; capped at 60000 |
{
"records": [
{ "offset": 0, "key": "YQ==", "value": "NQ==", "headers": [], "timestamp_ms": 1790272455527 }
],
"next_offset": 1,
"high_watermark": 1,
"log_start_offset": 0
}Continue from next_offset. An offset outside [log_start_offset, high_watermark] answers 416.
CloudEvents
POST /v1/namespaces/{ns}/streams/{stream}/events
Appends CloudEvents in binary mode (ce-* headers), structured mode (Content-Type: application/cloudevents+json) or batch mode (application/cloudevents-batch+json). Each source + id is appended once within the dedup window (1 hour by default, --cloudevents-dedup-window). An optional partition query parameter picks the partition.
{
"events": [{ "status": "appended", "partition": 0, "offset": 3 }],
"token": [{ "stream": 1, "partition": 0, "offset": 3 }]
}Each event's status is appended, duplicate (with the first write's partition and offset) or in_flight. Any in_flight event makes the answer 409 with Retry-After. A body in none of the three modes answers 415.
GET /v1/namespaces/{ns}/streams/{stream}/partitions/{partition}/events
Takes the same offset, max_bytes and max_wait_ms parameters as the records fetch and answers a CloudEvents batch, with the Loams-Next-Offset and Loams-High-Watermark headers. mode=binary answers one event in binary mode, or 204 when none is ready.
Links
POST /v1/namespaces/{ns}/links
{
"name": "totals",
"source": "events",
"target": { "kind": "counter", "name": "totals" },
"options": {}
}source is a stream in the same namespace. target defaults to a counter named after the link, and options defaults to empty. Answers 201 with {"id": 1}.
GET /v1/namespaces/{ns}/links
Lists the namespace's links, sorted by name, each with its source, target and status: running, or unregistered when no worker can apply its target kind.
{ "links": [{ "name": "totals", "source": "events", "target": { "kind": "counter", "name": "totals" }, "status": "running" }] }GET /v1/namespaces/{ns}/links/{link}
{
"id": 1,
"name": "totals",
"source": "events",
"target": { "kind": "counter", "name": "totals" },
"options": {},
"status": "running",
"version": 1,
"applied": [{ "partition": 0, "offset": 2 }, { "partition": 1, "offset": 1 }],
"lag": [{ "partition": 0, "records": 0 }, { "partition": 1, "records": 0 }],
"counters": { "a": 3, "b": 10 },
"skipped": 0
}Every link reports its status and per-partition lag, the records between the source's high watermark and the applied offset. For counter targets the response adds the table's version, the per-partition applied offsets (the next offset the link will read), the summed counters and the number of skipped records.
Errors
Every error has the same body, sometimes with extra fields:
{ "error": "offset_out_of_range", "message": "offset 99 out of range: log start 0, high watermark 2",
"offset": 99, "log_start_offset": 0, "high_watermark": 2 }| Status | error | When |
|---|---|---|
400 | invalid_argument | A malformed body, path or query, or bad base64 |
401 | unauthenticated | Authentication is on and the request has no valid credential |
403 | permission_denied | A valid credential this engine does not admit, such as a user off the allowlist |
404 | not_found | An unknown namespace, stream, partition, link or route |
405 | invalid_argument | A known route with a method it doesn't serve |
409 | already_exists | The name is taken; the body carries the existing id |
409 | conflict | A metastore conflict, such as a fenced or version-mismatched write |
413 | invalid_argument | The body is over 16 MiB |
415 | unsupported_media_type | A CloudEvents request in no supported mode |
416 | offset_out_of_range | A fetch outside the partition's offsets |
429 | resource_exhausted | Collection write backpressure; retry after Retry-After |
503 | unavailable | Log backpressure, no metastore leader, an object-store error, a stale object, or an append whose outcome is unknown. Carries Retry-After: 1. Retry; an unknown append may already be committed |
500 | internal | A bug or corrupt data |