developer portal
Get API key →

Errors & retries

HTTP status codes, retry pattern, and when to retry vs. when not to.

Status codes

CodeMeaningWhat to do
200 OK The response body holds the requested resource.
400 Bad Request The request is invalid (missing parameter, wrong body shape). Fix the call and resend. Don't retry.
401 Unauthorized Credentials are wrong or missing. Check that user|company:password is shaped correctly and base64-encoded. Don't retry until credentials are fixed.
403 Forbidden Credentials work, but the user lacks the required privilege. Ask an IntelliTracer admin to grant the right Api: ... role. Don't retry.
404 Not Found The resource doesn't exist, or not within your tenant. Check the Id and the path. Don't retry.
429 Too Many Requests You hit the rate limit. Wait the number of seconds in the Retry-After header and try again. See Rate limits.
500504 Server-side error Transient. Retry with exponential backoff (see below). If the failure persists past 5 attempts, escalate to support.

Retry pattern

For transient failures use exponential backoff. Start at a 1-second delay, double after each failure, cap at 30 seconds. At most 5 retries per request. Add jitter (a random component between 0 and the delay) so retries don't land in lockstep.

AttemptWait
1immediate
21s (+ jitter)
32s (+ jitter)
44s (+ jitter)
58s (+ jitter)
616s (+ jitter)
after 6give up, escalate

When to retry, when not to

Example: retry wrapper

node retry.ts
async function withRetry<T>(
  fn: () => Promise<Response>,
  opts: { maxRetries?: number; baseDelayMs?: number; capMs?: number } = {},
): Promise<Response> {
  const max = opts.maxRetries ?? 5;
  const base = opts.baseDelayMs ?? 1000;
  const cap = opts.capMs ?? 30000;

  for (let attempt = 0; attempt <= max; attempt++) {
    const res = await fn();

    if (res.ok) return res;
    if (res.status === 429) {
      const retryAfter = parseInt(res.headers.get("retry-after") ?? "1", 10);
      await sleep(retryAfter * 1000);
      continue;
    }
    if (res.status >= 500) {
      if (attempt === max) return res;
      const delay = Math.min(base * 2 ** attempt, cap);
      const jitter = Math.random() * delay;
      await sleep(delay + jitter);
      continue;
    }
    return res; // 4xx other than 429: don't retry
  }

  throw new Error("retry budget exhausted");
}

const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));
python retry.py
import random, time, requests

def with_retry(fn, max_retries=5, base_delay=1.0, cap=30.0):
    for attempt in range(max_retries + 1):
        res = fn()

        if res.ok:
            return res
        if res.status_code == 429:
            retry_after = int(res.headers.get("Retry-After", "1"))
            time.sleep(retry_after)
            continue
        if 500 <= res.status_code < 600:
            if attempt == max_retries:
                return res
            delay = min(base_delay * (2 ** attempt), cap)
            time.sleep(delay + random.random() * delay)
            continue
        return res  # 4xx other than 429: don't retry

    raise RuntimeError("retry budget exhausted")

Idempotency

All GET calls are idempotent: running them again gives the same result and has no side effects. Retrying after a 5xx or 429 is therefore safe.

Writes (POST, PUT, DELETE) are not generally available in V1. When writes ship, every write endpoint gets its own idempotency strategy documented here. Until then: treat write timeouts as ambiguous and check status via a follow-up GET.

More