Errors
How errors are shaped, every code the API sends, and what to do about each.
On this page
The error shape
Errors the API decides on are the same JSON object. error.code is for your program and never changes for a given failure; error.message is for people and may be reworded. When a request field is wrong, error.fields names it.
HTTP/1.1 429 Too Many Requests
Cache-Control: no-store
Retry-After: 4
X-Request-Id: 8c1d2b7e-55f3-4a0b-9e2d-6f4a1c3b8d90
{
"ok": false,
"error": {
"code": "rate_limited",
"message": "Too many requests. Try again later."
},
"meta": { "api_version": "v1" }
}Two answers come from the web server instead and have no JSON body: a 404 for a path that does not exist, and a 405 for a method a path does not take. Check the status before you parse.
JSON errors are never cached, and a 429 carries Retry-After, in seconds. Each also carries an X-Request-Id, unique to that response. NexG logs the id of every server error (5xx), so quote it when you report one.
Error codes
The set is small and closed, so you can handle every code.
| Code | Means | What to do |
|---|---|---|
invalid_request | The request was not in a shape NexG understands: conflicting credential headers, an unknown or invalid query parameter, or an id that is not a UUID. | Fix the request. Sending it again unchanged gets the same answer. |
not_found | Something does not exist, or is not yours. A refused key (401) and a missing scope (403) also use this code, so nobody can tell which keys exist. | Read the status: 401, replace the key; 403, check its scopes; 404, the record is not there. |
rate_limited | Your token bucket is empty. | Wait the seconds in Retry-After, then retry. |
internal_error | NexG could not finish the request. | Retry with backoff. On 503, NexG could not check your key or limit right now. |
method_not_allowed | Reserved. The API does not send it today. | An unsupported method gets an empty 405 instead; see below. |
HTTP statuses
| Status | When | Retry? |
|---|---|---|
| 200 | It worked. Check ok anyway. | — |
| 400 | invalid_request. | No. Fix the request. |
| 401 | The key was refused. | No. Replace the key. |
| 403 | The key lacks the scope, or no key was sent where one is needed. | No |
| 404 | not_found: no such record. A path that does not exist is also 404, with a web page instead of JSON. | No |
| 405 | The path exists but not for that method. The body is empty. | No |
| 429 | rate_limited. | Yes, after Retry-After |
| 500 | internal_error. NexG logs the request id. | Yes, with backoff |
| 503 | internal_error: NexG could not check the key or the limit. | Yes, with backoff |
Handling errors
Retry what can succeed on a second try, and stop on what cannot.
async function nexg(url, attempt = 0) {
const res = await fetch(url, { headers: { 'X-Api-Key': process.env.NEXG_API_KEY } });
const body = await res.json().catch(() => null);
if (body?.ok) return body.data;
const code = body?.error?.code;
const requestId = res.headers.get('X-Request-Id');
if (res.status === 429 && attempt < 3) {
const wait = Number(res.headers.get('Retry-After') ?? 1);
await new Promise((resolve) => setTimeout(resolve, wait * 1000));
return nexg(url, attempt + 1);
}
if (res.status >= 500 && attempt < 3) {
await new Promise((resolve) => setTimeout(resolve, 2 ** attempt * 1000));
return nexg(url, attempt + 1);
}
// 400, 401, 403: retrying the same request will not help.
throw new Error(`NexG ${res.status} ${code ?? 'no_body'} (request ${requestId})`);
}