Minting an Anytime tile grant
Create, read, and revoke Popcast Anytime tile-delivery grants. Grants are the capability that unlocks the TileJSON URL your map client consumes.
Tile access runs through grants: a minted record that authorizes your organization to fetch Popcast Anytime 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 .../anytime/{layer}/{z}/{x}/{y}.mvt) are metered, at 1 credit per 1000 tiles.
Prerequisites
- Your organization must be licensed for the
popcast.anytime.segment_occ_tilesfeature. This feature is manual-grant-only: an explicit grant row is required. Unlicensed orgs get403 FEATURE_NOT_LICENSED. Contact sales for an explicit grant. - Credentials: a Supabase JWT (
Authorization: Bearer <jwt>) or an org-scopedX-API-Key. Anonymous callers cannot mint grants.
Create a grant
POST /v2/popcast/tiles/anytime/grants
Request body:
| Field | Type | Meaning |
|---|---|---|
name | string (max 120) | Human-readable label, shown in the developer dashboard |
tilesets | string[] (max 12) | Tileset slugs from the discovery catalog. An empty array means all twelve published tilesets; the persisted grant carries the expanded concrete set. An unknown 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://api.mworks.com/v2/popcast/tiles/anytime/grants" \
-H "X-API-Key: <your-org-api-key>" \
-H "Content-Type: application/json" \
-d '{
"name": "Q3 campaign map",
"tilesets": [],
"allowed_origins": ["https://maps.example.com"],
"expires_at": "2026-12-31T00:00:00Z"
}'
Response (201):
{
"data": {
"grant_id": "01J5XH9K3W2Q5M0N8P7R4T6VYC",
"tile_json_url": "https://api.mworks.com/v2/popcast/tiles/anytime/grants/01J5XH9K3W2Q5M0N8P7R4T6VYC/tilejson",
"tilesets": ["anytime_21d529097c81af04_202501-202512_bg_tiles_v4", "..."],
"allowed_origins": ["https://maps.example.com"],
"expires_at": "2026-12-31T00:00:00Z",
"created_at": "2026-08-08T20: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-tilesets: any of the twelve published slugs implies access to all of them. There is no per-segment scoping.
List, read, revoke
| Operation | Endpoint | Notes |
|---|---|---|
| List grants | GET /v2/popcast/tiles/anytime/grants | Caller-org's grants, most recent first. Each grant carries usage_30d (placeholder; reported as 0 today, outbox aggregate lands in a follow-up). |
| Read one | GET /v2/popcast/tiles/anytime/grants/{id} | Scoped to the caller's org; expired or foreign grants read as 404. |
| Revoke | POST /v2/popcast/tiles/anytime/grants/{id}/revoke | Marks the grant revoked immediately. Caveat: tile-token JWTs already minted by the TileJSON endpoint remain valid until they expire (max 24h). |
Error cases
403 FEATURE_NOT_LICENSED: org has no explicit grant forpopcast.anytime.segment_occ_tiles. Contact sales.403 TILE_LAYER_NOT_LICENSED: atilesets[]entry names a slug Popcast Anytime does not publish. Fires on unknown slugs only, never as a licensed-subset rejection (the entitlement is all-tilesets).403 TILE_GRANTS_REQUIRE_METERED: org has no metered Stripe billing rail. Under the deployedaccumulatemeter mode this code is unreachable (credit-pack wallets are billed in-transaction); the license gate is the sole gate in practice.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 popcast.anytime.segment_occ_tiles.",
"status": 403,
"request_id": "req_e8281bd0f5a1",
"product": "popcast",
"context": {}
}
}Expired grants read as 404 NOT_FOUND (Grant expired.), not 403 or 410: a foreign, nonexistent, or expired grant id is indistinguishable from the outside. Revocation deletes the served record (subsequent reads 404), while tile-token JWTs already minted by the TileJSON endpoint remain valid until they expire (max 24h) unless the grant's tileset access is revoked earlier.
Token lifecycle
Each GET /tilejson mints a fresh tile-token JWT valid for 24 hours, embedded in tiles[]. Tokens are signed kid: popcast-anytime-v1; cross-product tile tokens are structurally rejected at verification, so a Popcast Anytime token never authorizes another product's tiles. 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.
Updated 3 days ago