ReferenceErrors

Errors

HTTP status → SDK class. Every `4xx` and `5xx` the gateway returns, with retry policy.

The gateway returns errors in the OpenAI-compatible shape. The SDK maps each status to a typed CRError subclass so your handlers stay readable.

HTTP → SDK class

HTTPerror.codeSDK classWhen
400validation_errorCRValidationErrorBad request body, malformed model id
401unauthorizedCRAuthErrorMissing, invalid or revoked API key
402budget_exceededCRBudgetExceededErrorPer-key budget reached
404model_not_foundCRModelNotFoundErrorModel id not in the registry
408timeoutCRTimeoutErrorClient-side timeout exceeded (SDK only)
410model_deprecatedCRModelDeprecatedErrorModel retired by the provider
429rate_limit_exceededCRRateLimitErrorSliding-window rate limit exceeded
502provider_downCRProviderDownErrorUpstream 5xx with no healthy alternative
503provider_downCRProviderDownErrorAll EU providers unavailable
504upstream_timeoutCRProviderDownErrorUpstream read timeout
500internal_errorCRErrorUnspecified — should never happen, file a bug

Body shape

{
  "error": {
    "message": "Rate limit exceeded for cr_live_abc… (60 RPM)",
    "type": "rate_limit_exceeded",
    "code": "rate_limit_exceeded"
  }
}

X-Request-Id is always set so you can include it in bug reports.

Typed handling

handle.ts
import {
  CRError,
  CRAuthError,
  CRRateLimitError,
  CRModelNotFoundError,
  CRModelDeprecatedError,
  CRBudgetExceededError,
  CRProviderDownError,
  CRValidationError,
  CRTimeoutError,
} from '@cleverrouter/sdk';

try {
  await cr.chat({ model: 'mistral/mistral-small-3.2', messages });
} catch (err) {
  if (err instanceof CRRateLimitError) {
    console.warn(`Retry in ${err.retryAfterSec ?? 60}s`);
  } else if (err instanceof CRAuthError) {
    // 401 — rotate the key
  } else if (err instanceof CRBudgetExceededError) {
    // 402 — top up
  } else if (err instanceof CRModelNotFoundError) {
    // 404 — typo or model not yet synced
  } else if (err instanceof CRModelDeprecatedError) {
    // 410 — pick a successor
  } else if (err instanceof CRTimeoutError) {
    console.warn(`Timed out after ${err.timeoutMs}ms`);
  } else if (err instanceof CRProviderDownError) {
    // 502/503/504 — try later
  } else if (err instanceof CRValidationError) {
    // 400 — fix the request body
  } else if (err instanceof CRError) {
    console.error(err.status, err.code, err.requestId);
  } else {
    throw err;
  }
}

With `cr.openai()`

Errors come from the OpenAI SDK's own subclasses (APIError, RateLimitError, AuthenticationError, …). The CR* classes apply to the gateway-direct helpers (cr.chat, cr.stream, cr.embed, cr.rerank, cr.transcribe, cr.listModels).

Retry policy

StatusRetried by SDKNotes
429yesHonours Retry-After
502 / 503 / 504yesExponential backoff with jitter
408 (CRTimeoutError)noAlready at the client-side timeout limit
401 / 402 / 404 / 410noNeeds code or config changes, not waiting
400noSame

Configure retry/timeout: Retry & timeout.

Rate-limit headers

Every response carries:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 12
X-RateLimit-Reset: 47
X-RateLimit-Enforced: true

X-RateLimit-Enforced: false means Redis was unreachable during the request — the gateway fails open rather than blocking traffic. Back off voluntarily when you see it. See Rate limits.