Errors
Errors are JSON, like everything else, with an HTTP status that matches and a code you can switch on. The message is a sentence meant for your logs, not for your users; it may change wording, the code never will.
The error envelope
{
"status": "error",
"error": {
"code": "invalid_request",
"message": "Parameter 'lat' must be a number between -90 and 90.",
"param": "lat"
}
}param is present when a specific parameter caused the problem. X-Request-Id is set on error responses too; include it if you contact support.
Codes
| HTTP | code | When | What to do |
|---|---|---|---|
| 400 | https_required | The request came over plain HTTP. Only HTTPS is served, so that a key is never sent in the clear. | Call the same URL with https://. |
| 400 | invalid_request | A required parameter is missing, a value is malformed or out of range, more than 100 points or addresses in a batch, more than 10 results requested. | Fix the request. Do not retry unchanged. |
| 401 | missing_key | No key was sent and keyless access is switched off on this server. By default it is on, and a request without a key is answered. | Send a key as X-API-Key, a bearer token or key=. |
| 429 | quota_exceeded | The address has used its 2,500 free requests for the day without a key (for calls from scripts on a web page: the website has). | Wait for the reset (Retry-After and X-Quota-Reset say when), or send a key that carries credit or an Unlimited package. |
| 401 | invalid_key | The key does not exist. | Check for typos or a truncated copy. |
| 401 | key_revoked | The key was revoked in the dashboard. | Use a current key. |
| 402 | no_credits | The account's free allowance for the day is used and its credit balance is empty. | Add credit in the dashboard, by card or crypto, or wait for the reset. |
| 403 | key_ip_limit | All of this key's IP slots are currently held by other addresses. | Use one of the slot-holding machines, give this machine its own key, or add a package. See IP slots. |
| 403 | browser_key_origin | A browser key was used from another website, or without an Origin or Referer header, or before its website was set. | Call it only from scripts on its website; use a server key elsewhere. See keys and browsers. |
| 403 | account_suspended | The account is suspended. | Check email from us or open a ticket. |
| 403 | email_unverified | The account has not confirmed its email address yet. | Open the confirmation link in the email we sent. |
| 403 | package_suspended | The Unlimited package behind the key is suspended. | Open a ticket. |
| 404 | not_found | The path does not exist. A query with no matches is 200 with empty results, not 404. | Check the path and version prefix. |
| 429 | rate_limited | One visitor sent more than 60 requests a minute on a browser key, or too many asynchronous batch jobs on a drop-in host are waiting to be fetched. | Wait for Retry-After, or fetch the waiting results first. |
| 429 | key_daily_cap | The key has reached the daily spending limit its owner set. | Wait for the reset at 00:00 UTC, or raise the limit on the key page. |
| 500 | server_error | Something broke on our side. | Retry once after a second. It is logged and alerts us. Not counted. |
| 503 | unavailable | The service is temporarily unavailable. | Retry with backoff. Not counted. |
| 503 | backend_unavailable | The elevation service is not answering. | Retry with backoff (Retry-After is 10 seconds). Not counted. |
| 503 | maintenance | The API is in a maintenance window. | Retry after Retry-After. Not counted. |
Things that are not errors
- No results. A forward geocode of gibberish returns
200with"results": []. A reverse geocode in the ocean returns200with"result": null. Both count as a request. - Low precision. A result with
"precision": "admin"when you wanted a house is a successful request that found less than you hoped. Check the field. - Private IP addresses. Looking up
10.0.0.1returns200with"is_private": trueand no location.
Errors on the drop-in hosts
The compatibility hosts return errors in the shape the original provider uses, so existing handling works. For example, an empty credit balance is "status": "OVER_QUERY_LIMIT" with HTTP 200 on gapi.mygeocode.com, "statusCode": 429 in the Bing envelope on bing.mygeocode.com, and {"status":"fail","message":"quota"} on ipapi.mygeocode.com; a bad key is REQUEST_DENIED, 401 and invalid key respectively. The X-Quota-* headers are sent on every host regardless, so you can read the real state from the headers if you want to.
A reasonable handling strategy
def call(url, params, tries=3):
for attempt in range(tries):
r = session.get(url, params=params, timeout=10)
if r.status_code == 200:
return r.json()
body = r.json()
code = body["error"]["code"]
if r.status_code in (500, 503):
time.sleep(0.2 * (2 ** attempt))
continue
# no_credits, key_ip_limit, invalid_request, invalid_key: retrying will not help
raise ApiError(code, body["error"]["message"], r.headers.get("X-Request-Id"))
raise ApiError("retries_exhausted", "Gave up after %d attempts" % tries, None)