Error handling

The basics are on the Errors reference page. This guide is the production-shaped version: which errors are terminal, which to retry, and what to log.

Decision tree

status code ──► retry?
─────────────────────
4xx (except 429)  no  — fix and re-call
429               yes — backoff (or honour Retry-After)
5xx               yes — backoff
network error     yes — backoff (max 3 attempts)

The SDKs already implement this — you only need to write retry logic when you’ve explicitly disabled it (retries=0).

Distinguishing transient vs terminal in TypeScript

import {
  BadRequestError,
  AuthenticationError,
  InsufficientCreditsError,
  NotFoundError,
  RateLimitError,
  ServiceUnavailableError,
  NetworkError,
} from "brightbean";

try {
  await call();
} catch (err) {
  if (err instanceof BadRequestError || err instanceof NotFoundError) {
    // terminal: log + drop
    logger.error({ requestId: err.requestId, code: err.code }, "bad request");
  } else if (err instanceof AuthenticationError) {
    // terminal: rotate key, page on-call
    pager.alert("brightbean auth failure", { requestId: err.requestId });
  } else if (err instanceof InsufficientCreditsError) {
    // terminal for THIS call; halt batch and top up
    queue.pauseAll("out of credits");
  } else if (err instanceof RateLimitError) {
    // already retried by SDK — escalate if you see this
    metrics.increment("brightbean.rate_limited.unrecovered");
  } else if (err instanceof ServiceUnavailableError || err instanceof NetworkError) {
    // already retried — likely an outage
    metrics.increment("brightbean.upstream_down");
  } else {
    throw err; // unexpected
  }
}

What to log

Always log:

Never log:

Idempotency for retries you control

If you’re doing your own retries (for whatever reason — disabled SDK retry, or fanning out across processes), pass Idempotency-Key on POSTs. A repeated key within 24h returns the original response and does not consume credits.

import hashlib
key = hashlib.sha256(f"{user_id}:{title}".encode()).hexdigest()
client.score(title=title, idempotency_key=key)

Surfacing errors to end users

In an app:

BrightBean error What the user sees
BadRequestError (e.g. title too long) Inline validation: “Title must be under 250 characters.”
InsufficientCreditsError Modal: “You’re out of credits — upgrade or top up.” (Link to billing.)
RateLimitError (after SDK retries) Toast: “We’re processing your previous request — try again in a moment.”
AuthenticationError Server-side problem, not the user’s. Generic “Something went wrong.” + alert.
ServiceUnavailableError, NetworkError “BrightBean is temporarily unavailable. Retry?”

Debugging a single failed call

Every error carries request_id. Forward it to support@brightbean.xyz along with the timestamp and we can look up the server-side trace.