Realtime feeds
Three GTFS-realtime protobuf files from SL — vehicle positions, trip updates and service alerts — decoded with proto2 semantics intact. A field the agency never set stays absent rather than defaulting to zero.
Absent is not zero →Where the Try it consoles send requests. Stored in this browser only. The API needs no key — why.
GTFS-realtime, as JSON you can actually use
Vehicle positions, arrival predictions and service alerts from Storstockholms Lokaltrafik — decoded from protobuf, joined against the national GTFS archive so every id has a name, and served as JSON that never invents a value it wasn't given.
No key, no sign-up, no token. Point at a host and start asking questions — which is also why the Try it consoles here run for real in your browser.
export API_BASE=https://gtfs-realtime-api.broad-darkness-36e7.workers.dev
# What was running, and where, at feed time
curl "$API_BASE/api/vehicles?limit=3"
# Everything calling at Tyresö centrum — a station id, platforms expanded for you
curl "$API_BASE/api/stops/778/arrivals?limit=3" Prefer to click? Every endpoint page has a Try it console that runs the request from your browser and shows the real response. Try one now →
Three GTFS-realtime protobuf files from SL — vehicle positions, trip updates and service alerts — decoded with proto2 semantics intact. A field the agency never set stays absent rather than defaulting to zero.
Absent is not zero → 7,880 routes and 181,369 stops from the national archive,
joined on trip_id. This is what turns 9011001087500000 into
“bus 875” — and what supplies the route id the realtime feed almost never sends.
Every trip update carries a predicted delay for each of its remaining calls, so a single snapshot yields 15,451 observations of how delay drifts with distance. That is enough for an empirical forecast: give it a trip and it will tell you the odds it is still late in five stops' time — with the sample size and the slice it actually used, so you can tell a real answer from a fallback.
Not on the public API. These four endpoints read every trip and stop time
event on each call, which the deployed Worker's database backend can't do economically — so
they run only against a local instance and return 404 on the public URL. Documented
in full anyway, because they're the most interesting thing here.
How the forecasts work, and what they can't do →
7 short pages covering the parts of this API that will otherwise surprise you.
The data is a fixed capture from 27 April 2026. Time-based filters compare against your clock, not the feed's — the single most likely thing to confuse you.
02The feed is proto2. A field that was never set is omitted entirely — no occupancy reading is not 0% full, and no delay is not on time. Check presence, not truthiness.
03A stop time event may carry a delay with no absolute time. Deriving one from the other needs the scheduled timetable, which this API does not load. You get what the feed gave.
04The realtime feed almost never carries route_id — 2 in 1,000 vehicles. Joining trip_id against the static GTFS archive takes coverage to 96%, and marks every backfilled value.
05SL's two feeds reference stops at different levels: trip updates use platform ids, alerts use station ids. Stop endpoints accept either and tell you what they searched.
06One snapshot of trip updates yields thousands of delay-versus-horizon observations. That is enough for an empirical forecast — and not enough for several things you might want one for.
07No key, no rate limit, and CORS open to any origin — so a browser can call it directly. What that means for calling it from a page, and the error shapes you can rely on.
19 endpoints. Every one has a runnable console.
/health Liveness, uptime and a one-line feed summary. GET /api Machine-readable list of every endpoint and its filters. GET /api/feed Feed header, per-file load report, and static-join statistics. POST /api/feed/reload Re-read the data directory and rebuild every index. GET /api/vehicles Vehicle positions, freshest first, with route and stop names joined on. GET /api/vehicles/:id One vehicle by id. GET /api/trips Predicted arrivals and departures per trip, freshest first. GET /api/trips/:tripId One trip with every stop time update, untruncated. GET /api/alerts Service alerts, soonest-starting first, with an isActive flag. GET /api/alerts/:id One alert by feed entity id. GET /api/routes Routes present in the feed, named, with per-route entity counts. GET /api/routes/:routeId Everything the feed says about one route, in a single response. GET /api/stops Every stop id mentioned anywhere in the feed, named, with counts. GET /api/stops/:stopId/arrivals Predicted calls at a stop, earliest first, across all trips. GET /api/stops/:stopId/alerts Alerts affecting a stop, with the same station/platform expansion. GET /api/punctuality What the forecasts are built from, and — importantly — what they cannot do. GET /api/punctuality/trips/:tripId A delay forecast for every remaining call of a trip that is in the feed. GET /api/punctuality/predict A forecast for a trip this snapshot does not carry — one being planned, say. GET /api/punctuality/baseline How punctual the network is right now, as a distribution and a histogram.