How do I handle rate limit errors (429)?

Handle Unison 429 rate limit responses with exponential backoff and jitter to avoid thundering-herd retries.

I'm getting 429 errors under load. How should I back off?

A 429 means you've exceeded your request rate for the current window. The response includes a Retry-After header (in seconds) when available — respect it. When it's absent, use exponential backoff with jitter.

Minimal retry pattern (TypeScript)

async function callWithRetry(fn: () => Promise<Response>, maxAttempts = 5): Promise<Response> {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    const res = await fn();
    if (res.status !== 429) return res;

    const retryAfter = res.headers.get('Retry-After');
    const delay = retryAfter
      ? parseInt(retryAfter, 10) * 1000
      : Math.min(1000 * 2 ** attempt + Math.random() * 500, 30_000);

    await new Promise(r => setTimeout(r, delay));
  }
  throw new Error('Rate limit retries exhausted');
}

Python equivalent

import time, random, httpx

def call_with_retry(fn, max_attempts=5):
    for attempt in range(max_attempts):
        res = fn()
        if res.status_code != 429:
            return res
        retry_after = res.headers.get("Retry-After")
        delay = int(retry_after) if retry_after else min(2 ** attempt + random.random(), 30)
        time.sleep(delay)
    raise RuntimeError("Rate limit retries exhausted")

Reducing 429s upstream

  • Batch ingestion: spread large backlogs over time rather than ingesting everything at once.
  • Cache recall results: for a given query+actor combination, recall results are stable for seconds to minutes. Cache aggressively at the application layer.
  • Queue writes: funnel ingest calls through a queue with a configurable rate rather than hitting the API directly from every request.

On this page