Guides
Docs
Live UK train departures
This guide builds a live departure board for a National Rail station: expected times, platforms, cancellations and the reason a train is late. The live data is National Rail's Darwin, the same source as the boards at the station. It's merged into the timetable for you, so there's no SOAP, no XML and no separate Darwin account.
What you need
- Python 3.9 or later, and
pip install requests - A TransCAPI key: sign up free,
then
export TRANSCAPI_KEY=your_key - A station's three-letter CRS code:
EDBis Edinburgh Waverley,KGXLondon King's Cross,MANManchester Piccadilly./v1/rail/stations.json?lat=&lon=finds them by location.
The code
import os
from datetime import datetime
from zoneinfo import ZoneInfo
import requests
API = "https://api.transcapi.com/v1"
HEADERS = {"X-Api-Key": os.environ["TRANSCAPI_KEY"]}
UK = ZoneInfo("Europe/London")
def uk_time(iso):
return datetime.fromisoformat(iso).astimezone(UK).strftime("%H:%M") if iso else "--:--"
board = requests.get(f"{API}/rail/station_timetables/EDB.json", headers=HEADERS).json()
for message in board["messages"]:
print("!", message)
for d in board["departures"][:10]:
note = d["cancel_reason"] or d["delay_reason"] or ""
print(f"{uk_time(d['aimed_departure_time'])} exp {uk_time(d['expected_departure_time'])} "
f"plat {d['platform'] or '-':>3} {d['destination']:<24} {d['status']:<9} {note}")
Run it:
$ python train_departures.py
16:29 exp 16:29 plat 17 Aberdeen on_time
16:30 exp 16:30 plat 5 London Kings Cross on_time
16:30 exp 16:30 plat 10 Glasgow Queen Street on_time
16:33 exp 16:33 plat 9 Inverness on_time
16:34 exp 16:34 plat 7 Dunbar on_time
16:38 exp 16:38 plat 12 Dunblane on_time
16:39 exp 16:39 plat 1 Perth on_time
16:40 exp 16:40 plat 4 North Berwick on_time
16:41 exp 16:41 plat 14 Milngavie on_time
16:43 exp 16:43 plat 3 Tweedbank on_time
Real output from Edinburgh Waverley, 29 September 2026. A good afternoon for ScotRail.
What's in each departure
| Field | Meaning |
|---|---|
aimed_departure_time | The timetabled time |
expected_departure_time | Darwin's current estimate; null if it has none |
status | on_time, late, early, delayed (late, no estimate yet) or cancelled |
platform | Where Darwin has one; null otherwise |
delay_reason, cancel_reason | National Rail's wording, e.g. "a points failure" |
trip_id | Opens the whole train at /v1/rail/services/{trip_id}.json |
The board's messages carries National Rail's station notices,
such as lift outages and engineering works, as plain text.
Live and timetabled together
Darwin covers two hours either side of now. Ask for a later window with
datetime and the same endpoint returns the timetable alone, from
Network Rail's schedule, refreshed daily, with live: false. Each
row's source says which you're looking at: darwin for
a train matched to Darwin, gtfs_scheduled for one it didn't list.
Pass live=false to skip Darwin altogether.
If Darwin is unreachable the board still comes back, timetabled, with
live_error saying why. A departure board that shows nothing when
one upstream feed has a bad minute is worse than one that shows the timetable
and says so.
Following one train
/v1/rail/services/{trip_id}.json lists every calling point. For
trains running today, each has its own status,
expected_time, actual_time and platform,
which is enough for a "where's my train" screen.
Attribution
If you display live rail data, National Rail's licence asks you to show "Powered by National Rail Enquiries". The data sources page has every line you need.
All the parameters are in the rail docs.
Try it. A free key gives you 1,000 requests a day with no card. Every endpoint covers Great Britain: see coverage for the parts that are regional.