Errors & Retries
Renidly never raises HTTP-level errors for application failures — the body always carries the truth. Branch on body.success, read body.message, and retry only on transient infrastructure issues.
The contract
Every failure response keeps the same envelope shape as a successful one — only the values change. Your client code should:
- Branch on
body.success— never on HTTP status. - Read the reason from
body.message. - When
body.errorsis a map, surface the field-level reasons too.
The two failure shapes
Renidly produces exactly two failure shapes, distinguished by whether body.errors is null or a per-field map. Pattern-match on that and you cover every error case.
General failure — errors is null
Used for missing required parameters, auth issues, business rule violations, billing problems, and rate-limit pressure. The reason lives in message.
{
"success": false,
"statusCode": 200,
"message": "either 'entityId' or 'handle' parameter is required",
"errors": null,
"data": null
}Validation failure — errors is a field map
Used when one or more input fields fail schema validation. message is a fixed line; errors names every offending field.
{
"success": false,
"statusCode": 200,
"message": "validation error; Please recheck your inputs",
"errors": {
"keyword": "keyword is required"
},
"data": null
}Credits and errors
Failed requests never deduct credits. Successful requests deduct the endpoint's posted cost — see Endpoint Costs for the full catalog.
What to retry
Most error categories require a change to your request or configuration — retrying an invalid handle isn't going to make the handle valid. Two categories of failure are transient and safe to retry with exponential backoff:
| Failure category | Retry? |
|---|---|
| Network / timeout / connection reset | Yes — exponential backoff, max 5 attempts. |
| Rate-limit pressure | Yes — back off, then re-read your tier in case it dropped. |
| Validation (missing field, wrong type) | No — fix the request. |
| Auth (key missing/invalid) | No — fix the header or rotate the key. |
| Business rule (e.g. identifier not found) | No — try a different identifier. |
| Insufficient credits | No — top up. |
// Exponential backoff with jitter for transient failures.
// Only retry on network errors and explicit rate-limit signals — never on
// validation or auth failures, since those require a code 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 {
// Network / connection / timeout — always safe to retry.
if (err instanceof TypeError) return true;
// A RenidlyError surfaced from your fetch wrapper: retry only when the
// message indicates rate-limit pressure. Validation / auth must not retry.
if (err instanceof RenidlyError) {
return /rate limit|too many|try again/i.test(err.message);
}
return false;
}