Renidlyrenidly
Core Concepts

Errors & Retries

2 min read

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.errors is 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 categoryRetry?
Network / timeout / connection resetYes — exponential backoff, max 5 attempts.
Rate-limit pressureYes — 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 creditsNo — 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;
}