Quickstart
From an API key to a live event stream in minutes.
Get from zero to a live, authenticated event stream with a single curl. The
stream is Server-Sent Events (SSE) over plain HTTP, so anything that can read an
HTTP response can consume it.
Get an API key
Mint a scoped key with the CLI. The plaintext key is shown once at creation, so copy it immediately.
livestrom apikey create --name my-app --scopes stream:read
# → lstr_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX (shown once)Export it so the snippets below can read it:
export LIVESTROM_KEY="lstr_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"Stream live events
Point curl -N (no buffering) at /v1/stream with your key in the X-API-Key
header. Events arrive as SSE frames as soon as the Engine detects them.
curl -N -H "X-API-Key: $LIVESTROM_KEY" https://<host>/v1/streamYou can also present the key as a bearer token:
curl -N -H "Authorization: Bearer $LIVESTROM_KEY" https://<host>/v1/streamEach frame carries an id (also the resume cursor), an event label
(the source_type), and a data line with one StreamEvent JSON object:
id: 018f9c2e-1a2b-7c3d-8e4f-5a6b7c8d9e0f
event: rss
data: {"id":"018f9c2e-1a2b-7c3d-8e4f-5a6b7c8d9e0f","schema_version":1,"source_id":"svt-nyheter","source_type":"rss","url":"https://example.se/artikel","title":"...","content":"...","tags":["kommun"],"fetched_at":"2026-07-01T12:00:00Z","emitted_at":"2026-07-01T12:00:01Z"}Filter server-side
Narrow the stream to just the slice you care about with repeatable query
parameters: source, source_type, tag, and keyword.
curl -N -H "X-API-Key: $LIVESTROM_KEY" \
"https://<host>/v1/stream?source_type=rss&tag=kommun"A trailing .* on a source value is a prefix match (for example
?source=kommun.*). Filters are capped per key; an over-cap filter returns a
documented 400.
Resume without gaps
SSE reconnects automatically and replays from the last id it saw via the
Last-Event-ID request header, so a dropped connection resumes gap-free:
curl -N \
-H "X-API-Key: $LIVESTROM_KEY" \
-H "Last-Event-ID: 018f9c2e-1a2b-7c3d-8e4f-5a6b7c8d9e0f" \
https://<host>/v1/streamTo backfill from a known point on a fresh connection, use ?since=<event-id>:
curl -N -H "X-API-Key: $LIVESTROM_KEY" \
"https://<host>/v1/stream?since=018f9c2e-1a2b-7c3d-8e4f-5a6b7c8d9e0f"Expired cursors
A Last-Event-ID older than the retained backfill window returns
410 Gone. Resync from now (drop the cursor) and continue tailing live.
Consume from JavaScript
The browser's native EventSource API cannot set request headers, so a bare
new EventSource("/v1/stream") gets a 401 from our auth middleware. Read the
stream with fetch() and a streaming body reader instead.
Primary: fetch() with a streaming reader
// Node 18+ / modern browser
const res = await fetch("https://<host>/v1/stream", {
headers: { "X-API-Key": process.env.LIVESTROM_KEY }
});
const reader = res.body.getReader(); // a ReadableStream reader
const dec = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
process.stdout.write(dec.decode(value));
}For automatic reconnect and Last-Event-ID handling that matches native
EventSource behaviour, use the @microsoft/fetch-event-source npm package (a
convenience wrapper for your own project, not a dependency of these docs):
import { fetchEventSource } from "@microsoft/fetch-event-source";
await fetchEventSource("https://<host>/v1/stream", {
headers: { "X-API-Key": process.env.LIVESTROM_KEY },
onmessage(ev) {
console.log(ev.id, JSON.parse(ev.data));
},
});Why not plain EventSource?
The browser's EventSource API cannot set request headers. Use fetch() with
a streaming reader, or a wrapper like @microsoft/fetch-event-source, for
authenticated access.
Fallback: query-param key (less secure)
If you must use bare EventSource, pass the key as a query parameter. Note the
key then appears in server access logs and browser history, so prefer the
header-based approaches above.
// Less secure: key visible in logs and history
const es = new EventSource(`https://<host>/v1/stream?api_key=${key}`);
es.onmessage = (ev) => console.log(JSON.parse(ev.data));Latency by source tier
Expected latency depends on the source_type of each event:
- Firehose sources arrive in seconds.
- RSS / JSON feeds arrive in minutes.
- Feed-less HTML-diff sources arrive in minutes to tens of minutes.
Errors
Every error on the boundary is an RFC 9457
application/problem+json document with type, title, status, and detail
fields. The statuses you will see on /v1/stream:
| Status | Meaning |
|---|---|
400 | Malformed or over-cap filter (too many source/tag/keyword terms for your key). |
401 | Missing or invalid API key. |
403 | The key lacks the required stream:read scope. |
410 | The Last-Event-ID cursor is older than the retained window. Resync from now. |
429 | Per-key rate limit exceeded. Honour the Retry-After header. |
See the API Reference for the full operation and the
StreamEvent schema.