Errors
The Navigo API uses standard HTTP status codes. The table below is derived from the real behavior of the frontend interceptor.
HTTP status codes
| Status | Meaning | Reason / note |
|---|---|---|
400 | Bad Request | Validation error; the body has the shape { field: ["message"] } |
401 | Unauthorized | Key/token is invalid or expired ("Session expired") |
403 | Forbidden | Insufficient permission (not an admin / a resource you don't own) |
404 | Not Found | Resource not found |
429 | Too Many Requests | Rate limit exceeded (60/min, 2000/day) |
5xx | Server Error | An error on Navigo's side — retry with backoff |
Error body format (validation, 400)
For validation errors, messages are returned per field as an array:
{
"external_id": ["lead with this external_id already exists."],
"email": ["Enter a valid email address."]
}
The frontend typically shows the first message of the first field to the user.
Handling errors correctly
const res = await fetch(url, options);
if (!res.ok) {
if (res.status === 429) {
// Rate limit — respect Retry-After, retry with backoff
const retryAfter = Number(res.headers.get("Retry-After") ?? 1);
await new Promise((r) => setTimeout(r, retryAfter * 1000));
// ... retry
} else if (res.status >= 500) {
// Server error — retry with exponential backoff
} else {
const errors = await res.json(); // { field: ["message"] }
throw new Error(Object.values(errors)[0]?.[0] ?? `HTTP ${res.status}`);
}
}
:::tip Retry policy
For 429 and 5xx errors, retry with exponential backoff (1s, 2s, 4s, 8s…).
For 4xx errors (other than 429), do not retry — fix the request instead.
:::