HTTP API¶
For: anyone writing a client, a script or an agent against a running node.
One port serves all of it (listen.http, 4318 by default). OTLP/gRPC is
a separate port (listen.grpc, 4317) and a separate protocol, so it is
not in this table.
Request and response bodies are KYAML, of which JSON is a subset — so an
ordinary JSON client needs nothing special, and content-type is not
load-bearing.
Each group below is a module, and its rustdoc is the reasoning behind
those routes rather than a restatement of this table:
Ingest is receiver;
Query is api;
Agents is mcp;
Operate is alert and main;
Web UI is ui.
Ingest¶
| Endpoint | What it answers |
|---|---|
POST /v1/logs |
OTLP/HTTP log export. Protobuf or JSON, gzip either way. |
POST /v1/traces |
OTLP/HTTP trace export. Protobuf or JSON, gzip either way. |
POST /v1/metrics |
OTLP/HTTP metric export. Protobuf or JSON, gzip either way. |
Query¶
| Endpoint | What it answers |
|---|---|
POST /api/v1/query |
Log records or spans matching a filter, newest first. |
POST /api/v1/metrics/query |
One metric's series over a window, with the exemplars naming the traces behind the points. |
POST /api/v1/metrics/names |
Which metric names a window holds. An empty body means right now. |
POST /api/v1/correlate |
The frame around a filter: its time extent, the traces it touches and the services that took part — one round trip where a client makes three. |
POST /api/v1/map |
The service map over a window. |
POST /api/v1/entities |
Every service that produced anything in a window. |
Agents¶
| Endpoint | What it answers |
|---|---|
POST /mcp |
The MCP endpoint: JSON-RPC in, one of the eight tools in TOOLS out. |
Operate¶
| Endpoint | What it answers |
|---|---|
GET /health |
Liveness, plus what the ingest path has refused so far. |
GET /readyz |
Readiness: 200 while exports can be made durable, 503 once they cannot. |
GET /api/v1/stats |
Everything this node knows about itself, in one document. |
GET /api/v1/alerts |
Every rule this node evaluates and what it is currently doing. |
Web UI¶
| Endpoint | What it answers |
|---|---|
GET / |
The web UI. One HTML document; every view lives under the location hash. |
GET /{file} |
One of the three assets, or nothing. |
Request bodies¶
Every query endpoint takes a KYAML document and enforces a closed key set: an unknown key is a 400 naming it, rather than a filter that was silently not applied.
from and to are each one of three things: "now", a signed duration
relative to now ("-15m", "+2h"), or Unix nanoseconds as a number.
Left out, the window is the last 1h ending now.
POST /api/v1/query¶
Log records or spans matching a filter, newest first.
Accepts signal, from, to, where, limit, after.
{
"signal": "logs",
"from": "-15m",
"to": "now",
"where": [
{ "attr": "service.name", "eq": "checkout" },
{ "field": "severity_number", "gte": 17 },
{ "attr": "http.route", "contains": "/api" },
],
"limit": 100,
"after": "1757241600000000000.2718281828.7.41",
}
Answers under rows, with the envelope below.
POST /api/v1/metrics/query¶
One metric's series over a window, with the exemplars naming the traces behind the points.
Accepts name, from, to, where, max_series, max_points.
{
"name": "http.server.request.duration",
"from": "-1h",
"where": [ { "attr": "service.name", "eq": "checkout" } ],
"max_series": 50,
}
Answers under series, with the envelope below.
POST /api/v1/metrics/names¶
Which metric names a window holds. An empty body means right now.
Accepts from, to.
An empty body is valid, and means the last 1h.
Answers under names, with the envelope below.
POST /api/v1/correlate¶
The frame around a filter: its time extent, the traces it touches and the services that took part — one round trip where a client makes three.
Accepts signal, from, to, where, limit, after, expand.
{
"signal": "logs",
"from": "-15m",
"where": [ { "field": "severity_number", "gte": 17 } ],
"expand": [ "traces", "around:2s", "peers" ],
}
Answers under frame, with the envelope below.
POST /api/v1/map¶
The service map over a window.
Accepts from, to, max_spans.
An empty body is valid, and means the last 1h.
Answers under map, with the envelope below.
POST /api/v1/entities¶
Every service that produced anything in a window.
Accepts from, to.
An empty body is valid, and means the last 1h.
Answers under entities, with the envelope below.
where terms¶
A term names its target and its operator in two keys rather than three:
{"attr": "service.name", "eq": "checkout"}, not a target/op/value
triple. Terms in a list are ANDed; there is no or.
attr— an OTLP attribute key, on the record, its scope or its resource. Any key at all; nothing is declared in advance.field— a column of the signal itself, from the tables below.
Operators: eq or = or ==, ne or !=, lt or <, lte or <=, gt or >, gte or >=, contains or ~.
field names for signal: logs:
| Field | Compare against |
|---|---|
id |
number |
time_unix_nano |
nanoseconds |
observed_time_unix_nano |
nanoseconds |
severity_number |
number |
severity_text |
string |
event_name |
string |
body |
string |
trace_id |
hex string |
span_id |
hex string |
flags |
number |
dropped_attributes_count |
number |
resource_id |
number |
scope_id |
number |
field names for signal: traces:
| Field | Compare against |
|---|---|
id |
number |
trace_id |
hex string |
span_id |
hex string |
parent_span_id |
hex string |
trace_state |
string |
flags |
number |
name |
string |
kind |
number |
start_time_unix_nano |
nanoseconds |
duration_nano |
number |
status_code |
number |
status_message |
string |
dropped_attributes_count |
number |
dropped_events_count |
number |
dropped_links_count |
number |
resource_id |
number |
scope_id |
number |
The response envelope¶
Every query answers with its own field plus one stats object, so a
caller always learns what the read cost:
{
"rows": [ ... ], # or series, names, frame, map, entities
"stats": {
"blocks_total": 0,
"blocks_scanned": 0,
"rows_scanned": 0,
"rows_matched": 0,
"elapsed_us": 0,
},
"next": "...", # absent on the last page
}
elapsed_us is wall time including the wait for a scan permit — the
number the caller actually waited. next is absent rather than null when
there is no page behind this one, so if (doc.next) is the whole of a
reader's paging logic; pass it back as after.
Status codes¶
| Code | When |
|---|---|
200 |
The answer, as the envelope below. |
400 |
The document did not parse, named a key no endpoint accepts, or gave a value out of range. The body is {"error": "..."} — JSON, like the success, so a caller has one thing to parse. |
500 |
The read failed or its thread panicked. The body is the error text. |
413 |
The export was larger than ingest.max_request_bytes. |
503 |
The signal's queue is full and the export was shed. Retry it: this is backpressure, not a failure, and ingest.queue is how much of it an operator buys away. |
415 |
The export was not protobuf. |
202 |
An OTLP export was queued, or an MCP notification was taken. Accepted means durable when ingest.wal is on and queued when it is off. |
304 |
A UI asset the caller already has. The bundle is built into the binary, so its ETag is its content hash. |
404 |
No such UI asset. |