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:

sourceMeaning
irish_railIrish Rail's own live board for that station: trains, DART and the Enterprise
luas_forecastsTransport 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_gtfsrThe 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_scheduledThe 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.