# Errors and retries

Source: https://docs.orbytelabs.com/errors

Handle rejected charges and retry requests without duplicate debits or deposits.

API errors return a code and message:

```json
{
  "error": "insufficient_balance",
  "message": "This wallet does not have enough credits."
}
```

The SDK exposes rejected API requests as `OrbyteApiError`, with `status`, `code`, and `message`.

## Handle a rejected charge [#handle-a-rejected-charge]

```ts
import { OrbyteApiError, track } from "@orbytelabs/credit";

try {
  await track("api-call", "customer_123", { id: "request_123:api-call" });
} catch (error) {
  if (
    error instanceof OrbyteApiError &&
    error.code === "insufficient_balance"
  ) {
    // Ask the customer to add credits, or stop further work.
  } else {
    throw error;
  }
}
```

An insufficient-balance error creates no usage transaction or debit. After funding the wallet, retry the same event ID and payload. An allowed `check` does not reserve funds, so tracking can still fail if another request spends the balance first.

An unaffordable `check` itself returns `allowed: false` with HTTP `200`. Invalid input, credentials, or pricing configuration can still cause a check to throw.

For [included plan usage](/subscriptions), check can return `reason: "feature_limit_exceeded"` or `reason: "subscription_inactive"`. Tracking the same usage returns a `409` error with that code. A blocked allowance requires a plan change or a new allowance period; an inactive subscription requires restoring its active state. Adding prepaid credits alone does not resolve either denial.

## Error codes [#error-codes]

| Status | Codes                                                                                 | What to do                                                                                                        |
| ------ | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `400`  | `invalid_request`, `invalid_event`, `invalid_json`, `invalid_quantity`                | Fix the request shape, quantity, or pricing inputs. Unknown properties are rejected.                              |
| `401`  | `unauthenticated`, `invalid_api_key`                                                  | Check the secret key and whether it has expired or been revoked.                                                  |
| `403`  | `forbidden`, `forbidden_api_key`                                                      | Use a secret key with the required permission.                                                                    |
| `404`  | `not_found`, `feature_not_found`, `wallet_not_found`                                  | Check the resource key or identity and the application selected by your API key.                                  |
| `409`  | `feature_limit_exceeded`                                                              | Wait for the quota period to reset, change plans, or use a plan with credit overage.                              |
| `409`  | `subscription_inactive`                                                               | Restore an active subscription before using its included features.                                                |
| `409`  | `insufficient_balance`                                                                | Fund the wallet before retrying usage.                                                                            |
| `409`  | `idempotency_conflict`                                                                | Reuse the original payload for a retry. Use a new ID only for a new operation.                                    |
| `409`  | `conflict`                                                                            | Inspect the message for an existing catalog key, locked period policy, balance overflow, or wallet balance limit. |
| `409`  | `stripe_not_connected`, `stripe_not_ready`                                            | Connect Stripe, complete onboarding, or finish plan synchronization before retrying checkout.                     |
| `413`  | `payload_too_large`                                                                   | Keep the JSON request body within 16 KiB.                                                                         |
| `415`  | `unsupported_media_type`                                                              | Send `Content-Type: application/json` on write requests.                                                          |
| `422`  | `credit_not_found`, `invalid_billing_period`, `unsupported_pricing`, `invalid_charge` | Fix the feature's credit, billing period, or charge calculation.                                                  |
| `429`  | `rate_limited`                                                                        | Wait for the `Retry-After` delay before retrying.                                                                 |
| `500`  | `internal_error`                                                                      | Retry eligible operations with the same IDs and payloads.                                                         |
| `502`  | `stripe_error`                                                                        | Inspect the payment state before retrying with the same operation key.                                            |

Missing resources are scoped to the API key's application. For example, a feature deployed in Development is not available to a Production key until you deploy it there.

## Retry the same operation [#retry-the-same-operation]

If a response is lost, the server may already have accepted the request. Keep the identifiers for that operation:

* Usage uses `id`, unique within the application. An identical retry returns the original transaction with `status: "duplicate"`.
* Funding uses `idempotencyKey`, also unique within the application. An identical retry returns the original deposit with `duplicate: true`.
* Checkout and subscription changes use stable `idempotencyKey` values for each purchase or change. Reuse the same inputs on retries.

The namespaces are separate. The same string can identify one event and one deposit, but two different usage events must not share an ID.

For callback prices, a retry must resolve to the same nanocredit amount. Automatic SDK retries preserve the serialized request. If your own job calls the SDK again, preserve the original pricing inputs and calculation too.

## Automatic SDK retries [#automatic-sdk-retries]

For `check`, `track`, `topup`, reads, and supported updates, the SDK makes up to three attempts for transport failures, interrupted responses, HTTP `429`, and server errors. Each attempt has a 20-second timeout. Retries use exponential backoff and honor a positive `Retry-After` delay up to 30 seconds.

After retries are exhausted, network failures raise `OrbyteTransportError`; valid API error responses raise `OrbyteApiError`. Local input validation and unexpected response shapes can raise other errors, so retain an unknown-error path in your handler.

Catalog creation does not automatically retry uncertain network or server failures. Read the catalog before trying another creation. The SDK can retry a catalog request explicitly rejected with `rate_limited` because that request was not admitted.

## Rate and size limits [#rate-and-size-limits]

API requests are limited per key to 600 per minute, with a burst capacity of 120. Failed authenticated requests also use request capacity. If your own retry loop runs beyond the SDK's attempt limit, keep it bounded and respect `Retry-After`.

JSON bodies are limited to 16 KiB. Unknown JSON fields and repeated query parameters are rejected. API amounts must be nonnegative integer strings within the supported 64-bit range; deposits must be positive. See [pricing](/pricing#precision-and-limits) for precision rules.

Use the [API reference](/api) for each operation's required permissions and schemas.