extractr docs

Errors

One error shape, predictable codes.

Every non-2xx response has the same body:

{
  "error": {
    "code": "insufficient_credits",
    "message": "Balance is 3 credits but this request costs 5. Top up at extractr.dev/dashboard/billing."
  }
}
  • code — a stable, machine-readable string. Branch on this.
  • message — human-readable detail. May change; don't parse it.

Failed requests are never charged.

Error codes

StatusCodeWhen
401unauthorizedNo API key (or session token) supplied.
401invalid_api_keyKey malformed, unknown or revoked.
402insufficient_creditsBalance below the endpoint's cost. Top up or enable auto top-up.
404not_foundUnknown route, or the requested resource doesn't exist.
422validation_errorA query/path/body parameter is missing or malformed.
429rate_limitedToo many requests — back off and retry.
400stripe_not_configuredBilling action attempted while Stripe isn't configured (self-hosted/dev).
500internal_errorSomething failed on our side. Safe to retry.

Examples

401 invalid_api_key
{
  "error": {
    "code": "invalid_api_key",
    "message": "API key is revoked or does not exist."
  }
}
422 validation_error
{
  "error": {
    "code": "validation_error",
    "message": "Query parameter \"symbol\" is required."
  }
}
429 rate_limited
{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit exceeded. Retry after a short backoff."
  }
}

Handling errors

Treat 429 and 500 as retryable (with exponential backoff); treat 401, 402 and 422 as bugs or billing state to surface immediately:

const res = await fetch(url, { headers: { "X-API-Key": key } });
if (!res.ok) {
  const { error } = await res.json();
  switch (error.code) {
    case "insufficient_credits":
      // alert billing / trigger a top-up
      break;
    case "rate_limited":
    case "internal_error":
      // retry with backoff
      break;
    default:
      throw new Error(`${error.code}: ${error.message}`);
  }
}

On this page