Qartas Error Reference
Error envelope
JSON endpoints return errors as:
{
"status": "error",
"error": {"code": "MACHINE_CODE", "message": "human text", "details": {"...": "..."}},
"message": "human text"
}
- Branch on
error.code, display error.message. details appears only
when there is structured context (e.g. a request_id on 5xx).
- 5xx bodies are sanitized: whatever went wrong internally, the wire
message is
"An internal error occurred. Please try again later." plus a
request_id in details — quote that ID (or the X-Request-ID response
header, same value) when reporting.
- Binary tile-family endpoints (
/tiles, /glyphs, /sprites,
/staticmap) return a bare status with no body for parameter and
upstream errors; only their auth failures use the JSON envelope.
- Two envelope deviations to tolerate in a strict parser:
REQUEST_TIMEOUT (504) omits the top-level message field, and
SESSION_SUPERSEDED (401, JWT path only) uses a flat
{"status","code","message"} shape.
Complete code catalog
Authentication — API key (all requests)
| Code |
HTTP |
Meaning |
Handling |
MISSING_TOKEN |
401 |
No X-API-Key and no Authorization header on a credentialed endpoint. |
Send your key. |
INVALID_API_KEY |
401 |
Key unknown, malformed, or revoked. |
Check for truncation/whitespace; if the key was working before, it has been revoked — contact the operator. Do not retry unchanged. |
API_KEY_EXPIRED |
401 |
Key exists but is inactive or past its expires_at. |
Contact the operator for a replacement. |
MAPS_AUTH_UNAVAILABLE |
503 |
Server-side auth subsystem misconfigured/unavailable. |
Transient from your perspective — retry with backoff; report if persistent. |
Authentication — tile family (/tiles, /style, /glyphs, /sprites)
| Code |
HTTP |
Meaning |
Handling |
AUTH_REQUIRED |
401 |
Neither X-API-Key nor ?t= presented. |
Attach a credential. |
INVALID_TOKEN |
401 |
Tile token malformed or signature mismatch. |
Re-mint via POST /maps/v1/tile-token; pass it URL-unmodified. |
TOKEN_EXPIRED |
401 |
Tile token past its expiry (24 h default). |
Mint a new token. Build refresh at ~80% of lifetime (the SDK does). |
TOKEN_NOT_KEY_BOUND |
401 |
Token is valid but wasn't minted from an API key (or its binding is malformed). |
Mint the token with your API key. |
API_KEY_REVOKED |
401 |
The key this token was minted from is no longer active. |
Contact the operator. All that key's tokens are dead. |
Rate limiting & quota
| Code |
HTTP |
Meaning |
Handling |
RATE_LIMIT_EXCEEDED |
429 |
Per-minute limit hit (per-key on credentialed endpoints, per-IP on the tile family). Retry-After: <seconds left in window>. |
Back off for Retry-After, then retry. Add client-side throttling/debouncing if recurrent. |
MAPS_QUOTA_EXCEEDED |
429 |
Daily miss-quota exhausted for this key (bucket-specific: "gather" for JSON endpoints, "tiles" for staticmap). Retry-After: 3600. |
Resets at midnight UTC. If hit in normal operation, your plan is undersized — contact the operator. |
Request validation (fix the request; never retry unchanged)
| Code |
HTTP |
Endpoint(s) |
Meaning |
VALIDATION_ERROR |
400 |
any JSON-body endpoint |
Body failed binding: missing/empty required field, non-JSON body, or a required numeric field sent as 0 (zero coordinates are rejected). Message names the field. |
SAME_POINT |
400 |
/directions, /prefetch-directions |
Origin ≈ destination with no waypoints. |
TOO_MANY_WAYPOINTS |
400 |
/directions, /prefetch-directions |
More than 4 waypoints. |
BAD_WAYPOINT |
400 |
/directions, /prefetch-directions |
A waypoint is out of range, NaN/Inf, or (0,0). |
ENRICH_MANEUVERS_UNSUPPORTED |
400 |
/directions, /prefetch-directions |
enrich_maneuvers: true was sent — not available on this service; omit the flag (native maneuvers[] still appear when the routing tier returns them). |
MISSING_ID |
400 |
/place/{id} |
Empty place ID. |
BAD_PLACE_ID |
400 |
/place/{id} |
ID doesn't match any known place-ID shape (after g_/w_ prefix strip). |
MISSING_COORDS |
400 |
/place-photos |
lat/lng missing or non-numeric. |
Service availability & upstream (transient — retry with backoff)
| Code |
HTTP |
Retry-After |
Meaning |
MAPS_UNAVAILABLE |
503 |
— |
The maps engine is not enabled on this server (also used by tile auth when the engine is down). Genuine outage/maintenance signal. |
AUTOCOMPLETE_BACKPRESSURE / GEOCODE_BACKPRESSURE / PLACE_BACKPRESSURE |
503 |
3 |
Gather pools momentarily saturated; the request was shed before doing work. Retry after the delay. |
REVERSE_BACKPRESSURE |
503 |
2 |
Same, reverse-geocode pool. |
DIRECTIONS_BACKPRESSURE |
503 |
5 |
Same, routing pool. |
AUTOCOMPLETE_UNAVAILABLE / GEOCODE_UNAVAILABLE / REVERSE_UNAVAILABLE / PLACE_UNAVAILABLE |
503 |
5 |
Every source tier failed for this lookup. Retry shortly. |
AUTOCOMPLETE_DISABLED / GEOCODE_DISABLED / REVERSE_DISABLED / PLACE_DISABLED |
503 |
— |
Feature disabled server-side. Don't hot-retry; report if unexpected. |
AUTOCOMPLETE_FAILED / GEOCODE_FAILED / REVERSE_FAILED / PLACE_FAILED |
502 |
— |
Unclassified upstream failure (catch-all). Retry with backoff. |
DIRECTIONS_UNROUTABLE |
422 |
— |
No route exists between the coordinates (e.g. across an ocean, outside the routable region). Not transient — don't retry unchanged. |
VECTOR_TILES_NOT_SUPPORTED |
501 |
— |
Vector tiles are not part of this product (deliberate). Never retry. |
TILE_TOKEN_FAILED |
500 |
— |
Token minting failed server-side (misconfiguration). Sanitized body + request_id; report it. |
REQUEST_TIMEOUT |
504 |
— |
The 30 s whole-request timeout fired. Retry; for directions prefer max_wait_ms so you get the degraded-200 instead. (Envelope note: no top-level message field on this one.) |
Remember: /directions converts most engine failures into a degraded
200 (partial: true, source: "server_straight") rather than a 5xx —
absence of an error does not mean you have a road route. Check partial on
every directions response.
Non-error "empty" answers (HTTP 200 — do not treat as failures)
| Signal |
Endpoint |
Meaning |
predictions: [] + budget_expired: true |
autocomplete |
Not authoritative — still gathering. Retry shortly. |
predictions: [] + budget_expired: false |
autocomplete |
Authoritative no-match. |
matched: false |
geocode |
No match for the address. |
source: "no_address" |
reverse |
Genuinely nothing at that coordinate. |
photos: [] |
place-photos |
No photos exist there (common). |
queued: false, reason: "prefetch_saturated" |
prefetch-directions |
Prefetch skipped (pool full) — harmless. |
Bare-status errors on binary endpoints (no JSON body)
| Status |
Endpoints |
Meaning |
Retry? |
| 400 |
tiles, glyphs, staticmap |
Malformed path/params (bad z/x/y, unsupported format, path traversal, <2 staticmap points). |
No. |
| 404 |
tiles, style, glyphs, sprites |
Asset doesn't exist (unknown style name, unbundled glyph range, origin tile miss). |
No. |
| 502 |
tiles, staticmap |
Transient upstream failure / non-image origin body (tiles send Retry-After: 1). |
Yes, briefly deferred. |
| 503 |
tiles |
Degraded tile on a prefetch request (X-Chaf-Prefetch), or service disabled. |
Yes, later. |
| 504 |
tiles |
Origin fetch timed out (Retry-After: 1). |
Yes, briefly deferred. |
Suggested retry policy
| Class |
Policy |
| 400 / 422 / 501 |
Never retry unchanged — fix the request (or, for 501, drop the feature). |
| 401 |
Re-mint tile tokens on TOKEN_EXPIRED/INVALID_TOKEN; otherwise escalate (key problem), don't loop. |
| 429 |
Wait exactly Retry-After, retry once, then surface to your own throttling. |
503 with Retry-After |
Honor it (2–5 s), retry up to 2–3 times with jitter. |
| 502 / 504 / other 503 |
Exponential backoff with jitter, small cap (e.g. 1 s, 2 s, 4 s). |
| 500 |
Retry once after a few seconds; then report with the request_id. |
This catalog was produced by enumerating every response.Error /
AbortWithStatusJSON / bare-status call site in internal/api/*.go and the
engine's tile error mapping (internal/engine/tiles.go,
internal/api/tiles.go), then spot-checking representative cases live
(SAME_POINT, TOO_MANY_WAYPOINTS, ENRICH_MANEUVERS_UNSUPPORTED,
BAD_PLACE_ID, VALIDATION_ERROR, INVALID_API_KEY, MISSING_TOKEN,
AUTH_REQUIRED, INVALID_TOKEN, RATE_LIMIT_EXCEEDED,
VECTOR_TILES_NOT_SUPPORTED, post-revoke 401s) on 2026-07-31. Codes not
individually triggered live (the 5xx family, backpressure/unavailable states,
JWT-path codes) are verified from source only.
← Documentation