Open data
Every vehicle in traffic reports its position, and the whole of it is published without an account, a key or a quota. Apps use it with OpenStreetMap to show where a tram is and how long a rider waits at the stop.
The interface
The base is https://api.podnik.rof. Everything answers JSON over HTTPS and everything answers to GET.
-
GET /v1/positions
Where everything is, now
One record for every vehicle in traffic, 12,400 of them at the morning peak and around 900 at three in the morning. Refreshed every ten seconds, and every second for the Lindoma pods. The whole document is about 2.4 MB uncompressed and 260 kB on HTTPS.
Filter it down with
?mode=tram,?town=lindomaor?line=14, in any combination. -
GET /v1/lines
What runs where
Every line, its stops in order, the OpenStreetMap way and node identifiers it follows, the models normally allocated to it, and the timetable in force. Changes rarely. Cache this one.
-
GET /v1/journeys
What is coming, and when
For a stop or a line, the journeys due, each with a scheduled time, a predicted time and a margin of uncertainty in seconds. The prediction comes from the vehicle position, the running time observed on that section over the last twenty minutes, and the time spent waiting at the stops between here and there.
?stop=takes either a Podnik stop identifier or an OpenStreetMap node. -
GET /v1/vehicle/{id}
One vehicle
Position, line, journey, model code, depot of allocation, and for a pod the identifier of the controller supervising it. The model code links straight into the catalogue, so an app can show a rider which vehicle is arriving before it arrives.
Shape of a record
One vehicle from the tram feed on line 14 at Lindoma, on a weekday morning.
GET https://api.podnik.rof/v1/positions?line=14&mode=tram
{
"generated": "2026-07-31T08:14:20Z",
"next_in": 10,
"vehicles": [
{
"id": "LIN-T-04417",
"model": "FER T4",
"line": "14",
"journey": "14-0812-E",
"lat": 52.1174,
"lon": 9.4402,
"bearing": 214,
"speed_kmh": 21.4,
"occupancy": "some_seats",
"next_stop": {
"podnik": "LIN-0412",
"osm_node": 3184402117,
"eta": "2026-07-31T08:15:48Z",
"uncertainty_s": 20
},
"depot": "Lindoma North",
"accessible": true
}
]
}
Method
-
Ask the stop, not the vehicle
Call
/v1/journeys?stop=LIN-0412. The reply already accounts for vehicles that have not yet started their journey, which a position feed alone cannot show. -
Take the difference, then widen it
The waiting time is the predicted time minus the current time. Show the margin of uncertainty with it. A margin of 20 seconds at three stops out becomes 90 seconds at fifteen stops out.
-
Refresh at the rate of the mode
Ten seconds for everything except the pods, which move on a one-second feed. Polling faster returns the same document with the same
generatedstamp. -
Fall back to the timetable
When
predictedis absent the vehicle has not reported for more than two minutes, show the scheduled time and state that it is scheduled. The fieldstale_sgives the length of the silence.
The map underneath
Podnik publishes identifiers, and OpenStreetMap helps place them. Every stop in the feed names the node it matches, and every line gives the ordered ways it runs over. Drawing a live map needs the two together: the feed for the moving points and the map for everything standing still.
The tagging is as follows: stops are public_transport=stop_position with network=Podnik and the Podnik identifier in ref:podnik. Routes use route=tram, route=bus, route=train, route=ferry and, for the Lindoma lanes, route=bus with podnik:pod=yes. Corrections go to OpenStreetMap directly, and the federation surveys the whole network against it twice a year.
The two are published under different terms. The Podnik feed is in the public domain, since it was made with public resources and patents are prohibited here. OpenStreetMap data comes under the Open Database Licence, and an application mixing them stays subject to that licence for the map part.
Other
-
No account, no key, no quota
There is no registration and no agreement to sign. The feed answers the same to a resident with a phone, a commune with a departure board and a company abroad.
-
Mirrors at every regional exchange
Twenty-two mirrors serve the same document. The nearest one answers on
api.podnik.rof, and each is reachable directly asapi-01.podnik.rofthroughapi-22.podnik.rofif the nearest is down. -
Cache the slow things
Lines and stops change a few times a year. Positions change every ten seconds. Fetching the first as often as the second wastes energy at the endpoint.
-
Faults are published
Feed interruptions go on the status page with the cause and the duration, alongside the figure for how much of the year it was running. That has been 99.94 per cent since 2021.