Minting a Displays tile grant
Tile access runs through grants: a minted record that authorizes your organization to fetch Displays vector tiles. A grant returns a tile_json_url capability URL you drop into MapLibre or Mapbox GL JS.
All grant management endpoints are free (0 credits): grant CRUD and the TileJSON fetch are control-plane operations. Only the tile bytes (GET .../displays/tiles/{layer}/{z}/{x}/{y}.mvt) are metered, at 1 credit per 1000 tiles.
Prerequisites
- Your organization must be licensed for the
oohdisplays.displays.inventory_tilesandoohdisplays.displays.face_point_tilesfeatures. Tile traffic is charged to your organization at 1 credit per 1,000 tiles served; if your billing setup cannot carry tile traffic, grant creation returns403 TILE_GRANTS_REQUIRE_METERED; contact sales to enable tile delivery. - Credentials: a signed-in session token (
Authorization: Bearer <token>) or an org-scopedX-API-Key. Anonymous callers cannot mint grants. Grants are attributed to the user who created them, so an API key must have been created by a user (seeAPI_KEY_NO_AUDIT_USERbelow).
Create a grant
POST /v2/displays/tiles/grants
Request body:
| Field | Type | Meaning |
|---|---|---|
name |
string (max 120) | Human-readable label, shown in the developer dashboard |
tilesets |
string[] | Layer slugs to include, from the discovery catalog: faces, viewsheds, paths_vehicle, paths_pedestrian, gates_vehicle, gates_pedestrian. Any other slug is rejected with 403 TILE_LAYER_NOT_LICENSED. |
allowed_origins |
string[] | Browser Origin allowlist for TileJSON and tile fetches. Wildcards are not supported; list each origin literally. Use the explicit string "null" to allow requests that omit Origin (server-side rendering, curl). |
expires_at |
ISO 8601 datetime | Grant expiry. Note the tile-token inside the TileJSON independently expires 24h after each mint; re-fetch TileJSON before then. |
Example:
curl -X POST "https://api2.mworks.com/v2/displays/tiles/grants" \
-H "X-API-Key: ***" \
-H "Content-Type: application/json" \
-d '{
"name": "Q3 inventory map",
"tilesets": ["faces", "viewsheds", "paths_vehicle"],
"allowed_origins": ["https://maps.example.com"],
"expires_at": "2026-12-31T00:00:00Z"
}'
Response (201):
{
"data": {
"grant_id": "01J5XH9K3W2Q5M0N8P7R4T6VYC",
"tile_json_url": "https://api2.mworks.com/v2/displays/tiles/grants/01J5XH9K3W2Q5M0N8P7R4T6VYC/tilejson",
"tilesets": ["faces", "viewsheds", "paths_vehicle"],
"allowed_origins": ["https://maps.example.com"],
"expires_at": "2026-12-31T00:00:00Z",
"created_at": "2026-08-10T20:05:11Z"
}
}
The tile_json_url is a capability URL: treat it like an AWS S3 presigned URL. The opaque grant_id segment IS the credential; anyone holding the URL can render tiles from an allowed origin until the grant expires or is revoked. Two safety nets are built in: allowed_origins pins which sites can fetch tiles (Origin-enforced), and revoke kills the grant.
The entitlement is one-feature-all-layers: any of the six published slugs implies access to all of them. There is no per-layer scoping.
List, read, revoke
| Operation | Endpoint | Notes |
|---|---|---|
| List grants | GET /v2/displays/tiles/grants |
Your organization's grants, most recent first. Each grant carries usage_30d, reported as 0 today until usage reporting ships. |
| Read one | GET /v2/displays/tiles/grants/{id} |
Scoped to the caller's org; expired or foreign grants read as 404. |
| Revoke | POST /v2/displays/tiles/grants/{id}/revoke |
Revokes the grant immediately. New TileJSON requests for it fail with 404, and tile requests that use tokens already minted from it are refused as well (401 UNAUTHORIZED with error.context.reason: grant_revoked), so revoking is the instant cutoff for a leaked URL. Revoking an already-revoked grant returns 200 with the unchanged record. |
Error cases
403 FEATURE_NOT_LICENSED: org is not licensed foroohdisplays.displays.inventory_tilesoroohdisplays.displays.face_point_tiles. Contact sales.403 TILE_GRANTS_REQUIRE_METERED: tile delivery for your organization requires pay-as-you-go (metered) billing, which is not enabled. Contact sales to enable tile delivery.403 TILE_LAYER_NOT_LICENSED: atilesets[]entry names a slug Displays does not publish.403 API_KEY_NO_AUDIT_USER: the API key is not tied to a user, so the grant cannot be attributed. Mint with a signed-in session, or with an API key created by a user.403 ORIGIN_NOT_ALLOWED: the fetch'sOriginis not inallowed_origins.400: invalid request body.401: missing or invalid credentials.
Deny envelope: every error response on this surface uses the standard envelope shape. Example for an unlicensed org:
{
"error": {
"code": "FEATURE_NOT_LICENSED",
"message": "Your organization is not licensed for oohdisplays.displays.inventory_tiles.",
"status": 403,
"request_id": "req_e8281bd0f5a1",
"product": "displays",
"context": {}
}
}
Expired grants read as 404 NOT_FOUND, not 403 or 410: a foreign, nonexistent, or expired grant id is indistinguishable from the outside. A revoked grant stays readable through the grant endpoints with revoked_at set, but its TileJSON returns 404 and every tile request made with a token minted from it is refused with 401 UNAUTHORIZED (grant_revoked), even if that token has not yet expired.
Token lifecycle
Each GET /tilejson mints a fresh opaque tile token valid for 24 hours, embedded in the manifest's tiles[] URLs. The manifest's x-mw.refresh_after (82,800 seconds, 23h) tells clients when to re-fetch the TileJSON for a fresh token. Standard TileJSON consumers append the ?token= query to every tile request automatically; you do not call the .mvt endpoint by hand, and the token is accepted only as a query parameter, never as a header. Revoking the grant invalidates outstanding tokens immediately (see above).