Disruptions

Planned works, incidents and line status across bus, tram and TfL rail modes, from two feeds normalised into one shape: the DfT's national BODS SIRI-SX situation feed, and TfL's line status for the Tube, DLR, Overground, Elizabeth line and London Trams. Every result carries the source it came from.

National Rail engineering works are not covered — those come from the Network Rail Knowledgebase, which this deployment has no credentials for. The rail mode covers only the handful of rail situations bus operators publish through SIRI-SX.

Fetch disruptions

GET /v1/disruptions.json

By default only disruptions currently in force are returned, and TfL lines running a good service are omitted.

curl "https://api.transcapi.com/v1/disruptions.json?mode=bus,tram&severity=severe" \
  -H "X-Api-Key: YOUR_API_KEY"

Filters

ParameterDescription
modeComma-separated: bus, tram, rail, tube, dlr, overground, elizabeth-line. Default all. tram draws on both feeds.
severityComma-separated: severe, minor, information, good, unknown. Many SIRI-SX situations carry no severity of their own and come back as unknown.
atcocodeOnly disruptions naming this stop as affected.
operatorOperator name or code, matched as a substring.
lineLine or route name, matched as a substring.
lat, lon, radiusRadius in metres (default 2000, max 50000). Provide both lat and lon, or neither.
min_lat, min_lon, max_lat, max_lonBounding box, as an alternative to radius.
path, path_radiusAn encoded polyline (precision 5) and how far off it a stop may be, in metres (default 300, max 5000). See matching a disruption to a route.
plannedtrue for planned works only, false for unplanned incidents only. Omit for both.
active_onlyDefault true. Excludes disruptions whose validity period hasn't started or has ended.
include_good_serviceDefault false. Set true to see every TfL line's status including healthy ones.
limit, pageDefault 50 per page, maximum 100.

Geographic queries can only match a situation that names at least one stop with coordinates. A situation that names only a line — "service 120 diverted" — carries no coordinates and will not be returned by a lat/lon or bounding-box query. Filter by line or operator to find those.

Response

Results are ordered most disruptive first, then most recently raised. Alongside member and total_count, the response carries a sources object reporting when each feed was last retrieved and whether it last failed — so an empty list is distinguishable from a feed outage.

{
  "request_time": "2026-08-06T10:42:11+00:00",
  "page": 1,
  "limit": 50,
  "total_count": 31,
  "results_count": 31,
  "sources": { "bods_sx": { "fetched_at": "...", "error": null }, "tfl": { ... } },
  "member": [
    {
      "id": "RGS-1234",
      "source": "bods_sx",
      "modes": ["bus"],
      "severity": "severe",
      "planned": true,
      "reason": "roadworks",
      "summary": "Corporation Street, Rotherham Town Centre",
      "description": "Stops suspended until further notice.",
      "advice": "Board at the Interchange instead.",
      "starts_at": "2026-08-01T00:00:00+01:00",
      "ends_at": null,
      "created_at": "2026-07-31T14:02:00+01:00",
      "operators": [{ "operator_id": "FSYO", "name": "First South Yorkshire" }],
      "lines": [{ "line": "X1", "line_ref": "X1", "direction": null }],
      "stops_count": 4,
      "stops": [{ "atcocode": "370010001", "name": "...", "latitude": 53.43, "longitude": -1.36 }],
      "url": null
    }
  ]
}

The affected-stop list is capped at 50 entries per situation, because a network-wide notice can name thousands; stops_count always reports the true figure.

Freshness

Both upstream feeds are cached for 5 minutes, so polling more often than that returns the same data. An open-ended situation — no ends_at — is normal for an unplanned incident.

Matching a disruption to a route

Line numbers are not unique nationally, and not even within one operator: First South Yorkshire runs a 21 in Barnsley and a 21 in Rotherham. Filtering by line alone will return both, so a journey through one town gets badged with a disruption from the other.

Pass the route's geometry as path instead. Only situations naming a stop within path_radius metres of it come back:

curl -G "https://api.transcapi.com/v1/disruptions.json" \
  --data-urlencode "path=ktrfIxu`Ln@sJ..." \
  --data-urlencode "path_radius=300" \
  -H "X-Api-Key: YOUR_API_KEY"

This matches against each situation's complete stop list. The 50-stop cap applies only to what the response carries, so a situation naming eight hundred stops is still matched on all of them — which a client filtering the returned stops itself cannot do.

Geography alone is not the whole answer, though. A stop closure a few metres from your route may name a list of lines that doesn't include yours, in which case it doesn't affect you. Combine the two: use path to establish that a situation is where you are going, then check its lines — if it names lines and yours isn't among them, skip it. A situation naming no lines at all affects whatever calls at the stops it lists, whichever operator runs it.

See the changelog for updates as coverage expands.