Transit API
Every request needs an API key; see Authentication.
The transit data API exposes the static GTFS release behind Unmap as agencies, nearby stops, routes and scheduled departures. It uses the same key and the same usage meter as routing. There is no separate transit plan or quota.
The release is national and rebuilt every week. It carries every feed the transit routing graph
serves whose licence is cleared for republication: 80 feeds in seven provinces when it was first
built nationally on 2026-10-09, up from the eight pilots. Feeds whose terms neither public catalog
gives are held until the terms are found, and feeds whose terms restrict redistribution are left
out. VIA Rail Canada is left out because it publishes frequencies.txt headways, which cannot be
stated as exact departures. GET /transit/agencies lists every agency in the release being
served, and GET /coverage.json says why each other feed is not in it. Every departure is
scheduled, not realtime. When no release is available the API returns 503 with
retryable: true, so an empty bucket never appears as an empty country.
See Coverage for the feeds that have cleared licensing and
validation.
Client
@unmap/sdk exposes all five endpoints through unmap.transit:
import { Unmap } from "@unmap/sdk";
const unmap = new Unmap({ key: "um_live_..." });
const agencies = await unmap.transit.agencies();
const stops = await unmap.transit.nearbyStops([-79.3832, 43.6532], {
radius: 800,
limit: 10,
});
const stop = stops.data[0];
if (stop) {
const departures = await unmap.transit.departures(stop.id, {
at: new Date(),
window: 60,
limit: 20,
});
}Each method returns { data, meta }. meta identifies the source release, attribution and
freshness. Static transit calls count as one normal routing call.
A response can span several feeds: nearby stops in Toronto can come from TTC, GO Transit, York
Region Transit and MiWay at once. Then meta.sources lists each feed and meta.attribution joins
their attribution statements into one string. Show that string wherever you display the data.
Six Quebec feeds are published under the Open Database License (ODbL) 1.0, which is share-alike. Their attribution says so, and says that unmap's copy is offered under the ODbL 1.0. If you redistribute data from those feeds, it stays under the ODbL.
Agencies
GET /transit/agenciesReturns every agency in the enabled release. An agency with the GTFS id TTC in the ttc feed
has the identifier um:transit:agency:ttc:TTC. A single-agency feed that omits agency_id uses
the feed-level form, such as um:transit:agency:ttc. GTFS identifiers are only unique inside one
feed, so raw ids are never presented as global ids.
Nearby stops
GET /transit/stops?near=-79.3832,43.6532&radius=800&limit=10nearis required aslng,latin WGS84.radiusis metres from 50 to 10,000. The default is 1,000.limitis 1 to 100. The default is 20.
The result is filtered by exact great-circle distance and sorted nearest first. Every returned
stop has distance in metres and a stable id such as um:transit:stop:ttc:1234.
Parent stations aggregate the routes and timed departures of their child platforms.
Stop and route lookup
GET /transit/stops/{id}
GET /transit/routes/{id}Pass the Unmap id returned by another transit call. Encode it as a URL path segment when building
the request yourself. The SDK does this automatically with getStop(id) and getRoute(id).
Departures
GET /transit/stops/{id}/departures?at=2026-09-17T08:00:00-04:00&window=60&limit=50atis an ISO 8601 instant withZor an explicit offset. It defaults to now. A local time without an offset is rejected because daylight-saving transitions can make it ambiguous.windowis 1 to 240 minutes. The default is 60.limitis 1 to 200. The default is 50.
The compiler preserves GTFS times after midnight and service exceptions from calendar_dates.txt.
The API resolves them in the feed's timezone, including daylight-saving boundaries, and returns
absolute ISO instants:
{
"data": [
{
"stop_id": "um:transit:stop:ttc:1234",
"route_id": "um:transit:route:ttc:1",
"trip_id": "um:transit:trip:ttc:night-1",
"headsign": "Finch",
"arrival": "2026-09-18T05:14:00.000Z",
"departure": "2026-09-18T05:15:00.000Z",
"stop_sequence": 7,
"schedule_type": "scheduled"
}
],
"meta": {
"source": "Toronto Transit Commission: TTC Routes and Schedules",
"source_updated_at": null,
"source_version": "mdb-732",
"content_hash": "<sha256>",
"data_version": "20260917-0123456789ab",
"unmap_updated_at": "2026-09-17T12:00:00.000Z",
"status": "current",
"attribution": "Open Government Licence - Toronto",
"schedule_type": "scheduled"
}
}schedule_type is always scheduled in this release, on every row and in meta even when the
result is empty. No response implies live vehicle movement, delay or cancellation data.
GTFS-Realtime is a later, separate phase.
Feeds that use frequencies.txt are refused for now. A headway-based trip cannot be represented as
one exact departure without inventing precision the producer did not publish.
Routing is a separate deployment
The static API carries only feeds that are routing-ready on the transit graph, so an agency listed
here is one mode=transit can ride. The two are still separate deployments: each agency's
capabilities.routing is true only while the transit graph is serving. Use mode=transit with
depart_at on /route and /isochrone only when the coverage page says transit routing is
deployed; a transit isochrone centre with no scheduled transit nearby is a 422. There is no
arrive_by or transit matrix contract.
Next steps
- Coverage lists which agencies are in this release.
- Routing API for
mode=transit, which is a separate deployment. - Errors for the failure contract.