Skip to content

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/agencies

Returns 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=10
  • near is required as lng,lat in WGS84.
  • radius is metres from 50 to 10,000. The default is 1,000.
  • limit is 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=50
  • at is an ISO 8601 instant with Z or an explicit offset. It defaults to now. A local time without an offset is rejected because daylight-saving transitions can make it ambiguous.
  • window is 1 to 240 minutes. The default is 60.
  • limit is 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.