Guides
- UK bus times in Python
- Live UK train departures
- Journey planning that knows about hills
- Where can I get to in 30 minutes?
- How do I get to the airport?
- Irish Rail, Dublin Bus and Belfast in one API
- How reliable is my bus?
Docs
Irish Rail, Dublin Bus and Belfast in one API
Ireland's transport data is open, but it's spread out. The Republic's timetables and live buses come from the National Transport Authority, behind a developer key and a limit of a few requests a minute. Irish Rail's live trains are a separate XML API. Northern Ireland's timetables are Translink's, published in another format again. This guide uses all of it through one key and one JSON shape: a live board at Dublin Connolly, a Dublin Bus stop with live times, and a journey from Belfast to Dublin across the border.
What you need
- Python 3.9 or later, and
pip install requests - A TransCAPI key: sign up free,
then
export TRANSCAPI_KEY=your_key
No NTA key, no Irish Rail polling and no TransXChange parsing. TransCAPI fetches the live feeds once a minute for everyone, so your app's requests don't count against the NTA's limit.
The code
import os
from datetime import date, timedelta
import requests
API = "https://api.transcapi.com/v1"
HEADERS = {"X-Api-Key": os.environ["TRANSCAPI_KEY"]}
def get(path, **params):
r = requests.get(f"{API}{path}", params=params, headers=HEADERS)
r.raise_for_status()
return r.json()
def clock(iso):
# Times come in the place's own local time, so the clock is in the string.
return iso[11:16] if iso else "--:--"
# 1. A station by name, then its departures with Irish Rail's live times.
station = next(p for p in get("/places.json", query="Connolly")["member"]
if p["type"] == "train_station")
board = get(f"/bus/stop_timetables/{station['atcocode']}.json", limit=5)
print(f"{station['name']} ({board['timezone']})")
for d in board["departures"]:
print(f" {clock(d['aimed_departure_time'])} exp {clock(d['expected_departure_time'])} "
f"{d['line'] or d['mode']:<9} {d['destination']:<28} {d['source']}")
# 2. The nearest Dublin Bus stop to O'Connell Bridge, with the NTA's live times.
stop = get("/bus/stops.json", lat=53.3473, lon=-6.2591, radius=200, type="bus_stop")["member"][0]
board = get(f"/bus/stop_timetables/{stop['atcocode']}.json", limit=5)
print(f"\n{stop['name']} ({stop['atcocode']})")
for d in board["departures"]:
print(f" {clock(d['aimed_departure_time'])} exp {clock(d['expected_departure_time'])} "
f"{d['line']:<5} {d['destination']:<32} {d['status']}")
# 3. Belfast to Dublin, across the border, at 8am tomorrow.
tomorrow = date.today() + timedelta(days=1)
plans = get("/journey.json", from_lat=54.5947, from_lon=-5.9398, # Belfast Grand Central
to_lat=53.3531, to_lon=-6.2464, # Dublin, Connolly
datetime=f"{tomorrow}T08:00:00", limit=3)
print("\nBelfast to Dublin")
for it in plans["itineraries"]:
rides = [f"{leg['route']['operator']} {leg['route']['line'] or leg['mode'].lower()}"
for leg in it["legs"] if leg["mode"] != "WALK"]
print(f" {clock(it['start_time'])} -> {clock(it['end_time'])} {' + '.join(rides)}")
Run it:
$ python ireland.py
Connolly (Europe/Dublin)
17:58 exp 17:58 DART Malahide nta_gtfsr
18:09 exp 18:09 DART Grand Canal Dock nta_gtfsr
18:10 exp 18:10 rail Drogheda (MacBride) nta_gtfsr
18:17 exp 18:17 rail Maynooth nta_gtfsr
18:18 exp 18:18 DART Howth Junction and Donaghmede nta_gtfsr
O'Connell Br (8220DB000273)
17:57 exp 17:57 700 Clayton Hotel on_time
18:00 exp 18:00 4 Monkstown Ave on_time
18:07 exp 18:07 7 Brides Glen on_time
18:14 exp 18:14 19 Merrion Square on_time
18:15 exp 18:15 4 Monkstown Ave on_time
Belfast to Dublin
08:27 -> 11:00 Goldliner X2 + Dublin Express 783
08:27 -> 10:58 Goldliner X2 + Dublin Express 783 + LUAS Red
08:59 -> 11:19 Iarnród Éireann / Irish Rail Belfast - Dublin
Real output, Sunday 25 October 2026, early evening.
Where the live times come from
Each departure's source says what it's based on:
source | Meaning |
|---|---|
irish_rail | Irish Rail's own live board for that station: trains, DART and the Enterprise |
luas_forecasts | Transport Infrastructure Ireland's forecasts for the Luas, from the trams' tracked positions. A Luas board's messages carries the line's status when there's a problem |
nta_gtfsr | The NTA's live feed: Dublin Bus, Bus Éireann, Go-Ahead Ireland and the other operators it covers, and trains when Irish Rail's board has nothing better |
gtfs_scheduled | The timetable, with no live update for that departure |
A cancelled journey, or a stop it will skip, comes back with
status: "cancelled". /v1/bus/vehicles.json gives the
positions of the Republic's buses, each linked to its trip.
Times
Every time is in the local time of the place asked about, with its offset,
and the response's timezone names the zone:
Europe/Dublin in the Republic, Europe/London in
Northern Ireland, the same clock either way. That's why the script can cut
the clock straight out of the string. A datetime without an
offset, like the journey's 08:00:00, is read as local time.
Across the border
The island is planned as one network: Translink's Ulsterbus, Goldline and
NI Railways, the Enterprise, and everything in the Republic. A journey from
Britain to Ireland returns 422 outside_coverage rather than
nothing, because there's no ferry in the planner.
What's not covered yet
- Northern Ireland has timetables but no live data, because Translink doesn't publish an open live feed.
- Fares are the Luas only (in euros, on journey legs); no bus or Irish Rail fares yet.
Attribution
The NTA's data is CC BY 4.0, and Translink's comes through OpenDataNI. The data sources page has the exact credit lines. The bus docs and journey docs have every parameter.
Try it. A free key gives you 100 requests a day with no card. Every endpoint covers Great Britain: see coverage for the parts that are regional.