Section 06 of 12
Streams
Two endpoints push updates over server-sent events (SSE) instead of being polled: departures at a stop and vehicles on a line. Streams need a plan with the streams entitlement (every paid plan, including Pay as you go); on Free they answer 402 plan_required.
Endpoints#
| Route | Parameters | Interval |
|---|---|---|
GET /stops/{stop_id}/departures/stream | interval, horizon, limit, line, direction | 15–120 s, default 20 |
GET /lines/{line_id}/vehicles/stream | interval | 10–120 s, default 10 |
The response is text/event-stream with Cache-Control: no-cache. Ask for it with a client that keeps the connection open:
curl -N "https://api.quavern.net/v1/stops/tbm:stop:3824/departures/stream?interval=20&limit=5" \
-H "Authorization: Bearer qv_p_…"Events#
Each event is event: <name> followed by one data: line of JSON, then a blank line.
| Event | When | Payload |
|---|---|---|
meta | first, once | {"plan", "interval", "max_duration"} |
departures | every interval seconds | {"data": [Departure…], "meta": {…}}, identical to the REST response |
vehicles | every interval seconds | {"data": [Vehicle…], "meta": {…}} |
error | once, then the stream closes | {"code", "reason", "message"} |
A comment line : ping is sent every 15 seconds to keep intermediaries from closing an idle connection; SSE clients ignore it. The server closes the stream after max_duration (600 s); reconnect to continue.
event: meta
data: {"plan":"pro","interval":20,"max_duration":600}
event: departures
data: {"data":[{"stop_id":"tbm:stop:3824","line_code":"B","headsign":"Berges de la Garonne","expected_at":"2026-09-07T14:13:20+02:00","realtime":true,"status":"delayed"}],"meta":{"generated_at":"2026-09-07T12:11:03Z","units":1,"freshness":"realtime"}}
: pingUnits#
Every departures or vehicles event costs 1 unit, charged as it is sent; meta, pings and error cost nothing. A stream at the default departures interval therefore costs 3 units per minute. When the monthly budget runs out mid-stream the server sends error with reason quota_exhausted and closes.
Concurrency#
Each key may hold a bounded number of streams at once, set by the plan: 2 on Pay as you go and Starter, 3 on Pro, 8 on Max, 16 on Enterprise. Opening one more answers 429 stream_limit_reached. The service also has a global ceiling; when it is reached the same error is returned even below your own limit, so treat it as a retry-later condition (back off 5 to 30 seconds).
Client notes#
The browser EventSource API cannot set an Authorization header, and keys must not reach browsers anyway: consume streams from a server process and relay to your clients if needed. With fetch, read response.body as a stream and split on blank lines. Reconnect on close with a short, jittered delay, and prefer polling the REST endpoint if you need fewer than one update every 20 seconds.