API reference
Every route the gateway serves, in one place. The pages before this one explain each service; this one is the table you keep open while you write the call.
The machine-readable version is /openapi.json
(OpenAPI 3.1), discoverable from
/.well-known/api-catalog. Generate a
client from that rather than transcribing this page.
Conventions
Every route is a GET with query parameters and no request body, except the two BYOD
writes. Responses are JSON except tiles (Mapbox Vector Tile bytes), terrain (PNG), glyphs
and sprites.
Coordinates are longitude,latitude, in that order, in WGS84 degrees. Bounding boxes are
minlng,minlat,maxlng,maxlat.
Authentication is one key in any of three places, checked in this order:
| Form | When |
|---|---|
Authorization: Bearer um_live_… | Server-side calls. The normal form. |
X-API-Key: um_live_… | Clients that reserve Authorization for something else. |
?key=um_live_… | Required for tiles, glyphs, sprites, terrain and contours: MapLibre cannot attach headers to the requests it fires for them. Stripped before the response is cached. |
Auth runs before routing, so an unauthenticated request to any path returns 401,
including a path that does not exist. See Authentication.
Add-ons gate the industry layers and grammars: legal-land, energy, agriculture,
remote, mining. A key without the one a request needs gets 403 with the add-on
named. See Layers for how to buy one.
Maps
GET /styles/{style}.json
A MapLibre style document. The one URL most applications need.
| Parameter | In | Notes |
|---|---|---|
style | path, required | base, muted, outdoor, blueprint, blush, orchid, canopy, lagoon, tropic, sunset, bold, pastel. An optional -light or -dark suffix sets the mode inline. |
mode | query | light or dark. An unrecognised value is ignored, not rejected. |
lang | query | BCP 47 primary subtag, default en. fr-CA resolves to fr. Falls back to Accept-Language, then en. |
theme | query | A ut1. code from unmap.dev/create. A theme replaces the cartography, so it wins over the style in the path. |
overlays | query | Comma-separated catalog layer ids, or byod:<id>. Overrides profile. |
profile | query | energy, agriculture, remote or mining. A composition preset, not a style. |
Returns application/json: a complete style with sources, layers, glyphs,
sprite and metadata.
Also 400 unknown overlay id or profile · 404 the style name does not resolve.
GET /tiles/{z}/{x}/{y}
The Canada basemap, as Mapbox Vector Tile bytes. Range-read from object storage, never through compute, which is why it is the fastest thing here.
z, x, y are path integers. ?key= is required.
Returns application/x-protobuf.
Also 204 the request is inside the archive but there is no tile there · 400
malformed coordinates, as plain text rather than JSON.
GET /terrain/{z}/{x}/{y}
MRDEM-30 elevation for hillshade, Terrarium-encoded. Used by the outdoor style. A
versioned form, /terrain/{version}/{z}/{x}/{y}, exists so a rebuilt archive gets a fresh
URL; those are cached immutable for a year.
Returns image/png. Also 204 no tile · 404 the archive is not configured.
GET /contours/{z}/{x}/{y}
Contour lines from the same DEM, z9 to z13, with no 0 m line. Versioned form available.
Returns application/x-protobuf. Also 204 no tile · 404 not configured.
GET /glyphs/{fontstack}/{range}.pbf
Font glyphs, for example Noto Sans Regular/0-255.pbf. Includes the faces that draw
syllabics. Versioned form available. Not metered.
Returns application/x-protobuf. Also 404 unknown fontstack or range.
GET /sprite/{file}
Sprite sheet, .json or .png, with @2x variants. Versioned form available. Not
metered.
Returns application/json or image/png. Also 400 unexpected extension ·
404 unknown sprite.
Catalog and data
GET /layers
The whole catalog. Raw storage object keys are never exposed; incomplete coverage always is.
Returns application/json, an array of:
{
"id": "energy.wells",
"display_name": { "en": "Wells", "fr": "Puits" },
"namespace": "energy",
"source": "…",
"source_url": "…",
"licence": "…",
"attribution": "…",
"coverage": "…",
"resolution": "…",
"updated_at": "2026-09-10",
"source_updated_at": "…",
"ingestion_version": "1",
"schema_version": "1",
"freshness_policy": "…",
"known_gaps": "…",
"addon": "energy",
"archives": [{ "id": "default", "minzoom": 4, "maxzoom": 12 }],
"source_layer": "wells"
}An archive holding more than one vector layer also carries source_layers, the full list. The
survey grids use it: survey.dls stacks sixteen levels in one file and survey.nts four,
each with its own zoom range, so a client can add exactly the level it wants as its own map
layer. See Layers.
Read known_gaps before you promise coverage to a user. It is written to be accurate
rather than flattering: several layers cover one province, not the country.
GET /layers/{id}
One catalog row, same shape. Also 404 unknown layer.
GET /layers/{id}/{z}/{x}/{y}
Overlay tiles for one catalog layer. ?key= is required.
Returns application/x-protobuf.
Also 204 outside the layer's zoom band, or no tile there · 403 add-on required ·
404 the layer is catalogued but its archive is not uploaded yet.
GET /identify
Which catalog features contain a point. This is the click handler for a map, and it is
not reverse geocoding: /geocode/reverse finds the nearest named
places, identify returns the polygons the point falls inside.
| Parameter | In | Notes |
|---|---|---|
at | query, required | lng,lat. Required unless you send lon and lat separately. |
lon, lat | query | Alternative to at. |
layers | query | Comma-separated identify-capable ids. Default municipalities, which is core, so a key with no add-ons still works. |
geometry | query | true to include each hit's polygon where the row has one. |
Returns application/json:
{
"at": [-114.0719, 51.0447],
"results": [
{ "layer": "municipalities", "id": "ab:calgary", "name": "Calgary", "province": "AB" }
]
}A result carries the fields its layer has: unit and survey_system on survey cells,
uwi, licence, operator and status on energy rows, pid on parcels, geometry
when asked for.
Also 400 bad point or unknown layer · 403 add-on required · 502 / 503 the
backing service is unreachable or still waking.
GET /data/query
Every feature of one layer inside a bounding box. One layer per call, by design.
| Parameter | In | Notes |
|---|---|---|
layer | query, required | One queryable catalog id. |
bbox | query, required | minlng,minlat,maxlng,maxlat. A hard filter: nothing outside it is ever returned. |
limit | query | 1 to 50, default 50. A non-numeric value falls back to the default rather than erroring. |
geometry | query | true to include each feature's geometry. |
Returns application/json: { "layer": …, "bbox": [4], "results": [ … ] }, the
results in the same shape as /identify.
Also 400 unknown layer id or malformed bbox · 403 add-on required · 501 the
layer is real but tile-only, with the queryable ids named in the message.
Geocoding
One URL reads the shape of your query and routes it. You do not pick a backend, and
profile= has no effect here: it composes maps, not searches.
| Looks like | Example | Needs |
|---|---|---|
| Civic address or place | Calgary | core |
| Legal land, DLS or NTS | NW-25-24-1-W5 | legal-land |
| Well identifier | 00/07-10-013-09W4/0 | energy |
| BC parcel id | 010-867-813 | agriculture |
| Facility by name | Kaybob South with layers=facility | energy |
| Rural or general delivery | RR 2 Okotoks | core |
GET /geocode/search
| Parameter | In | Notes |
|---|---|---|
q | query, required | The search text. |
lang | query | Which language renders as name, default en. Every result also carries all its languages under names. |
limit | query | 1 to 50, default 10. |
bbox | query | A hard filter, not a bias. |
layers | query | well or facility. Searches energy instead of civic. |
geometry | query | true to include the survey polygon on a legal-land hit. |
Returns application/json, an array of places:
[
{
"id": "…",
"name": "Iqaluit",
"layer": "locality",
"lng": -68.5170,
"lat": 63.7467,
"score": 18.4,
"names": { "en": "Iqaluit", "iu": "ᐃᖃᓗᐃᑦ" }
}
]A result may also carry distance (metres, on nearby and reverse), category,
address, legal_land, uwi, licence, operator, status, pid, bbox and
geometry, depending on what matched.
Also 400 q missing, or a query that is Canada Post delivery terms and no
community · 403 add-on required for the LSD, UWI or PID grammars.
A successful parse with no matching row returns []. Never a guessed centroid, and never
a silent fall-through to address ranking.
GET /geocode/autocomplete
Type-ahead, same routing as search. q required; lang default en; limit 1 to 50,
default 8. Returns the same array.
GET /geocode/nearby
Places of a category around a point.
| Parameter | In | Notes |
|---|---|---|
category | query, required | One to eight, comma-separated. A taxonomy id (health.pharmacy) or an everyday word in English or French (pharmacy, pharmacie). Case, accents and hyphens are ignored. |
near | query | lng,lat. Exactly one of near and bbox is required. |
bbox | query | Anchors at the box centre with a radius that covers it. "Around here", not a clipping rectangle. |
radius | query | Metres, 50 to 50000, default 5000. An explicit radius beats one derived from bbox. |
limit | query | 1 to 50, default 10. |
lang | query | Default en. |
Returns the same array, carrying distance in metres.
Also 400 an unknown category token, named in the message, or both or neither of
near and bbox.
GET /geocode/reverse
What is near a coordinate, ordered by true geodesic distance. lon and lat are both
required as separate parameters. lang default en; limit 1 to 50, default 5.
Routing
GET /route
Point to point across the Canada graph.
| Parameter | In | Notes |
|---|---|---|
from | query, required | lng,lat. One parameter, unlike /isochrone. |
to | query, required | lng,lat. |
mode | query | auto, car, bicycle, pedestrian or truck, default auto. car and auto are the same profile. Anything else is a 400; nothing falls back silently. |
use_highways | query | 0 to 1, default 0.5. 0 avoids motorways and trunks, 1 prefers them. Driving modes only. |
Truck parameters apply only with mode=truck. Sending one on another mode is a 400,
not a silent ignore.
| Parameter | Default | Range | Unit |
|---|---|---|---|
height | 4.11 | up to 10 | metres |
width | 2.6 | up to 6 | metres |
length | 21.64 | up to 50 | metres |
weight | 21.77 | up to 100 | tonnes gross |
axle_load | 9.07 | 0 to 40 | tonnes per axle |
axle_count | 5 | 2 to 20 | axles |
hazmat | false | boolean | avoids roads that prohibit |
use_truck_route | 0 | 0 to 1 | prefer designated truck routes |
Returns application/json:
{ "distanceMeters": 42731, "durationSeconds": 2184, "geometry": { … } }.
Also 400 unknown mode, or a truck option on another mode · 422 the graph cannot
connect those two points.
GET /isochrone
Travel-time bands from a point. Takes lon and lat as two separate parameters, not
from.
minutes is required: comma-separated bands, fractions accepted, at most four bands and
each 120 minutes or less. Same mode, use_highways and truck parameters as /route.
Returns application/geo+json, a FeatureCollection with one polygon per band.
Also 400 more than four bands, or a band over 120 minutes, with the rule named.
Your own data
Ten overlays per account, 5 MB each. See Bring your own data.
| Route | Does |
|---|---|
POST /byod | Upload application/geo+json, application/vnd.pmtiles, or multipart/form-data with a file field. ?name= sets the display name when the body is not multipart. Returns 201 with the overlay. |
GET /byod | Your overlays and the current quota. |
GET /byod/{id} | One overlay's metadata. |
GET /byod/{id}/data | The stored bytes. |
GET /byod/{id}/{z}/{x}/{y} | Tiles, for a PMTiles upload. Needs ?key=. |
DELETE /byod/{id} | Remove it. 204, no body. |
An overlay is { id, name, kind, bytes, createdAt, url, overlay }, where overlay is
the byod:<id> token you pass to ?overlays=.
Also 400 missing body, wrong content type, over 5 MB, or an eleventh file · 404
unknown id, or one belonging to another account.
Deleting an overlay a style still names leaves that ?overlays=byod:<id> a 404, so
update the style first.
Status codes
| Code | Means |
|---|---|
200 | Success. |
201 | Created, on POST /byod. |
204 | Not an error. A tile request inside the archive with no data at that tile. A missing archive is 404, and the two are kept distinct so a misconfiguration cannot read as empty terrain. |
400 | Bad parameter. The message names the rule. |
401 | No key, or an unknown or disabled key. Also returned for unknown paths, since auth runs before routing. |
403 | The key is valid but lacks the add-on this layer or grammar needs. The body names it. |
404 | The layer is catalogued but its archive is not uploaded, or the style name does not resolve. |
422 | /route only: the graph cannot connect those two points. |
429 | Rate limited. Read Retry-After. |
501 | /data/query only: the layer is real but tile-only. The message lists the queryable ids. |
502, 503 | A backing service is unreachable or still waking. Retry. |
Every error body is { "error": "…" }, with code and addon where they apply. See
Errors for the full list of codes.