M Motionworks Population Intelligence

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_tiles and oohdisplays.displays.face_point_tiles features. 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 returns 403 TILE_GRANTS_REQUIRE_METERED; contact sales to enable tile delivery.
  • Credentials: a signed-in session token (Authorization: Bearer <token>) or an org-scoped X-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 (see API_KEY_NO_AUDIT_USER below).

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 for oohdisplays.displays.inventory_tiles or oohdisplays.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: a tilesets[] 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's Origin is not in allowed_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).