{"openapi":"3.1.0","info":{"title":"Live Transit API","version":"1.0.0","summary":"Real-time public transport status from official sources.","description":"Airport boards, metros, trains, buses and ferries worldwide, taken from official operator feeds. No authentication, CORS enabled. Changes are also pushed as a Server-Sent Events stream and to webhooks. Every response is `{ data, meta }`. Items carry `updatedAt`/`lastSeenAt`, and sources carry a `health` of live, stale, failing or pending."},"servers":[{"url":"/","description":"Site origin"}],"tags":[{"name":"Meta"},{"name":"Sources"},{"name":"Alerts"},{"name":"Network"},{"name":"Stations"},{"name":"Vehicles"},{"name":"Search"},{"name":"Realtime"}],"paths":{"/api/v1/overview":{"get":{"summary":"Global overview","description":"Headline counts across all sources: source health, active alerts by severity, lines, stations, vehicles and departures.","operationId":"getOverview","tags":["Meta"],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/Overview"},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"sources":{"total":60,"live":54,"stale":3,"failing":1,"pending":2},"countries":22,"cities":40,"activeAlerts":310,"alertsBySeverity":{"info":120,"minor":100,"major":80,"severe":10},"lines":400,"linesDisrupted":31,"stations":900,"stationsDelayed":44,"vehicles":12000,"departures":25000,"lastIngestAt":"2026-11-04T11:59:00.000Z"},"meta":{"generatedAt":"2026-11-04T12:00:00.000Z","count":1}}}}},"500":{"$ref":"#/components/responses/ServerError"}}}},"/api/v1/sources":{"get":{"summary":"List sources","description":"Official data sources (operators, airports) with freshness. Each source has a `health` of live, stale, failing or pending.","operationId":"listSources","tags":["Sources"],"parameters":[{"name":"country","in":"query","description":"ISO 3166-1 alpha-2 country code, e.g. `gb`.","required":false,"schema":{"type":"string","pattern":"^[A-Za-z]{2}$"},"example":"gb"},{"name":"mode","in":"query","description":"Transport mode.","required":false,"schema":{"$ref":"#/components/schemas/Mode"},"example":"metro"},{"name":"region","in":"query","description":"Region slug.","required":false,"schema":{"type":"string","enum":["africa","asia","europe","latam","north-america","oceania","middle-east"]}},{"name":"q","in":"query","description":"Free-text match on name, operator and city.","required":false,"schema":{"type":"string"}},{"name":"health","in":"query","description":"Filter by health.","required":false,"schema":{"$ref":"#/components/schemas/Health"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Source"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":[{"id":"gb-london-tfl","name":"TfL Status","operator":"Transport for London","country":"gb","city":"London","region":"europe","modes":["metro","rail"],"kind":"api","homepage":"https://tfl.gov.uk","license":"TfL Open Data","attribution":"Powered by TfL Open Data","intervalMinutes":2,"lat":51.5,"lon":-0.12,"health":"live","lastSuccessAt":"2026-11-04T11:59:00.000Z","lastRunAt":"2026-11-04T11:59:00.000Z","lastError":null,"counts":{"lines":20,"alerts":14},"activeAlerts":14,"disruptedLines":3}],"meta":{"generatedAt":"2026-11-04T12:00:00.000Z","count":1}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/api/v1/sources/{id}":{"get":{"summary":"Get a source","description":"A source with its lines, active alerts, stations and the 30 most recent ingest runs.","operationId":"getSource","tags":["Sources"],"parameters":[{"name":"id","in":"path","required":true,"description":"Source id.","schema":{"type":"string"},"example":"gb-london-tfl"}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/SourceDetail"},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"id":"gb-london-tfl","name":"TfL Status","operator":"Transport for London","country":"gb","city":"London","region":"europe","modes":["metro","rail"],"kind":"api","homepage":"https://tfl.gov.uk","license":"TfL Open Data","attribution":"Powered by TfL Open Data","intervalMinutes":2,"lat":51.5,"lon":-0.12,"health":"live","lastSuccessAt":"2026-11-04T11:59:00.000Z","lastRunAt":"2026-11-04T11:59:00.000Z","lastError":null,"counts":{"lines":20,"alerts":14},"activeAlerts":14,"disruptedLines":3,"endpoints":["https://api.tfl.gov.uk/Line/Mode/tube,dlr/Status"],"lines":[],"alerts":[],"stations":[],"recentRuns":[]},"meta":{"generatedAt":"2026-11-04T12:00:00.000Z","count":1}}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/api/v1/health":{"get":{"summary":"Ingest health","description":"Per-source freshness and failure details. `summary` counts sources per health value.","operationId":"getHealth","tags":["Meta"],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/HealthReport"},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"summary":{"live":1,"stale":0,"failing":0,"pending":0},"sources":[{"id":"gb-london-tfl","name":"TfL Status","country":"gb","health":"live","intervalMinutes":2,"lastSuccessAt":"2026-11-04T11:59:00.000Z","lastRunAt":"2026-11-04T11:59:00.000Z","lastError":null,"consecutiveFailures":0,"lastDurationMs":420,"counts":{"lines":20}}]},"meta":{"generatedAt":"2026-11-04T12:00:00.000Z","count":1}}}}},"500":{"$ref":"#/components/responses/ServerError"}}}},"/api/v1/planned":{"get":{"summary":"Planned sources","description":"Known official sources not ingested yet, usually because they require a registration key.","operationId":"listPlanned","tags":["Meta"],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PlannedSource"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":[{"name":"Example Metro","country":"xx","modes":["metro"],"url":"https://example.org/open-data","reason":"Requires an API key"}],"meta":{"generatedAt":"2026-11-04T12:00:00.000Z","count":1}}}}},"500":{"$ref":"#/components/responses/ServerError"}}}},"/api/v1/search":{"get":{"summary":"Search","description":"Ranked search across sources, stations/airports, lines and active alerts.","operationId":"search","tags":["Search"],"parameters":[{"name":"q","in":"query","description":"Search text, at least 2 characters.","required":true,"schema":{"type":"string","minLength":2},"example":"heathrow"},{"name":"limit","in":"query","description":"Maximum items to return (default 20, max 100).","required":false,"schema":{"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SearchHit"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":[{"type":"station","id":"LHR","sourceId":"gb-airport-lhr","title":"London Heathrow","subtitle":"London · GB · air","status":"normal","score":3.2}],"meta":{"generatedAt":"2026-11-04T12:00:00.000Z","count":1}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/api/v1/routes":{"get":{"summary":"List routes (static catalog)","description":"Routes from static GTFS feeds with representative shapes for mapping. Requires sourceId, bbox, or both. When filtering by bbox, routes are ordered by mode priority then name.","operationId":"listRoutes","tags":["Network"],"parameters":[{"name":"sourceId","in":"query","description":"Restrict to one source.","required":false,"schema":{"type":"string"}},{"name":"bbox","in":"query","description":"Bounding box as minLon,minLat,maxLon,maxLat. Routes intersecting this area.","required":false,"schema":{"type":"string"}},{"name":"mode","in":"query","description":"Transport mode.","required":false,"schema":{"$ref":"#/components/schemas/Mode"},"example":"metro"},{"name":"geometry","in":"query","description":"Include encoded shapes. Default false.","required":false,"schema":{"type":"string","enum":["true","false"]}},{"name":"limit","in":"query","description":"Maximum items to return (default 500, max 5000).","required":false,"schema":{"type":"integer","minimum":1,"maximum":5000}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Route"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":[{"sourceId":"gb-london-tfl","routeId":"central","name":"Central","shortName":null,"longName":"Central line","mode":"metro","color":"DC241F","textColor":"FFFFFF","bbox":[-0.512,51.452,0.071,51.613]}],"meta":{"generatedAt":"2026-11-04T12:00:00.000Z","count":1}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/api/v1/stops":{"get":{"summary":"Stops in an area","description":"Stops and stations from static GTFS inside a bounding box, one per boarding place (platforms folded into their station). Covers timetable-only networks too.","operationId":"listStops","tags":["Network"],"parameters":[{"name":"bbox","in":"query","description":"Bounding box as minLon,minLat,maxLon,maxLat.","required":true,"schema":{"type":"string"},"example":"-122.42,37.77,-122.38,37.80"},{"name":"limit","in":"query","description":"Maximum items to return (default 3000, max 10000).","required":false,"schema":{"type":"integer","minimum":1,"maximum":10000}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Stop"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":[{"sourceId":"us-sf-bart","name":"Embarcadero","lat":37.79289,"lon":-122.39689}],"meta":{"generatedAt":"2026-11-04T12:00:00.000Z","count":1}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/api/v1/geo":{"get":{"summary":"Route geometry files for an area","description":"Route geometry is published as immutable JSON files, one per source per 0.5 degree cell. This lists the files covering a bounding box; fetch each `url` for `{ sourceId, routes: [[routeId, name, mode, color, [encoded polyline pieces]]] }`. Files are cached for a year and replaced, under new URLs, when a source's timetable changes.","operationId":"listGeoTiles","tags":["Network"],"parameters":[{"name":"bbox","in":"query","description":"Bounding box as minLon,minLat,maxLon,maxLat.","required":true,"schema":{"type":"string"},"example":"-122.5,37.6,-122.1,37.9"},{"name":"sourceId","in":"query","description":"Restrict to one source.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Maximum items to return (default 400, max 2000).","required":false,"schema":{"type":"integer","minimum":1,"maximum":2000}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/GeoTile"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":[{"sourceId":"us-sf-bart","tx":-245,"ty":75,"url":"https://example.public.blob.vercel-storage.com/geo/v1/us-sf-bart/-245_75-abc.json","bytes":18213,"routes":14}],"meta":{"generatedAt":"2026-11-04T12:00:00.000Z","count":1}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/api/v1/alerts":{"get":{"summary":"List active alerts","description":"Active service alerts ordered by severity, then newest. Keyset paginated: pass `meta.nextCursor` as `cursor`.","operationId":"listAlerts","tags":["Alerts"],"parameters":[{"name":"country","in":"query","description":"ISO 3166-1 alpha-2 country code, e.g. `gb`.","required":false,"schema":{"type":"string","pattern":"^[A-Za-z]{2}$"},"example":"gb"},{"name":"city","in":"query","description":"City name, case-insensitive match.","required":false,"schema":{"type":"string"},"example":"London"},{"name":"mode","in":"query","description":"Transport mode.","required":false,"schema":{"$ref":"#/components/schemas/Mode"},"example":"metro"},{"name":"severity","in":"query","description":"Exact severity.","required":false,"schema":{"$ref":"#/components/schemas/Severity"}},{"name":"minSeverity","in":"query","description":"Minimum severity (info < minor < major < severe).","required":false,"schema":{"$ref":"#/components/schemas/Severity"}},{"name":"sourceId","in":"query","description":"Restrict to one source.","required":false,"schema":{"type":"string"}},{"name":"q","in":"query","description":"Free-text match on title, description and routes.","required":false,"schema":{"type":"string"}},{"name":"since","in":"query","description":"Only alerts first seen at or after this time.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"limit","in":"query","description":"Maximum items to return (default 50, max 500).","required":false,"schema":{"type":"integer","minimum":1,"maximum":500}},{"name":"cursor","in":"query","description":"Opaque cursor from a previous page.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Alert"}},"meta":{"$ref":"#/components/schemas/PagedMeta"}}},"example":{"data":[{"sourceId":"gb-london-tfl","sourceName":"TfL Status","country":"gb","city":"London","id":"a1","severity":"major","effect":"REDUCED_SERVICE","cause":null,"title":"Central line: severe delays","description":"Signal failure at Bank.","url":null,"modes":["metro"],"routes":["Central"],"stops":["Bank"],"activeFrom":"2026-11-04T10:00:00.000Z","activeUntil":null,"active":true,"firstSeenAt":"2026-11-04T10:01:00.000Z","lastSeenAt":"2026-11-04T11:59:00.000Z","resolvedAt":null}],"meta":{"generatedAt":"2026-11-04T12:00:00.000Z","count":1}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/api/v1/lines":{"get":{"summary":"List lines","description":"Current status of lines and routes (up to 2000).","operationId":"listLines","tags":["Network"],"parameters":[{"name":"sourceId","in":"query","description":"Restrict to one source.","required":false,"schema":{"type":"string"}},{"name":"country","in":"query","description":"ISO 3166-1 alpha-2 country code, e.g. `gb`.","required":false,"schema":{"type":"string","pattern":"^[A-Za-z]{2}$"},"example":"gb"},{"name":"mode","in":"query","description":"Transport mode.","required":false,"schema":{"$ref":"#/components/schemas/Mode"},"example":"metro"},{"name":"disrupted","in":"query","description":"`true` returns only lines whose status is not good or unknown.","required":false,"schema":{"type":"string","enum":["true","false"]}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Line"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":[{"sourceId":"gb-london-tfl","sourceName":"TfL Status","country":"gb","city":"London","lineId":"central","lineName":"Central","mode":"metro","color":"DC241F","status":"major","statusText":"Severe delays","since":"2026-11-04T10:00:00.000Z","updatedAt":"2026-11-04T11:59:00.000Z"}],"meta":{"generatedAt":"2026-11-04T12:00:00.000Z","count":1}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/api/v1/changes":{"get":{"summary":"Recent status changes","description":"Status transitions of lines and stations, newest first.","operationId":"listChanges","tags":["Network"],"parameters":[{"name":"since","in":"query","description":"Only changes at or after this time.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"sourceId","in":"query","description":"Restrict to one source.","required":false,"schema":{"type":"string"}},{"name":"country","in":"query","description":"ISO 3166-1 alpha-2 country code, e.g. `gb`.","required":false,"schema":{"type":"string","pattern":"^[A-Za-z]{2}$"},"example":"gb"},{"name":"limit","in":"query","description":"Maximum items to return (default 100, max 500).","required":false,"schema":{"type":"integer","minimum":1,"maximum":500}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Change"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":[{"id":"1","sourceId":"gb-london-tfl","sourceName":"TfL Status","country":"gb","city":"London","subject":"line","subjectId":"central","subjectName":"Central","fromStatus":"good","toStatus":"major","statusText":"Severe delays","at":"2026-11-04T10:00:00.000Z"}],"meta":{"generatedAt":"2026-11-04T12:00:00.000Z","count":1}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/api/v1/sources/{id}/lines/{lineId}":{"get":{"summary":"Line detail","description":"Complete information about one line: status, live vehicles, shapes for mapping, active alerts and recent status history.","operationId":"getLineDetail","tags":["Network"],"parameters":[{"name":"id","in":"path","required":true,"description":"Source id","schema":{"type":"string"},"example":"gb-london-tfl"},{"name":"lineId","in":"path","required":true,"description":"Line id","schema":{"type":"string"},"example":"central"}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/LineDetail"},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"sourceId":"gb-london-tfl","lineId":"central","name":"Central","longName":"Central line","mode":"metro","color":"DC241F","textColor":"FFFFFF","status":{"sourceId":"gb-london-tfl","sourceName":"TfL Status","country":"gb","city":"London","lineId":"central","lineName":"Central","mode":"metro","color":"DC241F","status":"major","statusText":"Severe delays","since":"2026-11-04T10:00:00.000Z","updatedAt":"2026-11-04T11:59:00.000Z"},"shapes":["_p~iF~ps|U_ulLnnqC"],"vehicles":[{"sourceId":"gb-london-tfl","id":"1234","mode":"metro","routeName":"Central","lat":51.5,"lon":-0.1,"bearing":90,"delaySeconds":120,"observedAt":"2026-11-04T12:00:00.000Z"}],"alerts":[{"sourceId":"gb-london-tfl","sourceName":"TfL Status","country":"gb","city":"London","id":"a1","severity":"major","effect":"REDUCED_SERVICE","cause":null,"title":"Central line: severe delays","description":"Signal failure at Bank.","url":null,"modes":["metro"],"routes":["Central"],"stops":["Bank"],"activeFrom":"2026-11-04T10:00:00.000Z","activeUntil":null,"active":true,"firstSeenAt":"2026-11-04T10:01:00.000Z","lastSeenAt":"2026-11-04T11:59:00.000Z","resolvedAt":null}],"history":[{"from":"good","to":"major","text":"Severe delays","at":"2026-11-04T10:00:00.000Z"},{"from":"minor","to":"good","text":"Good service","at":"2026-11-04T08:00:00.000Z"}]},"meta":{"generatedAt":"2026-11-04T12:00:00.000Z","count":1}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/api/v1/sources/{id}/live":{"get":{"summary":"Live vehicles for a source","description":"Vehicle positions read from the operator's realtime feed at request time when the source supports it (shared for about 10 seconds), otherwise the last ingested snapshot. `meta.live` says which one was served.","operationId":"getLiveVehicles","tags":["Vehicles"],"parameters":[{"name":"id","in":"path","required":true,"description":"Source id","schema":{"type":"string"},"example":"gtfs-bg-sofia-sofia-traffic"},{"name":"routeId","in":"query","description":"Only vehicles on this route id.","required":false,"schema":{"type":"string"},"example":"TM33"}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Vehicle"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":[{"sourceId":"gb-london-tfl","id":"v1","mode":"metro","routeId":"central","routeName":"Central","color":"DC241F","lat":51.5,"lon":-0.1,"bearing":90,"observedAt":"2026-11-04T12:00:00.000Z"},{"sourceId":"gb-london-tfl","id":"v2","mode":"bus","routeName":"38","lat":51.52,"lon":-0.12,"bearing":180,"delaySeconds":60,"observedAt":"2026-11-04T12:00:00.000Z"}],"meta":{"generatedAt":"2026-11-04T12:00:00.000Z","count":2}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/api/v1/stations":{"get":{"summary":"List stations and airports","description":"Stations and airports with current status, worst first.","operationId":"listStations","tags":["Stations"],"parameters":[{"name":"mode","in":"query","description":"Transport mode.","required":false,"schema":{"$ref":"#/components/schemas/Mode"},"example":"metro"},{"name":"country","in":"query","description":"ISO 3166-1 alpha-2 country code, e.g. `gb`.","required":false,"schema":{"type":"string","pattern":"^[A-Za-z]{2}$"},"example":"gb"},{"name":"status","in":"query","description":"Station status.","required":false,"schema":{"type":"string","enum":["normal","delays","major_delays","closed","unknown"]}},{"name":"q","in":"query","description":"Match on name, city or exact id.","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","description":"Maximum items to return (default 200, max 1000).","required":false,"schema":{"type":"integer","minimum":1,"maximum":1000}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Station"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":[{"sourceId":"gb-airport-lhr","stationId":"LHR","name":"London Heathrow","mode":"air","city":"London","country":"gb","lat":51.47,"lon":-0.45,"status":"delays","statusText":"Minor delays","delayMinutes":12,"since":"2026-11-04T09:00:00.000Z","updatedAt":"2026-11-04T11:59:00.000Z"}],"meta":{"generatedAt":"2026-11-04T12:00:00.000Z","count":1}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/api/v1/stations/{id}":{"get":{"summary":"Get a station","description":"A station or airport. When several sources report it, `reports` has one entry each.","operationId":"getStation","tags":["Stations"],"parameters":[{"name":"id","in":"path","required":true,"description":"Station or airport id. IATA codes are case-insensitive.","schema":{"type":"string"},"example":"LHR"}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"$ref":"#/components/schemas/StationDetail"},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":{"sourceId":"gb-airport-lhr","stationId":"LHR","name":"London Heathrow","mode":"air","city":"London","country":"gb","lat":51.47,"lon":-0.45,"status":"delays","statusText":"Minor delays","delayMinutes":12,"since":"2026-11-04T09:00:00.000Z","updatedAt":"2026-11-04T11:59:00.000Z","reports":[{"sourceId":"gb-airport-lhr","stationId":"LHR","name":"London Heathrow","mode":"air","city":"London","country":"gb","lat":51.47,"lon":-0.45,"status":"delays","statusText":"Minor delays","delayMinutes":12,"since":"2026-11-04T09:00:00.000Z","updatedAt":"2026-11-04T11:59:00.000Z"}]},"meta":{"generatedAt":"2026-11-04T12:00:00.000Z","count":1}}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/api/v1/stations/{id}/board":{"get":{"summary":"Departures and arrivals board","description":"Live board for a station or airport, ordered by estimated or scheduled time.","operationId":"getBoard","tags":["Stations"],"parameters":[{"name":"id","in":"path","required":true,"description":"Station or airport id.","schema":{"type":"string"},"example":"LHR"},{"name":"direction","in":"query","description":"Departures or arrivals only.","required":false,"schema":{"type":"string","enum":["departure","arrival"]}},{"name":"from","in":"query","description":"Earliest time to include (default: 30 minutes ago).","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"limit","in":"query","description":"Maximum items to return (default 60, max 500).","required":false,"schema":{"type":"integer","minimum":1,"maximum":500}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Departure"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":[{"sourceId":"gb-airport-lhr","id":"LHR:departure:BA117:2026-11-04T12:30:00Z","stationId":"LHR","stationName":"London Heathrow","direction":"departure","mode":"air","service":"BA117","carrier":"British Airways","counterpart":"New York JFK","scheduledAt":"2026-11-04T12:30:00.000Z","estimatedAt":"2026-11-04T12:45:00.000Z","platform":"A12","status":"delayed","statusText":"Delayed 15 min","delayMinutes":15}],"meta":{"generatedAt":"2026-11-04T12:00:00.000Z","count":1}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/api/v1/services/{q}":{"get":{"summary":"Find a flight or train number","description":"Looks up a flight or train number across every board. Spaces and dashes are ignored, case-insensitive.","operationId":"findService","tags":["Stations"],"parameters":[{"name":"q","in":"path","required":true,"description":"Flight or train number.","schema":{"type":"string"},"example":"BA117"},{"name":"limit","in":"query","description":"Maximum items to return (default 50, max 200).","required":false,"schema":{"type":"integer","minimum":1,"maximum":200}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Departure"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":[{"sourceId":"gb-airport-lhr","id":"LHR:departure:BA117:2026-11-04T12:30:00Z","stationId":"LHR","stationName":"London Heathrow","direction":"departure","mode":"air","service":"BA117","carrier":"British Airways","counterpart":"New York JFK","scheduledAt":"2026-11-04T12:30:00.000Z","estimatedAt":"2026-11-04T12:45:00.000Z","platform":"A12","status":"delayed","statusText":"Delayed 15 min","delayMinutes":15}],"meta":{"generatedAt":"2026-11-04T12:00:00.000Z","count":1}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/api/v1/vehicles":{"get":{"summary":"Live vehicles","description":"Vehicle positions. Provide `bbox`, `sourceId`, or `lat`+`lon`+`radiusKm` (nearest first, adds `distanceKm`). Cached for 10 seconds.","operationId":"listVehicles","tags":["Vehicles"],"parameters":[{"name":"bbox","in":"query","description":"Bounding box `minLon,minLat,maxLon,maxLat`.","required":false,"schema":{"type":"string"},"example":"-122.5,37.7,-122.3,37.9"},{"name":"sourceId","in":"query","description":"Vehicles of one source.","required":false,"schema":{"type":"string"}},{"name":"lat","in":"query","description":"Centre latitude.","required":false,"schema":{"type":"number","minimum":-90,"maximum":90}},{"name":"lon","in":"query","description":"Centre longitude.","required":false,"schema":{"type":"number","minimum":-180,"maximum":180}},{"name":"radiusKm","in":"query","description":"Radius in km (0.1 to 100).","required":false,"schema":{"type":"number","minimum":0.1,"maximum":100}},{"name":"mode","in":"query","description":"Transport mode.","required":false,"schema":{"$ref":"#/components/schemas/Mode"},"example":"metro"},{"name":"limit","in":"query","description":"Maximum items to return (default 2000, max 5000).","required":false,"schema":{"type":"integer","minimum":1,"maximum":5000}}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Vehicle"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":[{"sourceId":"us-sf-bart","id":"v1","mode":"metro","routeId":"RED","routeName":"Red Line","tripId":"t1","headsign":"Richmond","lat":37.79,"lon":-122.4,"bearing":90,"speed":12.4,"label":"1234","delaySeconds":60,"observedAt":"2026-11-04T11:59:30.000Z"}],"meta":{"generatedAt":"2026-11-04T12:00:00.000Z","count":1}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/ServerError"}}}},"/api/v1/stream":{"get":{"tags":["Realtime"],"operationId":"streamChanges","summary":"Stream changes as they happen","description":"Server-Sent Events. Each line, station or alert change is sent as an `event: change` message whose `id` is a cursor, as soon as it is ingested. Connections close after about five minutes; EventSource clients reconnect with `Last-Event-ID` and receive what they missed. Pass `after` to start from a cursor without that header.","parameters":[{"name":"sourceId","in":"query","description":"Comma separated network ids.","required":false,"schema":{"type":"string"},"example":"at-vienna-wiener-linien"},{"name":"country","in":"query","description":"Comma separated ISO 3166-1 alpha-2 codes.","required":false,"schema":{"type":"string"},"example":"at,de"},{"name":"subject","in":"query","description":"Comma separated kinds: line, station, alert.","required":false,"schema":{"type":"string"},"example":"line,alert"},{"name":"after","in":"query","description":"Replay events after this cursor.","required":false,"schema":{"type":"string"},"example":"48200"}],"responses":{"200":{"description":"An event stream of `Change` objects.","content":{"text/event-stream":{"schema":{"type":"string"},"example":"id: 48213\nevent: change\ndata: {\"id\":\"48213\",\"at\":\"2026-11-06T08:14:03.000Z\",\"sourceId\":\"at-vienna-wiener-linien\",\"sourceName\":\"Wiener Linien\",\"country\":\"at\",\"subject\":\"line\",\"subjectId\":\"U6\",\"subjectName\":\"U6\",\"from\":\"good\",\"to\":\"major\",\"text\":\"Delays after a signal fault\",\"severity\":null,\"url\":\"https://livetransit.vercel.app/n/at-vienna-wiener-linien/l/U6\"}\n\n"}}},"400":{"$ref":"#/components/responses/BadRequest"}}}},"/api/v1/webhooks":{"post":{"tags":["Realtime"],"operationId":"createWebhook","summary":"Subscribe a URL to changes","description":"Before the subscription is created, the URL receives `{ \"type\": \"webhook.verify\", \"challenge\": \"...\" }` and must answer 2xx with the challenge in its body. Then every minute it receives `{ \"type\": \"changes\", \"subscription\": id, \"events\": [Change] }` for new matching changes. Requests carry `X-Live-Transit-Signature: t=<unix>,v1=<hex HMAC-SHA256 of \"t.body\" keyed by the secret>`. URLs must be public HTTPS; redirects are not followed. Subscriptions stop after 20 consecutive failed deliveries. At most 5 subscriptions per client per day.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri"},"sourceIds":{"type":"array","items":{"type":"string"}},"countries":{"type":"array","items":{"type":"string"}},"subjects":{"type":"array","items":{"type":"string","enum":["line","station","alert"]}}}},"example":{"url":"https://example.org/hooks/transit","sourceIds":["at-vienna-wiener-linien"],"subjects":["line","alert"]}}}},"responses":{"201":{"description":"Created; keep the secret to verify signatures and manage the subscription","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Webhook"}}},"example":{"data":{"id":"0f8b8c2e-3d7a-4c1e-9b4a-2b6f5e1d9a10","url":"https://example.org/hooks/transit","sourceIds":["at-vienna-wiener-linien"],"countries":[],"subjects":["line","alert"],"secret":"4f1c...e9"}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"description":"Too many subscriptions from this client today"}}}},"/api/v1/webhooks/{id}":{"get":{"tags":["Realtime"],"operationId":"getWebhook","summary":"Subscription status","description":"Delivery state of a subscription. Authenticate with `Authorization: Bearer <secret>`.","parameters":[{"name":"id","in":"path","required":true,"description":"Subscription id","schema":{"type":"string"},"example":"0f8b8c2e-3d7a-4c1e-9b4a-2b6f5e1d9a10"}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Webhook"}}},"example":{"data":{"id":"0f8b8c2e-3d7a-4c1e-9b4a-2b6f5e1d9a10","url":"https://example.org/hooks/transit","sourceIds":["at-vienna-wiener-linien"],"countries":[],"subjects":["line","alert"],"createdAt":"2026-11-06T08:00:00.000Z","lastEventId":"48213","lastDelivery":"2026-11-06T08:15:00.000Z","failures":0,"lastError":null,"active":true}}}}},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"tags":["Realtime"],"operationId":"deleteWebhook","summary":"Unsubscribe","description":"Deletes a subscription. Authenticate with `Authorization: Bearer <secret>`.","parameters":[{"name":"id","in":"path","required":true,"description":"Subscription id","schema":{"type":"string"},"example":"0f8b8c2e-3d7a-4c1e-9b4a-2b6f5e1d9a10"}],"responses":{"204":{"description":"Deleted"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/v1/news":{"get":{"tags":["Realtime"],"operationId":"listNews","summary":"Transport news","description":"Curated news about new lines, closures, timetable and network changes, newest first. Updated daily from operator and authority announcements.","parameters":[{"name":"country","in":"query","description":"ISO 3166-1 alpha-2 code.","required":false,"schema":{"type":"string"},"example":"at"},{"name":"kind","in":"query","description":"News kind.","required":false,"schema":{"type":"string","enum":["new_line","closure","timetable_change","network_change","fares","other"]},"example":"new_line"},{"name":"sourceId","in":"query","description":"Only news mentioning this network.","required":false,"schema":{"type":"string"},"example":"at-vienna-wiener-linien"},{"name":"since","in":"query","description":"Only items on or after this date.","required":false,"schema":{"type":"string","format":"date"},"example":"2026-10-01"},{"name":"limit","in":"query","description":"Maximum items (1 to 100).","required":false,"schema":{"type":"integer","minimum":1,"maximum":100},"example":20}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","required":["data","meta"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/NewsItem"}},"meta":{"$ref":"#/components/schemas/Meta"}}},"example":{"data":[{"id":"wl-u2-u5-2026","date":"2026-10-20","title":"U5 construction progress","summary":"Works on the new U5 line continue.","url":"https://www.wienerlinien.at/","sourceIds":["at-vienna-wiener-linien"],"country":"at","kind":"network_change"}],"meta":{"generatedAt":"2026-11-06T08:00:00.000Z","count":1}}}}},"400":{"$ref":"#/components/responses/BadRequest"}}}},"/api/v1/openapi.json":{"get":{"summary":"OpenAPI document","description":"This document.","operationId":"getOpenApi","tags":["Meta"],"responses":{"200":{"description":"OpenAPI 3.1 document","content":{"application/json":{"schema":{"type":"object"},"example":{"openapi":"3.1.0"}}}}}}}},"components":{"schemas":{"Mode":{"type":"string","enum":["air","rail","metro","tram","bus","ferry","cable","other"]},"Health":{"type":"string","enum":["live","stale","failing","pending"],"description":"live: fresh data. stale: last success older than 3 polling intervals. failing: 3+ consecutive failures. pending: never succeeded."},"Severity":{"type":"string","enum":["info","minor","major","severe"]},"Meta":{"type":"object","required":["generatedAt","count"],"properties":{"generatedAt":{"type":"string","format":"date-time","description":"When the response was generated."},"count":{"type":"integer","description":"Number of items in `data`."},"attribution":{"type":"array","items":{"type":"string"},"description":"Data attributions required by the licences of the sources used."}}},"PagedMeta":{"type":"object","required":["generatedAt","count"],"properties":{"generatedAt":{"type":"string","format":"date-time"},"count":{"type":"integer"},"nextCursor":{"type":"string","description":"Pass as `cursor` to fetch the next page. Absent on the last page."},"attribution":{"type":"array","items":{"type":"string"}}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["bad_request","not_found","internal_error"]},"message":{"type":"string"}}}}},"Overview":{"type":"object","required":["sources","countries","cities","activeAlerts","alertsBySeverity","lines","linesDisrupted","stations","stationsDelayed","vehicles","departures","lastIngestAt"],"properties":{"sources":{"type":"object","required":["total","live","stale","failing","pending"],"properties":{"total":{"type":"integer"},"live":{"type":"integer"},"stale":{"type":"integer"},"failing":{"type":"integer"},"pending":{"type":"integer"}}},"countries":{"type":"integer"},"cities":{"type":"integer"},"activeAlerts":{"type":"integer"},"alertsBySeverity":{"type":"object","additionalProperties":{"type":"integer"}},"lines":{"type":"integer"},"linesDisrupted":{"type":"integer"},"stations":{"type":"integer"},"stationsDelayed":{"type":"integer"},"vehicles":{"type":"integer"},"departures":{"type":"integer"},"lastIngestAt":{"type":["string","null"],"format":"date-time"}}},"Source":{"type":"object","required":["id","name","operator","country","city","region","modes","kind","homepage","intervalMinutes","health","lastSuccessAt","counts","activeAlerts","disruptedLines"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"operator":{"type":"string"},"country":{"type":"string","description":"ISO 3166-1 alpha-2, lowercase; `xx` for multi-country feeds."},"city":{"type":["string","null"]},"region":{"type":"string","enum":["africa","asia","europe","latam","north-america","oceania","middle-east"]},"modes":{"type":"array","items":{"$ref":"#/components/schemas/Mode"}},"kind":{"type":"string","enum":["gtfs-rt","api","scrape","fids"]},"homepage":{"type":"string"},"license":{"type":["string","null"]},"attribution":{"type":["string","null"]},"intervalMinutes":{"type":"integer"},"lat":{"type":["number","null"]},"lon":{"type":["number","null"]},"health":{"$ref":"#/components/schemas/Health"},"lastSuccessAt":{"type":["string","null"],"format":"date-time"},"lastRunAt":{"type":["string","null"],"format":"date-time"},"lastError":{"type":["string","null"]},"counts":{"type":"object","additionalProperties":{"type":"integer"},"description":"Item counts of the last successful run."},"activeAlerts":{"type":"integer"},"disruptedLines":{"type":"integer"}}},"IngestRun":{"type":"object","required":["startedAt","durationMs","ok","error","counts"],"properties":{"startedAt":{"type":["string","null"],"format":"date-time"},"durationMs":{"type":"integer"},"ok":{"type":"boolean"},"error":{"type":["string","null"]},"counts":{"type":"object","additionalProperties":{"type":"integer"}}}},"SourceDetail":{"allOf":[{"$ref":"#/components/schemas/Source"},{"type":"object","required":["endpoints","lines","alerts","stations","recentRuns"],"properties":{"endpoints":{"type":"array","items":{"type":"string"}},"lines":{"type":"array","items":{"$ref":"#/components/schemas/Line"}},"alerts":{"type":"array","items":{"$ref":"#/components/schemas/Alert"}},"stations":{"type":"array","items":{"$ref":"#/components/schemas/Station"}},"recentRuns":{"type":"array","items":{"$ref":"#/components/schemas/IngestRun"}}}}]},"Alert":{"type":"object","required":["sourceId","id","severity","title","modes","routes","stops","active","firstSeenAt","lastSeenAt"],"properties":{"sourceId":{"type":"string"},"sourceName":{"type":"string"},"country":{"type":"string"},"city":{"type":["string","null"]},"id":{"type":"string"},"severity":{"$ref":"#/components/schemas/Severity"},"effect":{"type":["string","null"],"description":"GTFS-RT style effect, e.g. NO_SERVICE, SIGNIFICANT_DELAYS."},"cause":{"type":["string","null"]},"title":{"type":"string"},"description":{"type":["string","null"]},"url":{"type":["string","null"]},"modes":{"type":"array","items":{"$ref":"#/components/schemas/Mode"}},"routes":{"type":"array","items":{"type":"string"}},"stops":{"type":"array","items":{"type":"string"}},"activeFrom":{"type":["string","null"],"format":"date-time"},"activeUntil":{"type":["string","null"],"format":"date-time"},"active":{"type":"boolean"},"firstSeenAt":{"type":"string","format":"date-time"},"lastSeenAt":{"type":"string","format":"date-time","description":"Last time the source still reported this alert."},"resolvedAt":{"type":["string","null"],"format":"date-time"}}},"Line":{"type":"object","required":["sourceId","lineId","lineName","mode","status","statusText","since","updatedAt"],"properties":{"sourceId":{"type":"string"},"sourceName":{"type":"string"},"country":{"type":"string"},"city":{"type":["string","null"]},"lineId":{"type":"string"},"lineName":{"type":"string"},"longName":{"type":["string","null"],"description":"Full descriptive name from the static catalog."},"mode":{"$ref":"#/components/schemas/Mode"},"color":{"type":["string","null"],"description":"Hex colour without `#`."},"status":{"type":"string","enum":["good","minor","major","suspended","closed","unknown"]},"statusText":{"type":"string"},"since":{"type":"string","format":"date-time","description":"When the line entered its current status."},"updatedAt":{"type":"string","format":"date-time"},"vehicles":{"type":["integer","null"],"description":"Number of vehicles currently reporting on this line."},"hasShape":{"type":"boolean","description":"Whether route geometry is available for maps."}}},"Station":{"type":"object","required":["sourceId","stationId","name","mode","status","since","updatedAt"],"properties":{"sourceId":{"type":"string"},"sourceName":{"type":"string"},"stationId":{"type":"string","description":"IATA code for airports, operator id otherwise."},"name":{"type":"string"},"mode":{"$ref":"#/components/schemas/Mode"},"city":{"type":["string","null"]},"country":{"type":["string","null"]},"lat":{"type":["number","null"]},"lon":{"type":["number","null"]},"status":{"type":"string","enum":["normal","delays","major_delays","closed","unknown"]},"statusText":{"type":["string","null"]},"delayMinutes":{"type":["integer","null"],"description":"Average delay in minutes when published."},"since":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"StationDetail":{"allOf":[{"$ref":"#/components/schemas/Station"},{"type":"object","required":["reports"],"properties":{"reports":{"type":"array","items":{"$ref":"#/components/schemas/Station"},"description":"One entry per source reporting this station."}}}]},"Departure":{"type":"object","required":["sourceId","id","stationId","stationName","direction","mode","service","scheduledAt","status"],"properties":{"sourceId":{"type":"string"},"id":{"type":"string"},"stationId":{"type":"string"},"stationName":{"type":"string"},"direction":{"type":"string","enum":["departure","arrival"]},"mode":{"$ref":"#/components/schemas/Mode"},"service":{"type":"string","description":"Flight number, train number or line name."},"carrier":{"type":["string","null"]},"counterpart":{"type":["string","null"],"description":"Destination for departures, origin for arrivals."},"scheduledAt":{"type":"string","format":"date-time"},"estimatedAt":{"type":["string","null"],"format":"date-time"},"platform":{"type":["string","null"]},"status":{"type":"string","enum":["scheduled","on_time","delayed","boarding","departed","arrived","landed","cancelled","diverted","unknown"]},"statusText":{"type":["string","null"]},"delayMinutes":{"type":["integer","null"]}}},"Vehicle":{"type":"object","required":["sourceId","id","mode","lat","lon"],"properties":{"sourceId":{"type":"string"},"id":{"type":"string"},"mode":{"$ref":"#/components/schemas/Mode"},"routeId":{"type":["string","null"]},"routeName":{"type":["string","null"]},"tripId":{"type":["string","null"]},"headsign":{"type":["string","null"]},"lat":{"type":"number"},"lon":{"type":"number"},"bearing":{"type":["number","null"]},"speed":{"type":["number","null"],"description":"Metres per second."},"label":{"type":["string","null"]},"color":{"type":["string","null"],"description":"Route hex colour without `#`."},"delaySeconds":{"type":["integer","null"]},"observedAt":{"type":["string","null"],"format":"date-time"},"distanceKm":{"type":"number","description":"Only present for lat/lon/radiusKm queries."}}},"Change":{"type":"object","required":["id","at","sourceId","sourceName","country","subject","subjectId","subjectName","to","url"],"properties":{"id":{"type":"string","description":"Cursor, increasing."},"at":{"type":"string","format":"date-time"},"sourceId":{"type":"string"},"sourceName":{"type":"string"},"country":{"type":"string"},"subject":{"type":"string","enum":["line","station","alert"]},"subjectId":{"type":"string"},"subjectName":{"type":"string"},"from":{"type":["string","null"],"description":"Previous status; null for alerts and first sightings."},"to":{"type":"string","description":"New status, or opened / resolved for alerts."},"text":{"type":["string","null"]},"severity":{"type":["string","null"]},"url":{"type":"string","format":"uri"}},"example":{"id":"48213","at":"2026-11-06T08:14:03.000Z","sourceId":"at-vienna-wiener-linien","sourceName":"Wiener Linien","country":"at","subject":"line","subjectId":"U6","subjectName":"U6","from":"good","to":"major","text":"Delays after a signal fault","severity":null,"url":"https://livetransit.vercel.app/n/at-vienna-wiener-linien/l/U6"}},"SearchHit":{"type":"object","required":["type","id","sourceId","title","subtitle","status","score"],"properties":{"type":{"type":"string","enum":["source","station","line","alert"]},"id":{"type":"string"},"sourceId":{"type":"string"},"title":{"type":"string"},"subtitle":{"type":"string"},"status":{"type":["string","null"]},"score":{"type":"number"}}},"HealthReport":{"type":"object","required":["summary","sources"],"properties":{"summary":{"type":"object","additionalProperties":{"type":"integer"},"description":"Source count per health value."},"sources":{"type":"array","items":{"type":"object","required":["id","name","country","health","intervalMinutes","consecutiveFailures"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"country":{"type":"string"},"health":{"$ref":"#/components/schemas/Health"},"intervalMinutes":{"type":"integer"},"lastSuccessAt":{"type":["string","null"],"format":"date-time"},"lastRunAt":{"type":["string","null"],"format":"date-time"},"lastError":{"type":["string","null"]},"consecutiveFailures":{"type":"integer"},"lastDurationMs":{"type":["integer","null"]},"counts":{"type":"object","additionalProperties":{"type":"integer"}}}}}}},"GeoTile":{"type":"object","required":["sourceId","tx","ty","url","bytes","routes"],"properties":{"sourceId":{"type":"string"},"tx":{"type":"integer"},"ty":{"type":"integer"},"url":{"type":"string","format":"uri"},"bytes":{"type":"integer"},"routes":{"type":"integer"}}},"Stop":{"type":"object","required":["sourceId","name","lat","lon"],"properties":{"sourceId":{"type":"string"},"name":{"type":"string"},"lat":{"type":"number"},"lon":{"type":"number"}}},"Route":{"type":"object","required":["sourceId","routeId","name","mode","bbox"],"properties":{"sourceId":{"type":"string"},"routeId":{"type":"string"},"name":{"type":"string"},"shortName":{"type":["string","null"]},"longName":{"type":["string","null"]},"mode":{"$ref":"#/components/schemas/Mode"},"color":{"type":["string","null"],"description":"Hex colour without `#`."},"textColor":{"type":["string","null"]},"shapes":{"type":"array","items":{"type":"string"},"description":"Encoded polylines (Google format, precision 5). Present only when geometry=true."},"bbox":{"type":["array","null"],"items":{"type":"number"},"minItems":4,"maxItems":4,"description":"Bounding box [minLon, minLat, maxLon, maxLat] or null."}}},"LineDetail":{"type":"object","required":["sourceId","lineId","name","mode","shapes","vehicles","alerts","history"],"properties":{"sourceId":{"type":"string"},"lineId":{"type":"string"},"name":{"type":"string"},"longName":{"type":["string","null"]},"mode":{"$ref":"#/components/schemas/Mode"},"color":{"type":["string","null"],"description":"Hex colour without `#`."},"textColor":{"type":["string","null"]},"status":{"$ref":"#/components/schemas/Line","description":"Current status, or null if no live status is published."},"shapes":{"type":"array","items":{"type":"string"},"description":"Encoded polylines (Google format, precision 5) for mapping."},"vehicles":{"type":"array","items":{"$ref":"#/components/schemas/Vehicle"},"description":"Live vehicles currently reporting on this line."},"alerts":{"type":"array","items":{"$ref":"#/components/schemas/Alert"},"description":"Active alerts affecting this line."},"history":{"type":"array","items":{"type":"object","required":["to","at"],"properties":{"from":{"type":["string","null"]},"to":{"type":"string"},"text":{"type":["string","null"]},"at":{"type":"string","format":"date-time"}}},"description":"Recent status transitions, newest first."}}},"PlannedSource":{"type":"object","required":["name","country","modes","url","reason"],"properties":{"name":{"type":"string"},"country":{"type":"string"},"modes":{"type":"array","items":{"$ref":"#/components/schemas/Mode"}},"url":{"type":"string"},"reason":{"type":"string","description":"Why it is not ingested yet, usually a registration key."}}},"NewsItem":{"type":"object","required":["id","date","title","summary","url","sourceIds","country","kind"],"properties":{"id":{"type":"string"},"date":{"type":"string","format":"date"},"title":{"type":"string"},"summary":{"type":"string"},"url":{"type":"string","format":"uri","description":"The original announcement."},"sourceIds":{"type":"array","items":{"type":"string"}},"country":{"type":"string"},"kind":{"type":"string","enum":["new_line","closure","timetable_change","network_change","fares","other"]}},"example":{"id":"wl-u2-u5-2026","date":"2026-10-20","title":"U5 construction progress","summary":"Works on the new U5 line continue.","url":"https://www.wienerlinien.at/","sourceIds":["at-vienna-wiener-linien"],"country":"at","kind":"network_change"}},"Webhook":{"type":"object","required":["id","url"],"properties":{"id":{"type":"string","format":"uuid"},"url":{"type":"string","format":"uri"},"secret":{"type":"string","description":"Only returned on creation."},"sourceIds":{"type":"array","items":{"type":"string"}},"countries":{"type":"array","items":{"type":"string"}},"subjects":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string","format":"date-time"},"lastEventId":{"type":"string"},"lastDelivery":{"type":["string","null"],"format":"date-time"},"failures":{"type":"integer"},"lastError":{"type":["string","null"]},"active":{"type":"boolean"}}}},"responses":{"BadRequest":{"description":"Invalid parameters","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"bad_request","message":"q: must be at least 2 characters"}}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"not_found","message":"Unknown source \"x\""}}}}},"ServerError":{"description":"Unexpected error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"internal_error","message":"Internal server error"}}}}}}}}