Clevermation
Documentation

Retry & timeout

Exponential backoff defaults, per-call overrides, `CRTimeoutError`, and how it interacts with streaming.

The SDK retries transient failures and enforces a client-side timeout so a slow upstream doesn't pin your request handler forever.

Defaults

SettingDefault
Retries3 attempts (2 retries on top of the initial call)
Status codes429, 502, 503, 504
BackoffbaseDelayMs * 2^attempt + jitter
Backoff cap10_000 ms
Timeout60 s
Retry-AfterHonoured when present

400/401/402/404/410 are not retried — they require code or config changes, not waiting.

Configure once

cr.ts
import { createCleverRouter } from '@cleverrouter/sdk';

const cr = createCleverRouter({
  apiKey: process.env.CLEVERROUTER_API_KEY!,
  retry: {
    maxAttempts: 5,
    baseDelayMs: 250,
    maxDelayMs: 8_000,
    statusCodes: [429, 502, 503, 504],
  },
  timeoutMs: 30_000,
});

Disable retries

const cr = createCleverRouter({
  apiKey: process.env.CLEVERROUTER_API_KEY!,
  retry: false,
});

Per-call override

await cr.chat(
  { model: 'mistral/mistral-small-3.2', messages },
  { timeoutMs: 5_000 },
);

CRTimeoutError

import { CRTimeoutError } from '@cleverrouter/sdk';

try {
  await cr.chat(
    { model: 'mistral/mistral-small-3.2', messages },
    { timeoutMs: 1_000 },
  );
} catch (err) {
  if (err instanceof CRTimeoutError) {
    console.warn(`Timed out after ${err.timeoutMs}ms`);
  } else {
    throw err;
  }
}

CRTimeoutError is distinct from CRProviderDownError (upstream 502/503/504) and AbortError (you called controller.abort()).

AbortSignal

Every helper accepts { signal }:

const ctrl = new AbortController();
const promise = cr.chat(
  { model: 'mistral/mistral-small-3.2', messages },
  { signal: ctrl.signal },
);

setTimeout(() => ctrl.abort(), 2_000);

try {
  await promise;
} catch (err) {
  if ((err as Error).name === 'AbortError') {
    // user-initiated cancel
  }
}

Retry meets stream

Streams don't retry

Once the first chunk has shipped, the SDK can't replay a stream on a different attempt. Instead, cr.stream() reconnects up to twice with Last-Event-ID if the connection drops before [DONE]. See Streaming.

A reasonable default

  • timeoutMs: 30_000 for chat completions on standard models.
  • timeoutMs: 120_000 for reasoning_effort: 'high' calls — they routinely take 20–90s.
  • maxAttempts: 3 is the sweet spot. More retries don't help when the upstream is genuinely down; they just delay surfacing the failure to the user.
  • Don't retry 4xx (the SDK already doesn't). A retried 401 is still going to fail.