> ## Documentation Index
> Fetch the complete documentation index at: https://docs.unprice.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Billing Architecture and Usage Metering

> How Unprice models events, meters, features, and period-based usage.

Unprice meters usage within billing periods. That means usage is always interpreted relative to the current billing cycle, whether you bill monthly, yearly, or on a custom reset schedule.

The most important design decision is this: **meters are for usage entitlements**. Some features need a stream of events and an aggregation rule. Others are simply granted access and do not need a meter at all.

## 1. Core Concepts: Events vs. Meters vs. Features

These three terms work together, but they are not interchangeable:

| Term        | What it is                                                                | What it answers                                  |
| :---------- | :------------------------------------------------------------------------ | :----------------------------------------------- |
| **Event**   | An immutable envelope of activity sent from your application              | "What happened?"                                 |
| **Meter**   | A period-scoped rule that aggregates usage events for a usage entitlement | "How much usage happened this billing period?"   |
| **Feature** | A customer-facing capability in your product                              | "What is the customer paying for or limited on?" |

### Events

Events are the raw activity envelopes your application sends to Unprice.

* They are append-only records of activity.
* They should describe what happened at the moment it happened.
* They can carry multiple measured properties from the same product action.
* They are the input to one or more usage meters.

Use `eventSlug` for the broad action your app observed, such as `completions`. Put the measurable
facts inside `properties`. Each meter then chooses which property to aggregate.

For example, one `completions` event can describe a single customer request that made two LLM calls
and used several billable dimensions:

```ts theme={null}
await unprice.usage.record({
  idempotencyKey: request.id,
  customerId: "cus_1234567890",
  eventSlug: "completions",
  properties: {
    aiMessages: 1,
    llmCalls: 2,
    inputTokens: 1840,
    outputTokens: 620,
    totalTokens: 2460,
    toolCalls: 1,
    model: "gpt-4.1-mini"
  }
});
```

That one event could feed several meters:

| Feature            | Meter reads                 | What it measures                                            |
| :----------------- | :-------------------------- | :---------------------------------------------------------- |
| `ai-messages`      | `count` or `sum aiMessages` | One customer-visible message                                |
| `llm-calls`        | `sum llmCalls`              | Two provider calls inside the same request                  |
| `ai-token-usage`   | `sum totalTokens`           | Combined token usage for billing or analytics               |
| `ai-output-tokens` | `sum outputTokens`          | Output-token usage when that dimension is priced separately |
| `ai-tools`         | `sum toolCalls`             | Tool execution inside the same product action               |

The event is the envelope. The meters decide which fields matter for each usage entitlement.

### Meters

Meters are the rules that turn a stream of events into a number for the current billing period.

* A meter is only relevant for **usage entitlements**.
* A meter chooses the matching `eventSlug`, the aggregation method, and, when needed, the
  `aggregationField` inside the event properties.
* The result is what Unprice uses for billing, limits, and reporting on usage-based features.

Examples:

* "Count every API request this month"
* "Sum all bandwidth transferred this month"
* "Track the highest storage watermark reported this month"

### Features

Features are the actual capabilities you sell, limit, or expose to customers.

* A feature might be a metered capability, like API requests.
* A feature might also be simple access, like SSO or priority support.
* A feature can exist without any meter if it is not usage-based.

Examples:

* `pro-api-access`
* `database-storage`
* `priority-support`

<Tip>
  Not every entitlement has a meter. Usage entitlements need events plus an aggregation method.
  Non-usage entitlements such as flat access or fixed package access can exist without any
  aggregation rule.
</Tip>

## 2. Supported Aggregation Methods

Aggregation methods apply to **usage entitlements only**. Every meter evaluates usage inside the current billing period and resets automatically when that period ends.

| Method  | What it does during the period                                       | Good for                                            |
| :------ | :------------------------------------------------------------------- | :-------------------------------------------------- |
| `sum`   | Adds the values of a field across all events in the period           | Bandwidth, tokens, transferred GB                   |
| `count` | Counts how many matching events were received in the period          | API calls, jobs run, emails sent                    |
| `max`   | Keeps the highest value reported in the period                       | Peak storage, peak active users                     |
| `last`  | Uses the most recent value reported before billing closes the period | Active seats at period end, latest balance snapshot |

### `sum`

Use `sum` when each event contributes a numeric amount to the period total.

* Best for values that accumulate naturally.
* Common examples are tokens, bytes, seconds, or credits consumed.
* The total resets at the end of the billing period.

### `count`

Use `count` when the important signal is the number of events, not the numeric value inside each event.

* Best for request-style or action-style metrics.
* Common examples are API calls, export jobs, or webhook deliveries.
* The count resets at the end of the billing period.

### `max`

Use `max` when you care about the highest reported value reached during the period.

* Best for high-watermark billing.
* Common examples are storage, peak concurrent users, or largest queue depth.
* The recorded maximum resets at the end of the billing period.

### `last`

Use `last` when you care about the most recent reported value before the invoice or reset boundary.

* Best for end-of-period state.
* Common examples are active seats, enabled workspaces, or the final storage total at billing time.
* The stored value resets with the next billing period after that period closes.

## 3. Handling "Stateful" Metrics (The "Fast Cache, Slow Truth" Pattern)

Some usage is naturally event-shaped. Other usage is really a snapshot of a changing state. Treating those two categories the same is one of the fastest ways to create billing drift.

### Streaming metrics

Streaming metrics are actions that happen one event at a time.

Examples:

* API calls
* tokens generated
* emails sent
* files processed

For these metrics, report usage directly from the request path as the action happens.

```ts theme={null}
const { result, error } = await unprice.usage.consume({
  idempotencyKey: request.id,
  eventSlug: "completions",
  customerId: "cus_1234567890",
  featureSlug: "ai-messages",
  properties: {
    aiMessages: 1,
    llmCalls: 2,
    inputTokens: 1840,
    outputTokens: 620,
    totalTokens: 2460,
    route: "/v1/chat/completions",
    method: "POST"
  }
});

if (error) {
  throw error;
}

if (!result?.allowed) {
  return new Response("Usage limit reached", { status: 429 });
}
```

If the feature is truly "one event equals one unit," configure the meter with `count`. If the event
is an envelope with measured fields, use `sum` with the field that belongs to that feature, such as
`aiMessages`, `llmCalls`, `inputTokens`, `outputTokens`, `totalTokens`, or `toolCalls`.

### Stateful metrics

Stateful metrics represent a current total that lives in your own database or storage system.

Examples:

* rows stored
* active users
* provisioned seats
* bytes stored

For these metrics, the anti-pattern is sending create/delete deltas like `+1` on insert and `-1` on delete.

Why this causes problems:

* retries can double-apply a change
* dropped network requests can lose a decrement
* race conditions can reorder updates
* backfills and imports can skip historical events
* deletes are often harder to observe reliably than creates

Instead, use the **Fast Cache, Slow Truth** pattern.

### Real-time enforcement: Fast Cache

Use your own application or cache layer to enforce hard limits immediately.

* Keep the hot path in systems you control, such as Redis or your primary application database.
* Decide whether to allow or block the request before doing expensive work.
* Treat this as the fast operational check, not the source of billing truth.

Example: if you allow at most 10 active projects on a plan, check that constraint inside your app before creating project 11.

### Accurate billing: Slow Truth

Run a background job on a schedule, query the authoritative total from your database, and report that absolute total to Unprice as a snapshot event.

* Run hourly or daily, depending on how quickly the metric changes.
* Send the full current value, not a delta.
* Configure the meter as `max` if you bill on the highest watermark reached during the period.
* Configure the meter as `last` if you bill on the latest value at period close.

```ts theme={null}
async function syncActiveSeats(customerId: string) {
  const row = await db.query<
    { active_seats: number }
  >(
    `
      SELECT COUNT(*)::int AS active_seats
      FROM seats
      WHERE customer_id = $1
        AND revoked_at IS NULL
    `,
    [customerId]
  );

  const activeSeats = row[0]?.active_seats ?? 0;

  await unprice.usage.record({
    idempotencyKey: `active-seats:${customerId}:${new Date().toISOString().slice(0, 13)}`,
    eventSlug: "active_seats_snapshot",
    customerId,
    properties: {
      usage: activeSeats,
      source: "cron",
      metricType: "snapshot"
    }
  });
}
```

This same pattern works for storage totals too:

```sql theme={null}
SELECT SUM(bytes_used) AS total_bytes
FROM files
WHERE customer_id = $1;
```

Use `max` when you care about the highest watermark during the month. Use `last` when you care about the value that exists at the moment billing closes the period.

## 4. How to Handle "Lifetime" Limits (e.g., Lifetime Free Trials)

Sometimes the business rule you want is "never reset this allowance." Treat that as an entitlement or grant configuration decision, not as a different metering period.

Recommended pattern:

* Create a normal usage feature for the activity you want to track.
* Use a standard usage meter for that feature.
* Configure the customer's grant or entitlement so it does not expire and does not reset.
* Let the grant carry the long-lived allowance while the meter continues to process usage events normally.

Example:

* You want to give every free customer 10,000 API calls total.
* Keep the feature as `api-requests`.
* Meter the activity as normal usage.
* Create a non-expiring grant with a limit of `10,000`.

This keeps your billing architecture consistent:

* events still describe activity
* meters still aggregate usage
* entitlements and grants still define what the customer is allowed to consume

### Best Practices

* Use meters only for usage entitlements.
* Use `count` or `sum` for streaming activity reported directly from the request path.
* Use scheduled snapshot events for stateful resources.
* Use `max` for high-watermark billing and `last` for end-of-period state billing.
* Enforce hard operational limits in your own app or cache layer first.
* Model long-lived allowances as grant or entitlement rules rather than inventing a separate metering model.

Next steps:

* Read [Entitlements](/concepts/pricing/entitlements) to understand how features become customer access rules.
* Read [Usage consume](/libraries/ts/sdk/usage/consume) for the SDK contract and request shape.
