# Check and track usage

Source: https://docs.orbytelabs.com/checking-and-tracking

Check affordability before work and charge completed usage safely.

Use `check` to ask whether a customer can afford an operation. Use `track` to charge it and record usage. Both run on your server and require an API key with `events:ingest` permission.

The examples use the deployed catalog from the [quickstart](/quickstart), where `api-call` costs one credit per request. Include the generated `_orbyte.ts` in your TypeScript project to type feature names. You do not need to import the config for fixed prices. Set `ORBYTE_API_KEY` in your server environment. The SDK uses `https://api.orbytelabs.com` automatically.

## Check before work [#check-before-work]

```ts
import { check } from "@orbytelabs/credit";

const access = await check("api-call", "customer_123");

if (!access.allowed) {
  throw new Error("Add credits before making another request.");
}
```

For a wallet with 100 credits, the result is:

```json
{
  "allowed": true,
  "creditKey": "credits",
  "amountNanocredits": "1000000000",
  "balanceNanocredits": "100000000000"
}
```

The amount is the charge for the supplied feature and quantity, after any included plan allowance. `balanceNanocredits` is the current balance for that credit type. An unaffordable check returns `allowed: false` in a successful response; it does not throw an insufficient-balance error.

When a [plan](/subscriptions) covers the feature, the response can include `limit` with the allowance and current usage. A denial can include `reason: "feature_limit_exceeded"` or `reason: "subscription_inactive"`. Check `allowed` for every response; do not rely on a reason being present.

Checks do not create wallets, deduct credits, reserve funds, or record usage. A missing wallet has a zero balance. Checking a free feature can return `allowed: true` for a missing wallet without creating it.

## Track the charge [#track-the-charge]

After the customer uses the feature, record the event:

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

const result = await track("api-call", "customer_123", {
  id: "request_123:api-call",
});
```

```json
{
  "status": "accepted",
  "transactionId": "TRANSACTION_ID",
  "amountNanocredits": "1000000000"
}
```

Credit checks the balance and applies the debit in one transaction. Concurrent requests cannot spend the same credits twice or take the balance below zero.

An allowed check is a snapshot. Another request can spend the balance, or the price or usage period can change, before you track. `track` checks again and throws `OrbyteApiError` with code `insufficient_balance` when the wallet cannot cover the charge. Decide how your application handles that case, especially when provider work has already incurred a cost.

A rejected charge creates no usage transaction and does not lock a usage period. It can provision an empty wallet so you can fund it and retry.

Tracking also enforces plan allowances. A blocked limit or inactive subscription returns `409` with `feature_limit_exceeded` or `subscription_inactive`. Adding credits alone does not resolve those denials. Included usage and credit deductions commit together, so a failed charge does not consume the allowance.

## Quantities [#quantities]

For fixed-price features, send the same quantity to both calls:

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

const quantity = 5;
const access = await check("api-call", "customer_123", { quantity });
if (!access.allowed) throw new Error("Not enough credits.");

// Perform the five requests here.
await track("api-call", "customer_123", {
  id: "batch_123:api-calls",
  quantity,
});
```

Omitted quantity means 1. The SDK accepts a positive safe integer as a `number` or `bigint`. For callback pricing and period features, quantity must be 1.

Feature objects from your config remain supported. For callback prices, pass `config.features.inference` with its typed `data` so the SDK can run the pricing function on your server. Generated types reject callback names in string-based calls. See [prices from event data](/pricing#prices-from-event-data).

## Event IDs and retries [#event-ids-and-retries]

Choose one event ID for each billable occurrence, such as a job ID plus a feature key. Keep it stable when retrying that occurrence.

| Result         | Meaning                                                   | Balance effect                                          |
| -------------- | --------------------------------------------------------- | ------------------------------------------------------- |
| `accepted`     | New usage event                                           | Deducts the calculated charge, which can be zero.       |
| `duplicate`    | Identical retry of an existing event ID                   | Returns the original transaction without another debit. |
| `deduplicated` | New event ID for a feature already charged in this period | Records a zero-charge transaction.                      |

Event IDs are unique across the selected application. Retrying an ID with a different identity, feature, quantity, entity ID, or resolved callback amount returns `409 idempotency_conflict`.

If you omit `id`, the SDK creates a UUID and reuses it on its automatic transport retries. A separate call to `track` creates a new UUID. Supply your own ID when a job, request handler, or queue consumer can run again.

An identical retry returns the original charge even after a fixed price or period changes. For callback prices, preserve the original data and calculation when retrying; a different resolved amount conflicts with the original event.

`check` takes no event ID and creates no billable occurrence. Retrying a check simply reads the current state again. See [errors and retries](/errors) for failure handling.

## Periods and entities [#periods-and-entities]

For [period features](/usage-periods), `check` includes period boundaries. If the wallet or entity was already charged for that period, it returns a zero charge. A matching `track` records the repeated use with `status: "deduplicated"` and `deduplicatedFrom` pointing to the first transaction.

Use the same `entityId` in check and track when one wallet pays for several resources. The feature must have a deduplication period to accept an entity ID.

## HTTP equivalents [#http-equivalents]

`check` calls `POST /v1/check`. `track` calls `POST /v1/events`. Both accept an external wallet identity and a deployed feature key. For complete request schemas and responses, use the [API reference](/api).

For agents built with AI SDK, the [AI SDK integration](/integrations/ai-sdk) tracks model and tool usage through telemetry.