Retries and timeouts
odnoga handles vendor-side retries and fallback. You still need to handle network / 429 / 5xx on your side — but only those.
Rules
- Never retry
4xxother than408and429. They are deterministic; retrying just wastes money. - Always retry network errors,
408,429,5xx. Exponential backoff, jitter, cap at 3 attempts. - Honor
x-ratelimit-resetif present — wait until then before retrying. - Set a hard request timeout (e.g. 60s). Don't rely on the platform.
Helper
import { callodnoga, AirouterError } from './airouter.ts';
const RETRYABLE = new Set([408, 429, 500, 502, 503, 504]);
export async function callWithRetry<T>(
path: string, body: unknown, opts: { endUser: string; timeoutMs?: number } = { endUser: 'anonymous' },
) {
const timeoutMs = opts.timeoutMs ?? 60_000;
let lastErr: unknown;
for (let attempt = 0; attempt < 3; attempt++) {
const ctrl = new AbortController();
const t = setTimeout(() => ctrl.abort(), timeoutMs);
try {
return await callodnoga<T>(path, body, { endUser: opts.endUser, signal: ctrl.signal });
} catch (e) {
lastErr = e;
const status = e instanceof AirouterError ? e.status : 0;
const retryable = status === 0 || RETRYABLE.has(status);
if (!retryable) throw e;
const backoff = Math.min(8000, 250 * 2 ** attempt) + Math.random() * 250;
await new Promise((r) => setTimeout(r, backoff));
} finally {
clearTimeout(t);
}
}
throw lastErr;
}
Idempotency
Streaming or write-style requests should send x-airouter-idempotency-key (any unique string per logical call). Retries with the same key are deduped server-side, so two attempts of the same request bill once.