{"openapi":"3.1.0","info":{"title":"transport-mcp","version":"0.1.0","description":"The HTTP API of a Swiss public transport resource server, reading transport.opendata.ch. It is a sibling of the MCP endpoint at /mcp rather than a translation of it: both read the same data, and each is shaped for its own kind of caller.\n\nEvery timestamp is RFC 3339 with the offset the traveller is standing in. Every `datetime`, `date` and `time` PARAMETER carries no offset and is read as Europe/Zurich local time, because that is what the upstream does and translating it here would put a departure board an hour out for half the year.\n\nData by transport.opendata.ch, powered by Opendata.ch."},"servers":[{"url":"https://transport.mcp.cedricm.dev"}],"paths":{"/api/v1/stops":{"get":{"operationId":"listStops","summary":"Search stops by name or position","description":"Stations, stops, addresses and points of interest. Give `query` to search by name, or `lat` and `lon` together to find what is nearest a position. NOT PAGED: the upstream answers at most ten matches and offers no cursor, so `next_cursor` is never present.","tags":["stops"],"parameters":[{"name":"query","in":"query","required":false,"description":"Name to search for. Partial names match.","schema":{"type":"string"}},{"name":"lat","in":"query","required":false,"description":"WGS84 latitude. Requires `lon`.","schema":{"type":"number","minimum":-90,"maximum":90}},{"name":"lon","in":"query","required":false,"description":"WGS84 longitude. Requires `lat`.","schema":{"type":"number","minimum":-180,"maximum":180}},{"name":"stations_only","in":"query","required":false,"description":"Only places with a timetable of their own, which is to say places with a non-null `id`. Applied by this server: the upstream documents a `type` filter and ignores it.","schema":{"type":"boolean","default":false}},{"name":"limit","in":"query","required":false,"description":"Places per response, at most 10.","schema":{"type":"integer","minimum":1,"maximum":10,"default":8}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","title":"Stop","properties":{"id":{"type":["string","null"],"description":"The station id, and the only thing /stops/{id} and its departures accept. Null means this is an address or a point of interest and has no board of its own."},"name":{"type":["string","null"]},"icon":{"type":["string","null"],"description":"The kind of stop: train, bus, tram, ship, cableway."},"lat":{"type":["number","null"],"description":"WGS84 latitude."},"lon":{"type":["number","null"],"description":"WGS84 longitude."},"distance_metres":{"type":["number","null"],"description":"Set only on a coordinate search."}},"required":["id","name","icon","lat","lon","distance_metres"]}},"next_cursor":{"type":"string","description":"Pass as `cursor` for the next page. Absent when this is the last page."}},"required":["items"]}}}},"400":{"description":"The request was malformed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No token, or a token this server did not accept. The WWW-Authenticate header names the issuer to get one from.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The token is valid but does not carry the transport:stops.read scope. The WWW-Authenticate header names it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"oauth2":["transport:stops.read"]}]}},"/api/v1/stops/{id}":{"get":{"operationId":"getStop","summary":"Get one stop by id","description":"The stop's name and position. A name in this position is a 400 and not a lookup: the upstream would fuzzy-match it, so the same URL could name different stations on different days.","tags":["stops"],"parameters":[{"name":"id","in":"path","required":true,"description":"The station id, as returned in the `id` field of GET /api/v1/stops. Numeric; a name is refused.","schema":{"type":"string","pattern":"^\\d{3,9}$"}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","title":"Stop","properties":{"id":{"type":["string","null"],"description":"The station id, and the only thing /stops/{id} and its departures accept. Null means this is an address or a point of interest and has no board of its own."},"name":{"type":["string","null"]},"icon":{"type":["string","null"],"description":"The kind of stop: train, bus, tram, ship, cableway."},"lat":{"type":["number","null"],"description":"WGS84 latitude."},"lon":{"type":["number","null"],"description":"WGS84 longitude."},"distance_metres":{"type":["number","null"],"description":"Set only on a coordinate search."}},"required":["id","name","icon","lat","lon","distance_metres"]}}}},"400":{"description":"The request was malformed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No token, or a token this server did not accept. The WWW-Authenticate header names the issuer to get one from.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The token is valid but does not carry the transport:stops.read scope. The WWW-Authenticate header names it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"oauth2":["transport:stops.read"]}]}},"/api/v1/stops/{id}/departures":{"get":{"operationId":"listDepartures","summary":"Read a station's departure or arrival board","description":"The next services calling at this station, with the printed time, the time the operator is reporting now, the platform, and a platform change where there is one. NOT PAGED: `limit` is the only control the upstream offers, and a cursor over a board that moves every thirty seconds would promise a continuity that does not exist.","tags":["departures"],"parameters":[{"name":"id","in":"path","required":true,"description":"The station id, as returned in the `id` field of GET /api/v1/stops. Numeric; a name is refused.","schema":{"type":"string","pattern":"^\\d{3,9}$"}},{"name":"type","in":"query","required":false,"description":"Which board to read.","schema":{"type":"string","enum":["departure","arrival"],"default":"departure"}},{"name":"limit","in":"query","required":false,"description":"Services per response, at most 40.","schema":{"type":"integer","minimum":1,"maximum":40,"default":10}},{"name":"datetime","in":"query","required":false,"description":"When the board starts, as \"YYYY-MM-DD hh:mm\" in Swiss local time (Europe/Zurich). Absent means now. There is no offset in this format and none is read: it is local time.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}$"}},{"name":"stops","in":"query","required":false,"description":"Include every stop each service calls at. Off by default because a board of forty services carries forty full routes.","schema":{"type":"boolean","default":false}},{"name":"transportations","in":"query","required":false,"description":"Restrict to these vehicle classes. Repeat the parameter or send one comma-joined value. An unrecognised value is a 400 rather than a silently unfiltered result.","schema":{"type":"array","items":{"type":"string","enum":["train","tram","ship","bus","cableway"]}}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","title":"Departure","properties":{"line":{"type":"string"},"category":{"type":["string","null"]},"number":{"type":["string","null"]},"operator":{"type":["string","null"]},"destination":{"type":["string","null"]},"run":{"type":["string","null"]},"checkpoint":{"type":"object","title":"Checkpoint","properties":{"stop":{"type":"object","title":"Stop","properties":{"id":{"type":["string","null"],"description":"The station id, and the only thing /stops/{id} and its departures accept. Null means this is an address or a point of interest and has no board of its own."},"name":{"type":["string","null"]},"icon":{"type":["string","null"],"description":"The kind of stop: train, bus, tram, ship, cableway."},"lat":{"type":["number","null"],"description":"WGS84 latitude."},"lon":{"type":["number","null"],"description":"WGS84 longitude."},"distance_metres":{"type":["number","null"],"description":"Set only on a coordinate search."}},"required":["id","name","icon","lat","lon","distance_metres"]},"scheduled_arrival":{"type":["string","null"],"format":"date-time"},"scheduled_departure":{"type":["string","null"],"format":"date-time"},"expected_arrival":{"type":["string","null"],"format":"date-time"},"expected_departure":{"type":["string","null"],"format":"date-time"},"delay_minutes":{"type":["integer","null"],"description":"0 is on time. null means no realtime information, which is not the same as on time."},"platform":{"type":["string","null"],"description":"As printed in the timetable."},"expected_platform":{"type":["string","null"],"description":"Non-null only when the platform has CHANGED. This field is the change."}},"required":["stop","scheduled_arrival","scheduled_departure","expected_arrival","expected_departure","delay_minutes","platform","expected_platform"]},"stops":{"type":"array","description":"Present only when the request set stops=true.","items":{"type":"object","title":"Checkpoint","properties":{"stop":{"type":"object","title":"Stop","properties":{"id":{"type":["string","null"],"description":"The station id, and the only thing /stops/{id} and its departures accept. Null means this is an address or a point of interest and has no board of its own."},"name":{"type":["string","null"]},"icon":{"type":["string","null"],"description":"The kind of stop: train, bus, tram, ship, cableway."},"lat":{"type":["number","null"],"description":"WGS84 latitude."},"lon":{"type":["number","null"],"description":"WGS84 longitude."},"distance_metres":{"type":["number","null"],"description":"Set only on a coordinate search."}},"required":["id","name","icon","lat","lon","distance_metres"]},"scheduled_arrival":{"type":["string","null"],"format":"date-time"},"scheduled_departure":{"type":["string","null"],"format":"date-time"},"expected_arrival":{"type":["string","null"],"format":"date-time"},"expected_departure":{"type":["string","null"],"format":"date-time"},"delay_minutes":{"type":["integer","null"],"description":"0 is on time. null means no realtime information, which is not the same as on time."},"platform":{"type":["string","null"],"description":"As printed in the timetable."},"expected_platform":{"type":["string","null"],"description":"Non-null only when the platform has CHANGED. This field is the change."}},"required":["stop","scheduled_arrival","scheduled_departure","expected_arrival","expected_departure","delay_minutes","platform","expected_platform"]}}},"required":["line","category","number","operator","destination","run","checkpoint"]}},"next_cursor":{"type":"string","description":"Pass as `cursor` for the next page. Absent when this is the last page."}},"required":["items"]}}}},"400":{"description":"The request was malformed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No token, or a token this server did not accept. The WWW-Authenticate header names the issuer to get one from.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The token is valid but does not carry the transport:timetable.read scope. The WWW-Authenticate header names it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"oauth2":["transport:timetable.read"]}]}},"/api/v1/connections":{"get":{"operationId":"listConnections","summary":"Plan journeys between two places","description":"Journeys from one place to another, each with its legs, changes and platforms. `from` and `to` take a station id or a name; a program holding an id from GET /api/v1/stops should send the id, because a name goes through the upstream's fuzzy matcher.\n\nAnswers 404 when either endpoint matches no stop, with the closest matches in a `candidates` member, and 422 when both stops exist and no service runs between them. Those are different answers and the upstream conflates them: it returns 200 and an empty list for both.\n\nTHE ONLY PAGED COLLECTION here, four pages deep, which is the upstream's own limit.","tags":["connections"],"parameters":[{"name":"from","in":"query","required":true,"description":"Where the journey starts. A station id or a name.","schema":{"type":"string"}},{"name":"to","in":"query","required":true,"description":"Where it ends. A station id or a name.","schema":{"type":"string"}},{"name":"via","in":"query","required":false,"description":"Force the route through these stops, at most 5. Repeat the parameter.","schema":{"type":"array","items":{"type":"string"},"maxItems":5}},{"name":"date","in":"query","required":false,"description":"Travel date, \"YYYY-MM-DD\", Swiss local. Absent means today in Switzerland.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},{"name":"time","in":"query","required":false,"description":"Time of day, \"hh:mm\" on a 24 hour clock, Swiss local. Absent means now.","schema":{"type":"string","pattern":"^\\d{2}:\\d{2}$"}},{"name":"arrive_by","in":"query","required":false,"description":"True means `time` is when to arrive by rather than when to leave. Requires `time`: \"arrive by now\" is a search for a train that has gone.","schema":{"type":"boolean","default":false}},{"name":"transportations","in":"query","required":false,"description":"Restrict to these vehicle classes. Repeat the parameter or send one comma-joined value. An unrecognised value is a 400 rather than a silently unfiltered result.","schema":{"type":"array","items":{"type":"string","enum":["train","tram","ship","bus","cableway"]}}},{"name":"direct","in":"query","required":false,"description":"Only journeys with no change of vehicle.","schema":{"type":"boolean","default":false}},{"name":"bike","in":"query","required":false,"description":"Only services that carry a bicycle.","schema":{"type":"boolean","default":false}},{"name":"limit","in":"query","required":false,"description":"Journeys per page, at most 16. Services departing at the same minute count as one, which is the upstream's rule.","schema":{"type":"integer","minimum":1,"maximum":16,"default":4}},{"name":"cursor","in":"query","required":false,"description":"The `next_cursor` from the previous page. Opaque, and only ever a value this API issued. There are 4 pages at most.","schema":{"type":"string"}}],"responses":{"200":{"description":"Success.","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","title":"Connection","properties":{"departure":{"anyOf":[{"type":"object","title":"Checkpoint","properties":{"stop":{"type":"object","title":"Stop","properties":{"id":{"type":["string","null"],"description":"The station id, and the only thing /stops/{id} and its departures accept. Null means this is an address or a point of interest and has no board of its own."},"name":{"type":["string","null"]},"icon":{"type":["string","null"],"description":"The kind of stop: train, bus, tram, ship, cableway."},"lat":{"type":["number","null"],"description":"WGS84 latitude."},"lon":{"type":["number","null"],"description":"WGS84 longitude."},"distance_metres":{"type":["number","null"],"description":"Set only on a coordinate search."}},"required":["id","name","icon","lat","lon","distance_metres"]},"scheduled_arrival":{"type":["string","null"],"format":"date-time"},"scheduled_departure":{"type":["string","null"],"format":"date-time"},"expected_arrival":{"type":["string","null"],"format":"date-time"},"expected_departure":{"type":["string","null"],"format":"date-time"},"delay_minutes":{"type":["integer","null"],"description":"0 is on time. null means no realtime information, which is not the same as on time."},"platform":{"type":["string","null"],"description":"As printed in the timetable."},"expected_platform":{"type":["string","null"],"description":"Non-null only when the platform has CHANGED. This field is the change."}},"required":["stop","scheduled_arrival","scheduled_departure","expected_arrival","expected_departure","delay_minutes","platform","expected_platform"]},{"type":"null"}]},"arrival":{"anyOf":[{"type":"object","title":"Checkpoint","properties":{"stop":{"type":"object","title":"Stop","properties":{"id":{"type":["string","null"],"description":"The station id, and the only thing /stops/{id} and its departures accept. Null means this is an address or a point of interest and has no board of its own."},"name":{"type":["string","null"]},"icon":{"type":["string","null"],"description":"The kind of stop: train, bus, tram, ship, cableway."},"lat":{"type":["number","null"],"description":"WGS84 latitude."},"lon":{"type":["number","null"],"description":"WGS84 longitude."},"distance_metres":{"type":["number","null"],"description":"Set only on a coordinate search."}},"required":["id","name","icon","lat","lon","distance_metres"]},"scheduled_arrival":{"type":["string","null"],"format":"date-time"},"scheduled_departure":{"type":["string","null"],"format":"date-time"},"expected_arrival":{"type":["string","null"],"format":"date-time"},"expected_departure":{"type":["string","null"],"format":"date-time"},"delay_minutes":{"type":["integer","null"],"description":"0 is on time. null means no realtime information, which is not the same as on time."},"platform":{"type":["string","null"],"description":"As printed in the timetable."},"expected_platform":{"type":["string","null"],"description":"Non-null only when the platform has CHANGED. This field is the change."}},"required":["stop","scheduled_arrival","scheduled_departure","expected_arrival","expected_departure","delay_minutes","platform","expected_platform"]},{"type":"null"}]},"duration_seconds":{"type":["integer","null"]},"transfers":{"type":["integer","null"]},"products":{"type":"array","items":{"type":"string"}},"legs":{"type":"array","items":{"type":"object","title":"Leg","properties":{"mode":{"type":"string","enum":["transit","walk"]},"line":{"type":["string","null"]},"category":{"type":["string","null"]},"operator":{"type":["string","null"]},"destination":{"type":["string","null"]},"departure":{"anyOf":[{"type":"object","title":"Checkpoint","properties":{"stop":{"type":"object","title":"Stop","properties":{"id":{"type":["string","null"],"description":"The station id, and the only thing /stops/{id} and its departures accept. Null means this is an address or a point of interest and has no board of its own."},"name":{"type":["string","null"]},"icon":{"type":["string","null"],"description":"The kind of stop: train, bus, tram, ship, cableway."},"lat":{"type":["number","null"],"description":"WGS84 latitude."},"lon":{"type":["number","null"],"description":"WGS84 longitude."},"distance_metres":{"type":["number","null"],"description":"Set only on a coordinate search."}},"required":["id","name","icon","lat","lon","distance_metres"]},"scheduled_arrival":{"type":["string","null"],"format":"date-time"},"scheduled_departure":{"type":["string","null"],"format":"date-time"},"expected_arrival":{"type":["string","null"],"format":"date-time"},"expected_departure":{"type":["string","null"],"format":"date-time"},"delay_minutes":{"type":["integer","null"],"description":"0 is on time. null means no realtime information, which is not the same as on time."},"platform":{"type":["string","null"],"description":"As printed in the timetable."},"expected_platform":{"type":["string","null"],"description":"Non-null only when the platform has CHANGED. This field is the change."}},"required":["stop","scheduled_arrival","scheduled_departure","expected_arrival","expected_departure","delay_minutes","platform","expected_platform"]},{"type":"null"}]},"arrival":{"anyOf":[{"type":"object","title":"Checkpoint","properties":{"stop":{"type":"object","title":"Stop","properties":{"id":{"type":["string","null"],"description":"The station id, and the only thing /stops/{id} and its departures accept. Null means this is an address or a point of interest and has no board of its own."},"name":{"type":["string","null"]},"icon":{"type":["string","null"],"description":"The kind of stop: train, bus, tram, ship, cableway."},"lat":{"type":["number","null"],"description":"WGS84 latitude."},"lon":{"type":["number","null"],"description":"WGS84 longitude."},"distance_metres":{"type":["number","null"],"description":"Set only on a coordinate search."}},"required":["id","name","icon","lat","lon","distance_metres"]},"scheduled_arrival":{"type":["string","null"],"format":"date-time"},"scheduled_departure":{"type":["string","null"],"format":"date-time"},"expected_arrival":{"type":["string","null"],"format":"date-time"},"expected_departure":{"type":["string","null"],"format":"date-time"},"delay_minutes":{"type":["integer","null"],"description":"0 is on time. null means no realtime information, which is not the same as on time."},"platform":{"type":["string","null"],"description":"As printed in the timetable."},"expected_platform":{"type":["string","null"],"description":"Non-null only when the platform has CHANGED. This field is the change."}},"required":["stop","scheduled_arrival","scheduled_departure","expected_arrival","expected_departure","delay_minutes","platform","expected_platform"]},{"type":"null"}]},"walk_seconds":{"type":["integer","null"]}},"required":["mode","line","category","operator","destination","departure","arrival","walk_seconds"]}}},"required":["departure","arrival","duration_seconds","transfers","products","legs"]}},"next_cursor":{"type":"string","description":"Pass as `cursor` for the next page. Absent when this is the last page."}},"required":["items"]}}}},"400":{"description":"The request was malformed.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No token, or a token this server did not accept. The WWW-Authenticate header names the issuer to get one from.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The token is valid but does not carry the transport:timetable.read scope. The WWW-Authenticate header names it.","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"oauth2":["transport:timetable.read"]}]}}},"components":{"schemas":{"Problem":{"type":"object","title":"Problem","description":"RFC 7807. Served as application/problem+json.","properties":{"type":{"type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"instance":{"type":"string"}},"required":["title","status"]}},"securitySchemes":{"oauth2":{"type":"oauth2","description":"Tokens come from the issuer below. A client that has none can discover the issuer from /.well-known/oauth-protected-resource, which is what lets a tool connect having been told nothing but this URL.","flows":{"authorizationCode":{"authorizationUrl":"https://auth.cedricm.dev/authorize","tokenUrl":"https://auth.cedricm.dev/token","refreshUrl":"https://auth.cedricm.dev/token","scopes":{"transport:timetable.read":"Read departure and arrival boards for a station, plan journeys between places, and see live delays and platform changes.","transport:stops.read":"Search Swiss stations, stops and addresses by name, and find the ones nearest a coordinate. Searching by coordinate means telling this server where somebody is, which reading a timetable does not."}}}}}}}