ReferenceErrors
Errors
Every error is JSON with a stable machine-readable code and a short message for people. Branch on error, never on the message.
Shape
HTTP/1.1 404 Not Found
content-type: application/json
{
"error": "reserved_address",
"message": "private or reserved addresses have no location"
}The status code tells you who should act: 4xx means change the request, 429 and 503 mean wait and retry. Inside a bulk response, entries that fail carry only {"ip", "error"} and the request itself still succeeds.
Codes(18)
| Status | Code | Meaning and what to do |
|---|---|---|
| 400 | invalid_ip | Not a valid IPv4 or IPv6 address. Send the address without port, brackets or zone ID. |
| 400 | invalid_lang | lang is not one of en, de, es, fr, ja, pt-BR, ru, zh-CN. |
| 400 | invalid_body | The bulk body is not {"ips": ["..."]}. The message names the JSON problem. |
| 401 | unauthorized | The key is missing, unknown or disabled. See Authentication. |
| 404 | not_found | The database has no location for this address. Not an error in your request. |
| 404 | reserved_address | A private, loopback, link-local, documentation, multicast or other special-purpose address. These never have a location. |
| 404 | route_not_found | No such endpoint. Check the path and the /v1 prefix. |
| 405 | method_not_allowed | The endpoint exists but not with this method. |
| 413 | payload_too_large | The body is larger than server.max_body_bytes. Send fewer addresses per request. |
| 415 | unsupported_media_type | A bulk request without Content-Type: application/json. |
| 422 | empty_batch | ips is empty. |
| 422 | batch_too_large | More addresses than limits.bulk_max_ips. Split the batch. |
| 429 | rate_limited | Out of units. Wait Retry-After seconds, then retry. See Rate Limits. |
| 429 | exceeds_burst | The request costs more than the key's burst and can never succeed. Split the batch; do not retry. |
| 500 | internal_error | A bug or a damaged database. The server keeps running; report it with the time of the request. |
| 503 | database_unavailable | No database is loaded yet, usually right after the first start. Retry-After: 30. |
| 503 | overloaded | Too many requests in flight (server.max_inflight_requests). Retry-After: 1. |
| 503 | timeout | The request took longer than server.request_timeout_ms. Retry-After: 1. |
Retrying
Retry only 429 rate_limited and 503, after the Retry-After delay, with a little jitter so clients do not return in step. Give up after a few attempts.
const BASE = "http://localhost:8080";
async function locate(ip, attempts = 3) {
const url = `${BASE}/v1/lookup/${encodeURIComponent(ip)}`;
for (let i = 0; ; i++) {
const res = await fetch(url, {
headers: { "X-API-Key": process.env.LOCATE_ME_KEY },
});
const body = await res.json();
if (res.ok) return body;
const retry = res.status === 503 || body.error === "rate_limited";
if (!retry || i + 1 >= attempts) throw new Error(body.error);
const wait = Number(res.headers.get("retry-after") ?? 1) * 1000;
await new Promise((r) => setTimeout(r, wait + Math.random() * 250));
}
}