Errors & Retries
The body always carries the truth. Branch on body.success, read body.message, and retry only transient failures — rate limits and network blips.
The contract
Every failure keeps the same envelope shape as a success — only the values change. Your client should:
- Branch on
body.success— it is always present and correct. - Read the reason from
body.message(andbody.error_codewhen present). - When
body.errorsis a map, surface the field-level reasons too.
The two failure shapes
General failure — errors is null
Missing parameters, auth issues, business rules (e.g. a record that doesn't exist), billing, and rate-limit pressure. The reason is in message, sometimes with a stable error_code.
{
"success": false,
"statusCode": 404,
"message": "Profile not found",
"error_code": "1010",
"errors": null,
"data": null
}Validation failure — errors is a field map
One or more inputs failed validation; errors names each offending field.
{
"success": false,
"statusCode": 400,
"message": "validation error; Please recheck your inputs",
"errors": {
"handle": "handle is required"
},
"data": null
}What the failures mean
The common categories you'll handle, what triggers them, and whether to retry:
| Category | Looks like | Retry? |
|---|---|---|
| Validation | HTTP 400, errors field map. | No — fix the request. |
| Missing API key | HTTP 401, “Missing API Key…”. | No — add the header. |
| Invalid API key | HTTP 403, “Invalid X-renidly-apikey”. | No — fix or rotate the key. |
| Not found | HTTP 404 / error_code 1010 (person), 1020 (organization), 1040 (institution). | No — try a different identifier. |
| Insufficient credits | HTTP 403, “Insufficient credits…”. | No — top up. |
| Premium endpoint | HTTP 403, “…premium endpoint…”. | No — upgrade the plan. |
| Rate limited | HTTP 429 / error_code 1074. | Yes — back off, then retry. |
| Temporarily unavailable | error_code 1072. | Yes — back off, then retry. |
| Network / timeout | No response, connection reset. | Yes — exponential backoff. |
What to retry
Most failures need a change to your request or account — retrying an invalid handle won't make it valid. Only rate-limit pressure, temporary unavailability, and network errors are transient. Cap retries at five attempts with exponential backoff and a little jitter.
// Exponential backoff with jitter. Retry only transient failures —
// never validation, auth, or "not found", which need a request change.
async function withRetry<T>(fn: () => Promise<T>, max = 5): Promise<T> {
let attempt = 0;
while (true) {
try {
return await fn();
} catch (err) {
if (!isRetriable(err) || attempt >= max) throw err;
const delay = Math.min(1000 * 2 ** attempt, 15_000);
const jitter = Math.floor(Math.random() * 250);
await new Promise((r) => setTimeout(r, delay + jitter));
attempt += 1;
}
}
}
function isRetriable(err: unknown): boolean {
if (err instanceof TypeError) return true; // network / timeout
if (err instanceof RenidlyError) {
// Rate-limit pressure is transient; everything else needs a fix.
return /rate limit|too many|try again/i.test(err.message);
}
return false;
}