Errors
All errors are returned as application/json regardless of the requested output format.
HTTP status codes
| Status | Meaning | When it happens |
|---|---|---|
200 OK | Success | Map rendered and returned. |
304 Not Modified | Cached — no body | Sent when If-None-Match matches the current ETag. |
400 Bad Request | Validation or parser failure | A parameter value is invalid — wrong type, out of range, missing scope, or a data/markers/routes/patterns/annotations string could not be parsed. |
404 Not Found | Unknown scope | The scope value is not in the loaded topology. |
413 Content Too Large | Body too large | POST body exceeds 4 MB. |
Error shape
Every error response has this JSON structure:
{
"error": "error_code",
"message": "Human-readable description of the problem.",
"status": 400
}
error— a machine-readable code identifying the specific error.message— a human-readable explanation, often including the invalid value and what was expected.status— mirrors the HTTP status code.
Example 400 — invalid theme:
{
"error": "invalid_theme",
"message": "theme \"ocean\" not found. Available: light, light-blue, light-mono, dark, dark-blue, dark-mono",
"status": 400
}
Example 400 — missing scope:
{
"error": "missing_scope",
"message": "scope is required",
"status": 400
}
Example 404 — unknown scope:
{
"error": "invalid_scope",
"message": "Scope \"eu\" not found",
"status": 404
}
Example 400 — malformed data parameter:
{
"error": "invalid_data",
"message": "bad pair \"US-abc\" (expected id:value[:color])",
"status": 400
}
Example 400 — numeric and categorical values mixed in one request:
{
"error": "mixed_data_types",
"message": "data values must be either all numeric or all categorical labels, not a mix",
"status": 400
}
Common mistakes
Invalid scope
scope must exactly match a loaded topology ID. IDs are case-sensitive. Use the Scopes list to find the correct value.
# Wrong — lowercase
?scope=ro
# Correct
?scope=RO
Malformed data=
Each pair must be one of id:value, id:value:#rrggbb, id:#rrggbb, or id:label. The # must be URL-encoded as %23.
# Wrong — missing colon
?data=US,CN,DE
# Wrong — unencoded #
?data=US:100:#ff0000
# Wrong — looks like a typo'd hex (embedded # in a label is rejected)
?data=US:dem#ocracy
# Correct — numeric values
?data=US:100,CN:80,DE:60
# Correct — numeric + per-region color override
?data=US:100:%23ff0000,CN:80,DE:60
# Correct — color-only paint, no values
?data=US:%23dc2626,CN:%232563eb,DE:%2316a34a
# Correct — categorical labels (auto-categorical)
?data=US:NATO,CN:BRICS,DE:NATO
Mixing numeric and string values
A single request must commit to one kind of value — magnitudes for choropleth or labels for categorical, not both. Combining them returns 400 mixed_data_types. Color-only pairs (id:#hex) coexist with either kind, so a request like data=US:200,CN:150,DE:#ff0000 is fine; data=US:200,CN:BRICS is not.
Mixing data= and regions=
data= and regions= serve different modes. data= encodes numeric values, text labels, or per-region colors; regions= is highlight-only with a single shared accent. They are processed separately and merged, but passing both for the same region ID produces undefined colour precedence. Use one or the other per region.
Oversized POST body
The POST endpoint accepts up to 4 MB. If your payload exceeds this limit, the server returns 413. Consider:
- Reducing decimal precision in value fields.
- Omitting regions with
nullvalues if you only want to highlight a subset. - Splitting annotation strings that are close to the 200-character limit.
Too many regions
The maximum is 5000 regions per request. Requests exceeding this return:
{
"error": "too_many_regions",
"message": "region count exceeds limit (5000)",
"status": 400
}