HTTP API

The native JSON API that M0 serves for namespaces, streams 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.

This API will grow

This page covers the M0 endpoints. The query API, Flight SQL and the Qdrant and Elasticsearch surfaces arrive with their M1 milestones. Loam does not emulate Neo4j Bolt or the ClickHouse interface (decisions D44 and D45).

Health

Method and pathAnswers
GET /health200 while the process is up
GET /ready200 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/{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 parameterDefaultMeaning
offsetrequiredThe first offset to read
max_bytes1 MiBUpper bound on the response; capped at 16 MiB
max_wait_ms0Long-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.

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}.

{
  "id": 1,
  "name": "totals",
  "source": "events",
  "target": { "kind": "counter", "name": "totals" },
  "options": {},
  "version": 1,
  "applied": [{ "partition": 0, "offset": 2 }, { "partition": 1, "offset": 1 }],
  "counters": { "a": 3, "b": 10 },
  "skipped": 0
}

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 }
StatuserrorWhen
400invalid_argumentA malformed body, path or query, or bad base64
404not_foundAn unknown namespace, stream, partition, link or route
405invalid_argumentA known route with a method it doesn't serve
409already_existsThe name is taken; the body carries the existing id
413invalid_argumentThe body is over 16 MiB
416offset_out_of_rangeA fetch outside the partition's offsets
503unavailableBackpressure, no metastore leader, an object-store error, or an append whose outcome is unknown. Retry; an unknown append may already be committed
500internalA bug or corrupt data

On this page