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

# Usage-Based Billing

> Meter any action your product performs, price it on a plan with included credits and a per-unit overage, and let Yuno bill the usage on each subscription's renewal

<Note>
  **Enabled per organization**

  Usage-based billing must be enabled for your organization before these endpoints accept requests. Contact your Yuno account manager to enable it. Requests from an organization where it is not enabled return `403 PRODUCT_NOT_ENABLED`.
</Note>

Subscriptions bill a fixed price on a fixed cadence. Usage-based billing adds a variable part on top: you tell Yuno *what* to count (a **meter**), *how much of it* a plan includes and what extra units cost (a **metered price**), and then report *how much each subscriber consumed* (**usage events**). Yuno aggregates the usage per subscription and billing cycle and bills anything beyond the included allowance together with the subscription's regular renewal charge.

Typical uses: an AI product that includes 10,000 tokens a month and bills the rest per token, an API that includes 1,000 requests and bills per extra call, a storage product billed per gigabyte held.

## How the pieces fit

1. **Meter** — a named, countable action: `ai_tokens`, `api_request`, `storage_gb`. It defines the `event_name` your systems send and the aggregation (`SUM`, `COUNT` or `LAST`). A meter has no price of its own — at most a `default_price` in USD that plans can inherit. See [The Meter Object](/reference/meters/the-meter-object).
2. **Metered price** — the meter attached to a [plan](/docs/payment-features/subscriptions/plans): how many units are included per cycle (`credits`) and what each unit beyond that costs (`price_per_credit`, optionally per country). A plan can carry any number of metered prices next to its flat price. See [The Metered Price Object](/reference/meters/the-metered-price-object).
3. **Subscription** — created on that plan as usual. It inherits every metered price; nothing changes in [Create Subscription](/reference/subscriptions/create-subscription).
4. **Usage events** — one call per unit of work, carrying the `event_name`, the `subscription_id`, a `value` and your own `event_id` for idempotency. See [Report Usage Event](/reference/meters/report-usage-event).
5. **Renewal** — at the end of the billing cycle, Yuno computes the billable quantity (`max(0, usage − credits)`) and adds `billable × price_per_credit` to the subscription's renewal charge.

<Note>
  **Meters are generic**

  Nothing here is specific to AI. A meter can count deliveries, minutes, messages, seats or gigabytes just as well as tokens — the meter's `unit_label` / `plural_unit_label` are what usage is displayed in.
</Note>

## Quick start

The example builds a "Max" plan at \$200/month that includes 10,000 AI tokens and bills \$0.002 per token beyond that. Requests use the standard `public-api-key` / `private-secret-key` headers. `X-Idempotency-Key` is accepted on every request as elsewhere in the Yuno API, but usage events have their own dedicated dedup key — see [Idempotency and timestamps](#idempotency-and-timestamps).

<Steps>
  <Step title="Create the meter">
    Choose an `event_name` your systems will send (lowercase, `_` and `.` allowed) and the aggregation. `default_price` is optional and must be USD; it saves you from repeating the unit price on every USD plan.

    ```bash theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
    curl -X POST https://api-sandbox.y.uno/v1/subscriptions/meters \
      -H 'Content-Type: application/json' \
      -H 'public-api-key: <Your public-api-key>' \
      -H 'private-secret-key: <Your private-secret-key>' \
      -H 'X-Idempotency-Key: 5f1c2c1e-1a2b-4c3d-9e8f-0a1b2c3d4e5f' \
      -d '{
        "account_id": "00000000-0000-4000-8000-000000000002",
        "name": "AI tokens",
        "description": "LLM tokens consumed by model responses, reported once per response",
        "event_name": "ai_tokens",
        "aggregation": "SUM",
        "unit_label": "token",
        "plural_unit_label": "tokens",
        "default_price": { "currency": "USD", "value": 0.002 }
      }'
    ```

    The response is `201` with the meter, always `ACTIVE`. Keep its `id` — you need it to attach the meter to a plan.
  </Step>

  <Step title="Attach it to a plan">
    Either create the plan with the meter inline, or attach it to a plan that already exists. Both take the same metered-price body: `meter_id`, `credits` (included units per cycle), `pricing_strategy` and, unless inherited, `price_per_credit`.

    <CodeGroup>
      ```bash New plan theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
      curl -X POST https://api-sandbox.y.uno/v1/subscriptions/plans \
        -H 'Content-Type: application/json' \
        -H 'public-api-key: <Your public-api-key>' \
        -H 'private-secret-key: <Your private-secret-key>' \
        -H 'X-Idempotency-Key: 8d5c9a3e-4b7f-4a2c-b1d0-2e3f4a5b6c7d' \
        -d '{
          "account_id": "00000000-0000-4000-8000-000000000002",
          "name": "Max",
          "base_amount": { "currency": "USD", "value": 200.0 },
          "frequency": { "type": "MONTH", "value": 1 },
          "meters": [
            {
              "meter_id": "55555555-5555-5555-5555-555555555555",
              "credits": 10000,
              "pricing_strategy": "PER_UNIT"
            }
          ]
        }'
      ```

      ```bash Existing plan theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
      curl -X POST https://api-sandbox.y.uno/v1/subscriptions/plans/00000000-0000-4000-8000-000000000001/meters \
        -H 'Content-Type: application/json' \
        -H 'public-api-key: <Your public-api-key>' \
        -H 'private-secret-key: <Your private-secret-key>' \
        -H 'X-Idempotency-Key: 9e6d0b4f-5c8a-4b3d-a2e1-3f4a5b6c7d8e' \
        -d '{
          "meter_id": "55555555-5555-5555-5555-555555555555",
          "credits": 10000,
          "pricing_strategy": "PER_UNIT",
          "price_per_credit": { "currency": "USD", "value": 0.002 }
        }'
      ```
    </CodeGroup>

    In the "New plan" example `price_per_credit` is omitted: the plan is USD and the meter has a `default_price`, so \$0.002 applies and the metered price comes back with `price_per_credit: null`. The plan response and [Retrieve Plan](/reference/plans/retrieve-plan) now carry the metered price under `meters[]` (an empty array on plans without meters).

    <Note>
      Attaching a meter to a live plan does not change the plan's flat price or any existing subscriber's base amount — it only adds a variable line for usage beyond `credits`. Sending `{ "meters": [ ... ] }` to `PATCH /subscriptions/plans/{plan_id}` does the same for several meters at once — atomically (one bad entry and nothing is attached) — and ignores every other plan field.
    </Note>
  </Step>

  <Step title="Subscribe customers to the plan">
    Nothing new here: call [Create Subscription](/reference/subscriptions/create-subscription) with the `plan_id`. The subscription inherits the plan's metered prices. Store the subscription `id` — every usage event references it.
  </Step>

  <Step title="Report usage as it happens">
    Send one event per unit of work. `event_id` is your idempotency key; `value` is required for `SUM` and `LAST` meters and defaults to 1 for `COUNT`. The subscription must be `ACTIVE`, `TRIALING` or `PAST_DUE`.

    ```bash theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
    curl -X POST https://api-sandbox.y.uno/v1/subscriptions/meters/events \
      -H 'Content-Type: application/json' \
      -H 'public-api-key: <Your public-api-key>' \
      -H 'private-secret-key: <Your private-secret-key>' \
      -H 'X-Idempotency-Key: 3c7e8f10-2b4d-4e6a-9c1f-5a7b9d2e4f60' \
      -d '{
        "event_id": "11111111-1111-1111-1111-111111111111",
        "event_name": "ai_tokens",
        "subscription_id": "33333333-3333-3333-3333-333333333333",
        "value": 1500,
        "occurred_at": "2026-08-18T09:00:04Z",
        "metadata": [{ "key": "model", "value": "llm-large" }]
      }'
    ```

    A `201` confirms the event was validated and stored:

    ```json theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
    {
      "id": "22222222-2222-2222-2222-222222222222",
      "event_id": "11111111-1111-1111-1111-111111111111",
      "event_name": "ai_tokens",
      "meter_id": "55555555-5555-5555-5555-555555555555",
      "subscription_id": "33333333-3333-3333-3333-333333333333",
      "value": 1500,
      "received_at": "2026-08-18T09:00:04.000000Z"
    }
    ```

    Retrying the same request (same `event_id`) returns this same response and never counts the 1,500 tokens twice — even if the retry carries a different `value`, the original is kept.
  </Step>
</Steps>

## Choosing an aggregation

The aggregation is fixed when you create the meter and decides what a cycle's quantity means:

<Tabs>
  <Tab title="SUM — consumption">
    Adds every event's `value`. Report the amount consumed by each unit of work.

    ```json theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
    { "event_id": "resp_8f3k2", "event_name": "ai_tokens", "subscription_id": "3333…", "value": 1280 }
    ```

    Three events of 1,280 / 640 / 2,100 tokens → cycle quantity 4,020 tokens. Use for tokens, minutes transcribed, messages sent, deliveries completed.
  </Tab>

  <Tab title="COUNT — per call">
    Counts events; `value` is ignored (each event counts as 1, so you can omit it).

    ```json theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
    { "event_id": "req_01J5Y3Q9K8ZC5G6E4Y2W7X8N9M", "event_name": "api_request", "subscription_id": "3333…" }
    ```

    1,432 events → cycle quantity 1,432 requests. Use for API calls, jobs, exports.
  </Tab>

  <Tab title="LAST — gauge">
    Keeps the most recent `value` in the cycle by `occurred_at`. Report the current level, not the delta.

    ```json theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
    { "event_id": "storage-2026-08-18T09:00-sub-3333", "event_name": "storage_gb", "subscription_id": "3333…", "value": 128.4, "occurred_at": "2026-08-18T09:00:00Z" }
    ```

    Reports of 96.0 → 110.2 → 128.4 GB → cycle quantity 128.4 GB. Use for storage held, seats in use, active devices.
  </Tab>
</Tabs>

## Pricing a meter on a plan

A metered price has three pricing knobs:

* **`credits`** — the units included in the plan each billing cycle. Think "allowance": the first `credits` units are covered by the plan's flat price. `0` bills every unit. `credits` is measured in the meter's unit (tokens, requests, GB) and resets each cycle — it is **not** a prepaid balance or wallet.
* **`price_per_credit`** — the price of each unit beyond `credits`, in the plan's base currency. On a USD plan you can omit it if the meter has a `default_price`; the meter's default then applies and the metered price is returned with `price_per_credit: null`. On a non-USD plan, or when the meter has no default, it is required. The currency must always match the plan's `base_amount` currency.
* **`country_prices`** — optional per-unit prices by subscriber country (one entry per country), same shape as the plan's own `country_prices`. A subscriber whose country is not listed pays `price_per_credit`. The subscriber's country comes from the subscription — usage events carry no `country` or `account_id` — so `country_prices` are resolved automatically per subscriber.

`pricing_strategy` selects how overage is rated, and each strategy carries its own config fields at attach time:

<Tabs>
  <Tab title="PER_UNIT — flat rate">
    Billable units × `price_per_credit` (or the subscriber's `country_prices` entry). The default choice for tokens, requests, minutes.

    ```json theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
    { "pricing_strategy": "PER_UNIT", "credits": 10000, "price_per_credit": { "currency": "USD", "value": 0.002 } }
    ```
  </Tab>

  <Tab title="PACKAGE — bundles">
    Billable units are rounded **up** to the nearest whole `package_size` and billed at `package_price` per bundle — a partial bundle is billed as a full one. `package_size` accepts decimals, including scientific notation (`1e3` → `1000`); the stored value is rounded to 4 decimal places.

    ```json theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
    {
      "pricing_strategy": "PACKAGE",
      "credits": 10000,
      "package_size": 1000,
      "package_price": { "currency": "USD", "value": 1.50 },
      "package_country_prices": [{ "country": "BR", "amount": { "currency": "BRL", "value": 7.50 } }]
    }
    ```

    Example: 10,000 included + package of 1,000 at \$1.50 → a subscriber at 13,200 tokens has 3,200 billable units → `ceil(3200 / 1000) = 4` bundles → \$6.00 usage.
  </Tab>

  <Tab title="TIERED — graduated">
    Billable units are split across ascending bands (`tiers[]`), each priced at its own `rate` — usage is charged at the rate of the band it falls in, marginally (the first band's units at the first band's rate, the next band's units at the next rate, and so on), not at a single blended rate. Every tier but the last needs an `up_to` upper bound (**exclusive of the previous tier, inclusive of itself**, strictly ascending); the last tier's `up_to` must be `null` (unbounded). All tiers in one metered price must be priced in the same currency (or the same set of countries, for `country_rates`).

    ```json theme={"theme":{"light":"github-dark","dark":"github-dark-dimmed"}}
    {
      "pricing_strategy": "TIERED",
      "credits": 0,
      "tiers": [
        { "up_to": 1000, "rate": { "currency": "USD", "value": 0.01 } },
        { "up_to": 5000, "rate": { "currency": "USD", "value": 0.008 } },
        { "up_to": null, "rate": { "currency": "USD", "value": 0.005 } }
      ]
    }
    ```

    Example: 6,000 billable units → 1,000 at \$0.01 + 4,000 at \$0.008 + 1,000 at \$0.005 = \$47.00.
  </Tab>
</Tabs>

For non-`PER_UNIT` strategies, the meter must be priced in every currency the plan supports — a plan with `country_prices` in currencies the metered price doesn't cover is rejected (`PLAN_METER_MISSING_CURRENCY`), and every priced currency must match one the plan actually uses (`PLAN_METER_BASE_CURRENCY_MISMATCH`). All computed usage amounts (any strategy) round half-up to 4 decimal places.

<Warning>
  **Pricing config is set once, at attach time.** `credits`, `price_per_credit` and `country_prices` can be updated on a live `PER_UNIT` metered price with [Update Plan Meter](/reference/meters/update-plan-meter) — the new values apply to usage rated from the next billing cycle onward. `package_size`, `package_price`, `package_country_prices` and `tiers` cannot be changed after attach: switching a metered price's `pricing_strategy` validates that the request carries the target strategy's required config and clears the previous strategy's now-inapplicable fields — a switch with no config, or with the wrong strategy's config, is rejected. There is no detach endpoint, so to correct a `PACKAGE`/`TIERED` config you cannot re-attach the same meter — create a new meter with a new `event_name`, attach it with the corrected config, point your integration at the new `event_name`, and archive the old meter. The plan's flat price stays [immutable](/docs/payment-features/subscriptions/plans) as before.
</Warning>

## What happens at renewal

<Note>
  Usage is billed on the subscription's renewal charge, together with the plan's flat price — there is no separate usage invoice.
</Note>

For each subscription and each metered price on its plan, at the end of the billing cycle:

1. **Quantity** — events whose `occurred_at` falls in the cycle are aggregated with the meter's `aggregation` (SUM / COUNT / LAST).
2. **Billable units** — `max(0, quantity − credits)`. Unused credits do not roll over.
3. **Amount** — `billable × price_per_credit` (or the subscriber's `country_prices` entry).
4. **Charge** — the amount is added to the subscription's renewal charge alongside the plan's flat price. One charge, one payment, as today. A subscription with usage under its credits pays only the flat price.

Example, on the Max plan (10,000 tokens included, \$0.002 per extra token): a subscriber consumes 13,500 tokens in August → 3,500 billable → \$7.00 usage, so the renewal charges \$207.00. Another subscriber consumes 8,000 → \$200.00.

## Idempotency and timestamps

* **`event_id` is required and is your idempotency key.** Make it unique per event — a UUID, or the id of the request/response/job it came from — and reuse it on every retry. Yuno stores the first event under that id and answers every replay with the same response, including the original `value` (a different `value` on the retry is discarded). Usage is never double counted, however many times you retry. A request without `event_id` is rejected with `400`.
* **Report close to real time**, but you can backdate. `occurred_at` defaults to the receive time (returned as `received_at`) and can be up to **35 days** in the past; older events are rejected with `400`. The `occurred_at` (not the receive time) decides which billing cycle the event lands in.
* **`X-Idempotency-Key`** is accepted, as on the rest of the Yuno API (same key + same body → same response), but it is optional here and not what dedupes usage — `event_id` is the mechanism that protects billing, and it's the one that's required.
* **One event per request.** Aggregate on your side if you produce many small events (for example, sum tokens per response rather than per token) — a `SUM` meter is designed for that.
* **The subscription must be active.** `ACTIVE`, `TRIALING` and `PAST_DUE` subscriptions accept usage (a subscription in payment retries keeps metering); events for a `PAUSED`, `CANCELED` or `COMPLETED` subscription are rejected with `400 SUBSCRIPTION_NOT_ACTIVE` and nothing is counted.

## Error handling

Validation is synchronous: a rejected event returns `400` immediately (lookup failures are `400` too, never `404`) and is never counted, so treat non-`201` responses as "not recorded" and retry with the same `event_id` after fixing the request. The codes you will meet most often:

| Situation                                                                                                                                      | Response                                                                                                                                   |
| ---------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `event_name` does not match an `ACTIVE` meter of your account                                                                                  | `400 METER_NOT_FOUND`                                                                                                                      |
| `subscription_id` unknown                                                                                                                      | `400 SUBSCRIPTION_NOT_FOUND`                                                                                                               |
| Subscription is `PAUSED`, `CANCELED`, `COMPLETED` or otherwise not active                                                                      | `400 SUBSCRIPTION_NOT_ACTIVE` — "The subscription is not active"                                                                           |
| Usage-based billing not enabled for your organization                                                                                          | `403 PRODUCT_NOT_ENABLED`                                                                                                                  |
| `value` missing on a `SUM`/`LAST` meter                                                                                                        | `400 INVALID_PARAMETERS` — "The value is required for a SUM meter" / "…for a LAST meter"                                                   |
| `occurred_at` older than 35 days                                                                                                               | `400 INVALID_PARAMETERS`                                                                                                                   |
| Second meter with the same `event_name` (even if the first is archived)                                                                        | `409 METER_EVENT_NAME_CONFLICT`                                                                                                            |
| Same meter attached to a plan twice                                                                                                            | `409 PLAN_METER_ALREADY_ASSIGNED`                                                                                                          |
| Attaching an archived (`INACTIVE`) meter                                                                                                       | `400 METER_NOT_FOUND`                                                                                                                      |
| `price_per_credit` in the wrong currency / missing on a non-USD plan                                                                           | `400 INVALID_PARAMETERS`                                                                                                                   |
| `package_size`/`package_price`/`package_country_prices`/`tiers` sent for the wrong `pricing_strategy` (or on Update Plan Meter at all today)   | `400 PLAN_METER_PRICING_CONFIG_UNSUPPORTED` — "Package and tier pricing configuration is not supported for this meter's pricing strategy." |
| `tiers[]` bounds not strictly ascending, last tier's `up_to` not `null`, or an earlier tier's `up_to` missing                                  | `400 INVALID_PARAMETERS`                                                                                                                   |
| A currency the plan supports has no matching `country_prices`/`package_country_prices`/`country_rates` entry on a non-`PER_UNIT` metered price | `400 PLAN_METER_MISSING_CURRENCY`                                                                                                          |
| `package_price`/tier `rate` currency doesn't match the plan's base currency                                                                    | `400 PLAN_METER_BASE_CURRENCY_MISMATCH`                                                                                                    |

The full list, with every message, is on [Meter Error Codes](/reference/meters/meter-error-codes).

## FAQ

<AccordionGroup>
  <Accordion title="Do I need a new plan to add usage pricing to an existing one?">
    No. Attach the meter to the live plan with [Attach Meter to Plan](/reference/meters/attach-meter-to-plan) (or the attach-only `PATCH /subscriptions/plans/{plan_id}` with `meters[]`). The flat price and existing subscribers' base amount are unchanged; usage beyond `credits` starts being billed on their next renewal.
  </Accordion>

  <Accordion title="Can a plan have several meters?">
    Yes, any number — for example tokens (`SUM`) plus API requests (`COUNT`) plus storage (`LAST`), each with its own `credits` and unit price. Each meter can be attached to a given plan once.
  </Accordion>

  <Accordion title="Are credits shared across subscriptions or across meters?">
    Neither. `credits` apply per subscription, per meter, per billing cycle. Two subscribers on the same plan each get the full allowance; unused credits do not carry over.
  </Accordion>

  <Accordion title="What happens if I report usage for a subscription that isn't on a plan with that meter?">
    The event is validated against the meter and the subscription only, so it is accepted and stored — but only usage against a metered price on the subscription's plan is rated and billed. Check the plan's `meters[]` if usage isn't showing up on the renewal.
  </Accordion>

  <Accordion title="Can I change a meter's event_name or aggregation?">
    No — both are immutable because your systems and past events depend on them; a PATCH that includes them is silently ignored. Create a new meter (with a new `event_name` — names stay reserved even after archiving) and archive the old one with [Update Meter](/reference/meters/update-meter) (`status: INACTIVE`). Archived meters reject new events with `400 METER_NOT_FOUND`, can't be attached to plans, and their existing metered prices are frozen; you can reactivate one at any time with `status: ACTIVE`.
  </Accordion>

  <Accordion title="Can I remove a metered price from a plan?">
    There is no detach endpoint. Archive the meter to stop ingestion, or raise `credits` so no overage is billed.
  </Accordion>

  <Accordion title="Can I price the meter in a currency other than USD?">
    Yes, per plan. The meter's `default_price` is USD-only, but a metered price on a BRL plan takes `price_per_credit` in BRL (required — nothing is inherited for non-USD plans), and `country_prices` lets you set per-country unit prices in each country's currency.
  </Accordion>

  <Accordion title="What happens if I report usage for a paused or canceled subscription?">
    The event is rejected with `400 SUBSCRIPTION_NOT_ACTIVE` and nothing is counted — only `ACTIVE`, `TRIALING` and `PAST_DUE` subscriptions accept usage. Resume the subscription first ([Resume Subscription](/reference/subscriptions/resume-subscription)), then report the usage; you can backdate `occurred_at` up to 35 days.
  </Accordion>

  <Accordion title="Can I meter usage without a subscription?">
    No. Every event needs a `subscription_id`; usage is billed on that subscription's renewal.
  </Accordion>
</AccordionGroup>

## Reference

* [The Meter Object](/reference/meters/the-meter-object) · [Create](/reference/meters/create-meter) · [List](/reference/meters/list-meters) · [Retrieve](/reference/meters/retrieve-meter) · [Update](/reference/meters/update-meter)
* [Report Usage Event](/reference/meters/report-usage-event)
* [The Metered Price Object](/reference/meters/the-metered-price-object) · [Attach Meter to Plan](/reference/meters/attach-meter-to-plan) · [List Plan Meters](/reference/meters/list-plan-meters) · [Update Plan Meter](/reference/meters/update-plan-meter)
* [Meter Error Codes](/reference/meters/meter-error-codes)
* [Plans](/docs/payment-features/subscriptions/plans) · [The Plan Object](/reference/plans/the-plan-object)
