Skip to main content

Response

Both GET /map.{svg,png} and POST /render/map return the same response structure.

Content types

FormatContent-Type
SVGimage/svg+xml
PNGimage/png

On POST, the format comes from the format field and defaults to SVG. On GET it comes from the URL extension (/map.svg or /map.png); a format query parameter overrides the extension when both are present.

Caching

GET endpoint

GET responses include:

Cache-Control: public, max-age=31536000, immutable
ETag: "<sha1-of-normalised-request>"

The ETag value is a SHA-1 hash of the normalised request parameters (scope, theme, data values, width, projection, and all other inputs). Two requests with semantically identical inputs produce the same ETag: query-parameter order and region-ID capitalisation are normalised away. Enumerated values are not folded — theme=DARK is rejected as an unknown theme rather than treated as dark.

Requests carrying a valid API key are the exception to the long cache. Shared caches key on the URL and not on headers, so a keyed response must never be storable by one — otherwise a single keyed request would warm the edge with an un-watermarked map that every anonymous caller of the same URL would then receive. Keyed GET responses are sent as:

Cache-Control: private, no-store

The ETag and the 304 behaviour are unchanged; only cacheability differs.

POST endpoint

POST responses include an ETag header with the same SHA-1 semantics, but do not include Cache-Control. The response is served from the in-process LRU cache when the same normalised input has been seen before, but clients and CDNs are not instructed to cache POST responses.

Conditional requests

The GET endpoint supports If-None-Match. If you send the ETag value back on a subsequent request, the server returns 304 Not Modified with no body, skipping the render pipeline entirely:

GET /map.png?scope=world&data=US:200,CN:150&theme=dark
→ 200 OK
ETag: "a3f9c2..."
Cache-Control: public, max-age=31536000, immutable

GET /map.png?scope=world&data=US:200,CN:150&theme=dark
If-None-Match: "a3f9c2..."
→ 304 Not Modified

The POST endpoint returns an ETag but does not currently support If-None-Match conditional requests.

X-Cache header

Both endpoints return X-Cache: HIT or X-Cache: MISS to indicate whether the response was served from the in-process LRU cache.

Cloudflare edge caching (GET only)

When GET requests are served behind Cloudflare, the Cache-Control: public, max-age=31536000, immutable header causes Cloudflare to cache the response at the edge. Subsequent requests for the same URL are served directly from Cloudflare's CDN without reaching the origin server. Because all map parameters are encoded in the URL, each unique map is a separate cache entry.

Keyed requests are excluded: their private, no-store response is not stored at the edge. Cloudflare may also return the ETag in weak form (W/"…"); sending back either the weak or the strong form on a later request yields a 304.

X-Geo-Warnings

When part of a request cannot be resolved or rendered — an unknown region ID or route endpoint, an unusable GeoJSON feature, an unrecognised marker icon — the server renders everything it can and reports the rest in a warning header. Tokens are separated by ; :

X-Geo-Warnings: unknown-ids:XX,YY; unknown-routes:AA>BB
  • unknown-ids — region IDs that appeared in data, regions, per-region colour overrides, patterns, or annotations but were not in the topology. Up to 50 IDs are listed. IDs used only in proportional are not checked and never appear here.
  • unknown-routes — route endpoints that could not be resolved to coordinates. Up to 50 route pairs are listed. A sea-mode route whose endpoints resolved but for which no sea lane could be found is listed with a sea: prefix (sea:NL>CN).
  • invalid-shapes — features in shapes that could not be rendered, as index:reason pairs. Up to 50 are listed.
  • invalid-markers — markers that could not be rendered as requested, as index:reason pairs (e.g. 0:unknown-icon:notanicon). The marker still renders, with a fallback glyph. Up to 50 are listed.

The response is still 200 OK — your map renders with whatever IDs did match. Check this header to diagnose missing regions.