UK bus times in Python

This guide takes a UK postcode, finds the bus stops nearest to it, and lists what's leaving each one in the next hour. It works anywhere in England, Scotland and Wales. It uses three endpoints and the requests library.

What you need

  • Python 3.9 or later, and pip install requests
  • A TransCAPI key: sign up free, then export TRANSCAPI_KEY=your_key

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):
    """API times are UTC; show them as a UK clock time."""
    return datetime.fromisoformat(iso).astimezone(UK).strftime("%H:%M")


# 1. Turn a postcode into a point
place = requests.get(f"{API}/places.json", params={"query": "CB2 3QZ", "type": "postcode"},
                     headers=HEADERS).json()["member"][0]

# 2. Find the nearest bus stops, closest first
stops = requests.get(f"{API}/bus/stops.json",
                     params={"lat": place["latitude"], "lon": place["longitude"], "radius": 400},
                     headers=HEADERS).json()["member"]

# 3. What's leaving each of the three nearest in the next hour
for stop in stops[:3]:
    print(f"\n{stop['name']} ({stop['indicator']}), towards {stop['towards']}")
    board = requests.get(f"{API}/bus/stop_timetables/{stop['atcocode']}.json",
                         params={"to_offset": 60}, headers=HEADERS).json()
    for d in board["departures"][:5]:
        when = uk_time(d.get("expected_departure_time") or d["aimed_departure_time"])
        late = f"  ({round(d['delay_seconds'] / 60)} min late)" if d.get("delay_seconds") else ""
        print(f"  {when}  {d['line']:>4}  {d['destination']}{late}")
    if not board["departures"]:
        print("  nothing in the next hour")

Run it:

$ python bus_times.py

Bene't Street (opp), towards Dane Drive
  nothing in the next hour

St Andrew's Street (Stop S3), towards Arbury Campkin Road
  16:32     1  Arbury Campkin Road
  16:40     3  Thorpe Way 1St Stop
  16:44     1  Arbury Campkin Road
  16:55     3  Thorpe Way 1St Stop
  16:56     1  Arbury Campkin Road

St Andrew's Street (Stop S2), towards Milton Tesco
  16:28     2  Milton Tesco
  16:48     2  Milton Tesco
  17:08     2  Milton Tesco

Real output from central Cambridge, 29 September 2026.

How it works

1. Postcode to a point

/v1/places.json answers towns, villages, postcodes and stops. type=postcode keeps it to postcodes. Full postcodes come from Ordnance Survey's Code-Point Open, and a partial one like CB2 lists the postcodes it starts. Leave type off to let people type a town name instead.

2. The nearest stops

/v1/bus/stops.json with lat, lon and a radius in metres returns stops closest first. Each has towards, the next significant place its buses head for. Show it beside the name: most stops come in pairs on opposite sides of the road, and towards is how a passenger tells them apart.

3. The departure board

/v1/bus/stop_timetables/{atcocode}.json lists departures in a window you set with from_offset and to_offset (minutes from now). Every time is ISO 8601 in UTC, which is why the example converts to Europe/London. Printing the raw string would be an hour out all summer.

Where TransCAPI tracks a bus against its timetable, the departure carries expected_departure_time and delay_seconds. Where it doesn't, you get the timetabled time and source: "gtfs_scheduled", so you can label the two differently. Tracked delays currently cover northern England and West Sussex. Elsewhere, add live=true to get the position of the bus running the trip where the operator reports it.

Taking it further

  • type=arrivals lists what's arriving, including buses that terminate at the stop.
  • Each departure's trip_id opens the whole journey at /v1/bus/services/{trip_id}.json: every stop, and the live bus if it's running.
  • /v1/bus/vehicles.json?lat=&lon=&radius= gives every bus reporting its position nearby, for a map.
  • /v1/disruptions.json?atcocode= tells you about roadworks and diversions affecting the stop.

All the parameters are in the bus 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.