Errors
By the end of this guide, you'll be able to diagnose any error Plaza throws at you and know exactly how to fix it.
Error shape
Every error response has this structure:
{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded. Retry after 1710340800."
}
}
The code is machine-readable, the message is human-readable. Match on code in your application logic, show message to users.
400 Bad Request
Something is wrong with your request.
| Code | What happened | What to do |
|---|---|---|
invalid_params |
Missing or invalid query parameters | Check the endpoint docs for required params |
invalid_bbox |
Bounding box coordinates are out of range or the corners are flipped | Format is south,west,north,east. South < north, west < east |
invalid_query |
Overpass QL or SPARQL syntax error | The message field includes the parse error location. Check your query syntax |
bbox_too_large |
Bounding box exceeds 25 square degrees | Narrow your area. 25 sq degrees is roughly a 5x5 degree box |
invalid_element |
Bad OSM element type | Must be node, way, or relation |
401 Unauthorized
Auth problem. Three possible causes:
| Code | What happened | What to do |
|---|---|---|
missing_key |
No x-api-key header |
Add -H "x-api-key: pk_live_YOUR_KEY" to your request |
invalid_key |
Key doesn't match any active key | Check for typos. Make sure you copied the full key, not just the pk_live_ prefix |
revoked_key |
Key was revoked | Someone (maybe you) revoked this key. Create a new one from the dashboard |
402 Payment Required
| Code | What happened | What to do |
|---|---|---|
quota_exceeded |
Hit the 10,000 calls/month free tier cap | Upgrade your plan or wait for the next billing cycle |
404 Not Found
| Code | What happened | What to do |
|---|---|---|
not_found |
Element or endpoint doesn't exist | Double-check the OSM ID and type. The element may have been deleted from OSM |
408 Request Timeout
| Code | What happened | What to do |
|---|---|---|
timeout |
Query took longer than 30 seconds | Your query is too broad. Add tag filters, tighten the bounding box, or split into multiple smaller queries |
429 Too Many Requests
| Code | What happened | What to do |
|---|---|---|
rate_limited |
Exceeded your plan's rate limit | Check x-ratelimit-reset in the response headers for when you can retry. See authentication for your plan's limits |
500 Internal Server Error
| Code | What happened | What to do |
|---|---|---|
internal_error |
Bug on our end | If you can reproduce it, please report it. Include the request that triggered it |
503 Service Unavailable
| Code | What happened | What to do |
|---|---|---|
unavailable |
Service is down for maintenance or overloaded | Retry with backoff. Check status.plaza.fyi for incident updates |
Rate limit headers
Every response (not just errors) includes these headers:
x-ratelimit-limit: 60
x-ratelimit-remaining: 58
x-ratelimit-reset: 1710340800
x-ratelimit-limit-- your plan's per-minute capx-ratelimit-remaining-- requests left in the current windowx-ratelimit-reset-- Unix timestamp when the window resets
Check remaining before you hit zero. It's cheaper to throttle yourself than to eat 429s.
Retry strategy
For 429 and 503, use exponential backoff with jitter:
import time, random
def request_with_retry(fn, max_retries=5):
for attempt in range(max_retries):
response = fn()
if response.status_code not in (429, 503):
return response
wait = min(2 ** attempt + random.random(), 30)
time.sleep(wait)
raise Exception("Max retries exceeded")
For 429 specifically, you can do better: read the x-ratelimit-reset header and sleep until that exact timestamp instead of guessing with exponential backoff.
Don't retry 400 or 401 errors. Those won't fix themselves.