RouteMux Docs

Handling errors

Decide what to do next from retry.strategy, retry.retryable and billing.status instead of hard-coding a branch per error code.

Every RouteMux error carries the answer to "what should my code do now?" in three structured fields. Read those instead of writing one branch per error code — new codes appear over time, and code written against these fields keeps working.

The short version

async function callWithRouteMuxHandling(send) {
  for (let attempt = 0; ; attempt++) {
    const res = await send();
    if (res.ok) return res;

    const { error } = await res.json();

    switch (error.retry.strategy) {
      case 'BACKOFF': {
        if (attempt >= 5) throw new Error(error.code);
        // Honour the server's own delay when it sends one.
        const base = error.retry.retry_after_seconds ?? Math.min(2 ** attempt, 30);
        await sleep((base + Math.random()) * 1000); // add jitter
        continue;
      }
      case 'RETRY':
        if (error.retry.retryable && attempt < 3) { await sleep(500); continue; }
        throw new Error(error.code);

      case 'TOP_UP':        return promptUserToAddCredit(error);
      case 'SWITCH_MODEL':  return retryWithAnotherModel(error);
      case 'AUTHENTICATE':  return fixApiKey(error);
      case 'FIX_REQUEST':   throw new Error(error.message); // retrying changes nothing
      case 'CONTACT_SUPPORT':
        throw new Error(`${error.reference} · ${error.request_id}`);
      case 'NONE':
        return null; // nothing failed that you need to act on
    }
  }
}

The two retry fields answer different questions

FieldQuestion it answers
retry.retryableIf I send the exact same request again, could it succeed?
retry.strategyTo actually succeed, what should I change?

They are independent, and the interesting cases are where they disagree:

ErrorretryablestrategyWhy both are correct
MODEL_UNAVAILABLEtrueSWITCH_MODELThe model may come back, so retrying is not pointless — but switching models is faster.
JOB_ARTIFACT_NOT_FOUNDfalseRETRYThat artifact is gone for good; re-running a new job is what makes sense.
PLATFORM_INTERNAL_ERRORtrueCONTACT_SUPPORTWorth one retry, but if it repeats it is our bug, not yours.

What each strategy means

retry.strategyMeaningDo thisExample codes
FIX_REQUESTThe request itself is not acceptableCorrect the request. Retrying is pointlessREQUEST_INVALID, MODEL_VISION_UNSUPPORTED
AUTHENTICATECredential problemFix or replace the API key; do not retryAUTH_INVALID_API_KEY, AUTH_API_KEY_EXPIRED
BACKOFFTemporary congestionWait, then retry. Honour Retry-AfterMODEL_BUSY, LIMIT_RATE_EXCEEDED
RETRYTransient failureRetry; no mandatory delayREQUEST_TIMEOUT, JOB_FAILED
SWITCH_MODELThis model cannot serve the requestUse another modelMODEL_NOT_FOUND, MODEL_ACCESS_DENIED
TOP_UPNot enough creditAdd credit, then resendBILLING_INSUFFICIENT_CREDITS
CONTACT_SUPPORTYou cannot fix this from your sideOpen a ticket with reference and request_idAUTH_ACCOUNT_SUSPENDED, PLATFORM_PRICING_INCOMPLETE
NONENothing to act onContinueJOB_NOT_CANCELLABLE

Backing off correctly

When an error defines a delay, RouteMux sends it in both places — use whichever your client reads more easily:

  • the Retry-After response header, in seconds
  • error.retry.retry_after_seconds in the body

MODEL_BUSY sends 5, for example. When no delay is given, use exponential backoff and add jitter — without it, every client that failed at the same moment retries at the same moment and rebuilds the spike you are backing off from.

Were you charged?

error.billing.status tells you whether the failed request cost anything.

ValueMeaningSafe to retry?
NOT_BILLEDNothing was chargedYes, no double-charge risk
BILLEDThe request consumed credit before failingRetrying costs again — decide deliberately

Today every error in the catalog is NOT_BILLED: a request rejected with one of these codes never costs credit, so retrying per strategy cannot double-charge you. BILLED appears only on request-log entries where usage was already settled before the failure.

Treat the request log as authoritative for money questions. A stream that fails after tokens were produced is the one case where the response body and the final billed amount can differ.

Two things not to do

Do not branch on message. It is localized (via Accept-Language) and reworded as the product changes. Branch on code; show message to humans.

Do not retry FIX_REQUEST errors. Nothing about the request changed, so the outcome cannot change. Roughly half of the catalog is FIX_REQUEST — a blind "retry 3 times on any error" wrapper spends its budget on requests that were never going to succeed.

Finding a specific error

Every code and RMX-… reference is listed in the error reference, which is searchable and links each entry to a permanent anchor you can share with support.

On this page