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: EDB is Edinburgh Waverley, KGX London King's Cross, MAN Manchester 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

FieldMeaning
aimed_departure_timeThe timetabled time
expected_departure_timeDarwin's current estimate; null if it has none
statuson_time, late, early, delayed (late, no estimate yet) or cancelled
platformWhere Darwin has one; null otherwise
delay_reason, cancel_reasonNational Rail's wording, e.g. "a points failure"
trip_idOpens 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.