Disruptions

Planned works, incidents and line status across bus, tram, National Rail and TfL modes, from three feeds normalised into one shape: the DfT's national BODS SIRI-SX situation feed, National Rail's Knowledgebase incidents (planned engineering works and unplanned incidents), and TfL's line status for the Tube, DLR, Overground, Elizabeth line and London Trams, and the service alerts of every Dutch operator (from OVapi) and of SNCB, De Lijn and TEC in Belgium. Every result carries the source it came from: bods_siri_sx, nre_kb, tfl, ovapi_gtfsr or bmc_gtfsr. Dutch and Belgian alerts name their stops and lines, so the stop, location and route filters work for them too, and the modes add metro and ferry. Alert text is English where the operator gives it (SNCB does), otherwise Dutch or French. Asking about a place gives times in its local time.

National Rail engineering works and incidents

mode=rail returns them, alongside any rail situations bus operators publish through SIRI-SX. Each names the train operators affected, exactly as National Rail lists them, and links to National Rail's page for it in url. advice carries the affected routes as National Rail writes them ("Between Birmingham New Street and Coventry").

Stations. National Rail describes routes in words rather than station codes, so TransCAPI finds the stations named in the affected-routes text and lists them in stops with their crs_code, name and position. That's what lets atcocode=HUD (a CRS code works there for rail), a radius or bounding box, and path find rail incidents. About 98% of incidents name at least one station; a notice such as "Various Southeastern routes" names none and so can't match a geographic query. Where a closure affects through services, their other ends are listed too: a Holyhead closure lists Birmingham New Street and Crewe because those trains don't run.

Severity. National Rail grades almost nothing, so severity is read from the summary: "buses replace trains", "no trains" or "disruption" is severe; "amended", "changes to", "delays" or a line reopening is minor; anything else information. Most are planned works days or weeks ahead: they appear once they start, or pass active=false to see what's coming.

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.