Renidlyrenidly
Core Concepts

Errors & Retries

3 min read

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 (and body.error_code when present).
  • When body.errors is 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:

CategoryLooks likeRetry?
ValidationHTTP 400, errors field map.No — fix the request.
Missing API keyHTTP 401, “Missing API Key…”.No — add the header.
Invalid API keyHTTP 403, “Invalid X-renidly-apikey”.No — fix or rotate the key.
Not foundHTTP 404 / error_code 1010 (person), 1020 (organization), 1040 (institution).No — try a different identifier.
Insufficient creditsHTTP 403, “Insufficient credits…”.No — top up.
Premium endpointHTTP 403, “…premium endpoint…”.No — upgrade the plan.
Rate limitedHTTP 429 / error_code 1074.Yes — back off, then retry.
Temporarily unavailableerror_code 1072.Yes — back off, then retry.
Network / timeoutNo 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;
}