Skip to main content
Every error returns the same JSON envelope with an HTTP status that matches the error type:
{
  "error": {
    "type": "auth",
    "code": "invalid_key",
    "message": "invalid API key",
    "retryable": false,
    "billable": false
  }
}
  • type — the category (see the table below).
  • code — a stable, specific code you can branch on.
  • message — a human-readable description.
  • retryable — whether retrying the same request might succeed.
  • billable — whether the call consumed credits. Provider failures and timeouts are false (billed at zero).

Error types

TypeHTTPMeaning
validation400The request body is missing or malformed.
auth401Missing, invalid, or unauthorized API key.
insufficient_credits402Your balance can’t cover the call’s reserved maximum. Top up.
rate_limited429Too many requests. Back off and retry.
provider502The upstream provider failed. Billed at zero.
timeout504The provider didn’t respond in time. Billed at zero.
normalization502A curated tool couldn’t map the provider response. Billed at zero.

Handling errors

Branch on type (and code when you need specifics), and use retryable to decide whether to retry:
const res = await fetch("https://api.fetchbean.com/v1/search", { /* ... */ });
if (!res.ok) {
  const { error } = await res.json();
  if (error.retryable) {
    // back off and retry
  } else if (error.type === "insufficient_credits") {
    // prompt a top-up
  } else {
    // surface error.message
  }
}
You are never charged for provider, timeout, or normalization errors. Spend can never exceed your balance.