api.nrf.rof, version 1
Six endpoints, JSON, no key, no quota, no registration. Vehicle positions are not here; they belong to Podnik at api.podnik.rof and are documented by the transport federation. A client that wants a departure board joins the two.
Endpoints
| Path | Answers | Cache |
|---|---|---|
| /v1/stations | The 145 stations: identifier, name, sub-region, platforms, whether a gate stands there, step-free access, the fittings on the access page. | 1 day |
| /v1/routes | The 36 routes: number, name, group, the station sequence, length, end-to-end time, traction. | 1 day |
| /v1/timetable | Departures for a station and a date, or a whole route. Published times, platforms and the service identifier that joins to the Podnik feed. | 1 hour |
| /v1/platforms | Platform allocation for a station and a day, which moves more often than the timetable does. | 5 min |
| /v1/changes | What moved: time changes, platform changes, cancellations, with the cooperative that decided each and when. | 1 min |
| /v1/reports | Rider reports and the answers to them, published together under Article 61(2). POST files one. | 5 min |
Shapes
GET /v1/timetable?stop=LIN-CTR&from=2026-08-02T08:00
{
"stop": "LIN-CTR",
"generated": "2026-08-02T08:14:02+02:00",
"departures": [
{
"service": "S-35-0812",
"route": 35,
"group": "northern-coast",
"destination": "TOR-001",
"published": "2026-08-02T08:20:00+02:00",
"platform": 2,
"calls": [
{
"station": "NUR-001",
"published": "2026-08-02T08:41:00+02:00"
},
{
"station": "ORT-001",
"published": "2026-08-02T09:22:00+02:00"
},
{
"station": "TOR-001",
"published": "2026-08-02T10:40:00+02:00"
}
],
"vehicle_hint": "SED-R9"
}
]
}
GET /v1/changes?since=2026-07-30T00:00
{
"changes": [
{
"id": "C-2026-07-30-0114",
"service": "S-13-1440",
"route": 13,
"kind": "platform",
"was": 2,
"now": 4,
"at": "VES-001",
"decided_by": "southern/duty-office",
"decided_at": "2026-07-30T11:26:00+02:00",
"reason": "engineering possession on platform 2 until 12 August"
}
]
}
POST /v1/reports
Content-Type: application/json
{
"kind": "station",
"station": "SOR-001",
"vehicle": null,
"text": "The lift in the north subway is out of use.",
"reply_to": null
}
201 Created
{
"id": "R-2026-08-02-0338",
"group": "south-east-coast",
"acknowledge_by": "2026-08-05",
"published": "https://nrf.rof/reports/R-2026-08-02-0338"
}
Rules of the road
-
Conditional requests
Every response comes with an ETag and Last-Modified. A conditional request that returns 304 costs nothing and is not counted against anything, because nothing is counted.
-
Polling
The change feed is regenerated once a minute and platforms once every five. Polling faster returns the same document with the same ETag; there is no rate limit and no benefit.
-
Errors
Problem details in RFC 9457, with
type,titleanddetailin the language of the Accept-Language header where the message has a translation. -
Bulk
The whole timetable for a period is one gzip at
/v1/timetable/bulk, published on the day the period opens. A board in a shelter runs off that and the change feed alone.
Joining the two
A published time comes from here. Where a vehicle actually is comes from Podnik. The service identifier in the timetable is the join: pass it to the Podnik journeys endpoint and the reply returns the vehicle, its position, its estimate and the margin on that estimate.
Podnik documents its own interface at api.podnik.rof and sets its own terms; the federation neither proxies it nor mirrors it. board.py shows the join in about eighty lines.