Developer documentation

UK public transport data in one API

Search national stops and stations, resolve source identifiers, and explore operators, routes and ordered calling patterns across bus, rail, tram and metro datasets.

1. Create an account

Verify your email and open the account dashboard.

Sign up

2. Create a key

Name the key and copy it when shown. Plaintext is displayed once.

3. Send requests

Include the key in the X-API-Key header.

Your first rail-to-bus integration

Resolve a station, fetch its next departures, then discover nearby bus stops and published routes. These are three authenticated requests; there is no automatic polling.

1. Resolve PWY

/v1/resolve?crs=PWY

Keep the returned id; do not invent a UKPT identifier.

2. Read departures

/v1/map/locations/{id}/departures?limit=5

URL-encode the id as a single path segment. This is the existing map-board API, not a new versioned departures contract.

3. Find onward buses

/v2/rail/stations/PWY/nearby-bus-services

Request bounded stops and routes. Published routes are not guaranteed timed connections.

cURL: resolve a station

Set UKPT_API_KEY privately. Avoid shell tracing or shared logs.

curl --fail --max-time 15 -H "X-API-Key: $UKPT_API_KEY" "https://api.ukpt.botric.co.uk/v1/resolve?crs=PWY"
Python 3: resolve a station
import json, os
from urllib.request import Request, build_opener, HTTPRedirectHandler

class NoRedirect(HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        return None  # Never forward the key to another host.

request = Request(
    "https://api.ukpt.botric.co.uk/v1/resolve?crs=PWY",
    headers={"X-API-Key": os.environ["UKPT_API_KEY"]},
)
with build_opener(NoRedirect()).open(request, timeout=15) as response:
    station = json.load(response)
print(station["id"], station["name"])

The standard library raises HTTPError for non-success responses. Apply the error guidance below before adding retries.

JavaScript: runnable rail-to-bus example

Download and inspect the source, then run it on your server with Node.js 20 or later. Set your own key privately in the environment; never embed it in browser JavaScript.

Download rail-to-bus.mjs
# UKPT_API_KEY must already be set in your environment.
node rail-to-bus.mjs PWY

Outputs station, departures and buses objects. Requests time out after 15 seconds, reject redirects and stop on errors without printing your key. No packages or local server are required.

Read the data honestly

  • UKPT IDs are opaque strings. CRS identifies railway stations; ATCO/NaPTAN identifies source stops. Use resolve rather than assuming IDs are interchangeable.
  • generated_at is response creation time. source_observed_at is the latest contributing live update where available; null means freshness is unknown, not current.
  • aimed_departure is scheduled; expected_departure is an available prediction. Check live, cancelled and calendar_status. A GPS position alone is not an arrival prediction.
  • Empty departures mean no current results from available imports, not that no services run. Null fields are unknown or unavailable, never zero, false or confirmed access.
  • ISO timestamps carry timezone information. Display passenger times in Europe/London, including clock changes. Date-only service_date describes published validity, not confirmed weekday or holiday operation.
  • Nearby distances are straight-line metres. Read stops_truncated and services_truncated before treating a bounded list as complete. Nearby route results may be cached for one hour.

A minimal empty board can look like the example below. This is illustrative, not a live observation; consult OpenAPI for the full schema.

{
  "location_id": "ukpt:location:rail:EXAMPLE",
  "location_name": "Example station",
  "mode": "rail",
  "departures": [],
  "message": "No current departures are available from the imported live/timetable data.",
  "generated_at": "2026-10-01T12:00:00Z",
  "source_observed_at": null
}

Coverage and limitations · Rail notices

Complete runnable examples

Each rail-to-bus workflow resolves a station, reads a bounded board and finds onward bus routes. Keep keys in your server environment and respect quota headers.

For trams, search /v1/locations/search?q=your-stop, check mode and locality, then pass the returned ID to departures.mjs. /v1/passenger/stops/{id}/routes lists routes; /v1/passenger/coverage describes imported inventory. Tram schedules may be available without live estimates. The /partners screen view is a bounded local departures-display example.

Explore a real departure response

Choose a public bus, train, tram or metro stop. This limited example uses the website proxy. Run your integration on your server with your own key.

    Check service_date (Europe/London), source_status, source_observed_at and each row’s status. generated_at is the response time. Empty, scheduled, stale and unsupported results have different meanings.

    Errors, limits and polling

    Read the HTTP status before parsing a success schema. Error bodies normally contain detail (a string or validation list); do not treat them as an empty board.

    StatusWhat to do
    400 / 422Correct missing identifiers or invalid parameters; do not retry unchanged input.
    401Check the X-API-Key header and whether your key is revoked or expired.
    404The identifier is not available; nearby-station lookup also requires coordinates.
    409Resolve returned ambiguous or conflicting identifiers. Ask for a more specific identifier.
    429Stop requests. Minute throttles include Retry-After seconds. Monthly exhaustion includes Retry-After seconds and X-MonthlyLimit-Reset (Unix seconds).
    503The service or quota checks are unavailable. Keep old information labelled stale/unavailable; use bounded backoff rather than rapid retries.

    Free accounts allow 10,000 requests per UTC calendar month and 60 per minute. Read X-RateLimit-Limit/Remaining/Reset and X-MonthlyLimit-Limit/Remaining/Reset when supplied. Free account allowances are shared across keys, including rotated keys. Internal keys without an account retain their own allowances. Authenticated requests can consume allowance before endpoint validation, including errors and cached responses.

    For a small prototype, refresh a visible departure board no faster than every 60 seconds and reuse station IDs and nearby-route results. Continuous one-minute polling uses about 43,200 requests in a 30-day month, beyond the free allowance. Stop when hidden and budget across all endpoints. This recommendation is not a freshness guarantee.

    Use the interactive OpenAPI reference for current parameter ranges and schemas. v1 and v2 coexist; a larger version number does not replace every v1 endpoint. No new deprecation guarantee is introduced by this guide.

    Sources and configured refresh schedule

    Schedules use Europe/London time and describe configured imports, not successful observations or guaranteed freshness. BODS processes one operator code per hour, not the whole catalogue each hour.

    View detailed source schedule
    DatasetGathered fromConfigured refreshCoverage
    Bus stops and localitiesNaPTAN and NPTG, Department for TransportDaily overnightNames, coordinates, ATCO/NaPTAN codes and locality hierarchy
    Rail-replacement stopsDfT NaPTAN Rail Replacement LocationsDaily at 00:45Official replacement-stop locations, ATCO codes and station links
    Bus services, routes and calling patternsBus Open Data Service (BODS), TransXChangeOne NOC every hour; full catalogue continuously cyclesEnglish registered bus timetables, route numbers, LineRef and ServiceCode
    Bus operators and NOC codesBODS operator catalogueRefreshed during each hourly NOC passOperator identities, NOC codes and dataset import state
    Live bus vehicle positionsBus Open Data Service (BODS), SIRI-VMPolled every 15 seconds; positions expire after 10 minutesVehicle references, lines, journeys, GPS coordinates, bearings and destinations where supplied
    Rail stations and identifiersNational Rail Knowledgebase and Network Rail CORPUSKnowledgebase daily at 06:00; CORPUS weekly SundayStations, accessibility, CRS, TIPLOC, STANOX, NLC and UIC identifiers
    Planned trains and replacement busesNetwork Rail Schedule/CIFDaily at 05:50; rolling horizon up to about 12 weeksPlanned calling patterns, UIDs, TOCs and category BR replacement buses
    Near-term rail timetableNational Rail Darwin Timetable FilesChecked daily at 05:40Dated operational timetables and planned station calls
    Live trains and replacement servicesNational Rail Darwin realtime KafkaContinuous, normally within secondsRIDs, estimates, actual times, platforms and cancellations
    London transportTransport for London Unified APIDaily at 05:00London Underground, bus, tram and other supported TfL lines
    Regional tram and metroTraveline National Dataset (TNDS)Weekly Sunday at 05:30Manchester Metrolink, Sheffield Supertram and other available urban networks

    Location name search

    curl -H "X-API-Key: ukpt_your_key" \
      "https://api.ukpt.botric.co.uk/v1/locations/search?q=Manchester%20Piccadilly"

    Endpoints

    MethodPathPurpose
    GET/v1/bus/vehicles/{registration}/motKnown bus registrations: recorded DVSA MOT/annual tests, cached 24 hours; ready/stale/not_found/unavailable status and checked_at. Not a current roadworthiness guarantee.
    GET/v1/locationsList and filter transport locations
    GET/v1/locations/search?q=PiccadillySearch locations by name
    GET/v1/locations/near?lat=&lon=&radius=Find nearby locations
    GET/v1/operators?mode=tramList operators by mode
    GET/v1/operators/{id}/routesList routes for one operator
    GET/v1/routes?mode=metroList routes by mode
    GET/v1/routes/{id}/stopsGet a route’s ordered stops
    GET/v1/resolve?crs=&naptan=&atco=&tiploc=Resolve source identifiers
    GET/v1/map/locations/{id}/departures?limit=5Read the existing location departure board (limit 1–30)
    GET/v1/rail/operators/{toc}/summaryCompact operator counts, cached 60 seconds, with latest rail receipt time
    GET/v1/rail/operator-directoryLightweight operator names and service status; no national timetable or realtime count scan
    GET/v1/rail/operators/{toc}/track-networkConfigured rail operators: OSM route-member GeoJSON, monthly checks, source dates and ODbL attribution; not train positions
    GET/v1/rail/operators/{toc}/station-networkTimetable-derived station snapshot, cached 30 days, checked_at and truncation flag; no track geometry
    GET/v2/locations/RailReplacementList tagged rail-replacement stops
    GET/v2/rail/stations/{crs}/nearby-bus-services?distance=500Find bus operators and routes near a rail station
    GET/v1/rail/railreplacement?date=YYYY-MM-DD&toc=XXList live and 12-week planned replacements; TOC is optional
    GET/v1/rail/railreplacement/{rid}Get a replacement journey and linked ATCO stops
    GET/v1/rail/railreplacement/{uid}/{date}Get a future or live replacement by UID and date
    GET/v1/rail/train/{rid}Get an ordered rail journey by RID
    GET/v1/rail/disruption-snapshotBounded active/unexpired rail notices, operator indicators and successful feed import freshness
    GET/v1/tfl/railList TfL rail, Tube, DLR and tram lines
    GET/v1/tfl/busList TfL bus lines and matched operating-company tags
    GET/v1/tfl/stationsList TfL stations
    GET/v1/tfl/bus-stopsList TfL bus stops
    GET/v1/tfl/operatorsList operators matched to TfL lines
    GET/v1/tfl/search?q=FirstLondonSearch TfL lines, stops, stations and operators
    GET/v1/tfl/bus/{route_number}Get a TfL bus route with operators, patterns and identified stops
    GET/v1/tfl/rail/{line_name}Get all calling patterns and stops for a TfL rail line
    GET/v1/tfl/rail/statusGet current TfL rail network status

    Station-to-bus discovery

    Use a station CRS code to find nearby bus stops and published routes. Request a compact response for passenger interfaces:

    curl -H "X-API-Key: ukpt_your_key" "https://api.ukpt.botric.co.uk/v2/rail/stations/PWY/nearby-bus-services?distance=500&stop_limit=6&service_limit=30&include_calling_patterns=false"

    Distances are straight-line metres, not walking routes or guaranteed transfers. Optional service_date=YYYY-MM-DD selects published service validity periods (default: today in the UK); it does not confirm weekday, holiday or live operation. Stops and services each accept limits from 1 to 500. Calling patterns are included by default.

    Responses include stop coordinates, service_date, generated_at, stops_truncated and services_truncated. Each parameter combination is cached for up to one hour. The generated timestamp describes the list, not an upstream observation or departure update.

    Authentication

    Issue, rotate and revoke keys from the account dashboard. Keep keys server-side and never publish them in URLs, screenshots or source control. See the limits and error guidance above before adding polling.

    Coverage

    NaPTAN/NPTG supplies national locations, BODS supplies English bus timetables, TfL supplies London modes, TNDS supplies selected tram and metro timetables, and DfT supplies rail-replacement locations. Network Rail and National Rail feeds supply rail identifiers, schedules, live running updates, incidents, accessibility and operator status.

    Open interactive API reference