Usage metering
A metered product (quantity_source: METERED) gets its billable quantity from raw events you post as consumption happens. The pipeline is:
Code
A meter is an org-scoped, reusable aggregation definition - it describes what is measured (which events count, how to combine them, what windowing applies). A product meter binding wires a meter to a product and declares how the result is consumed (as the line's billable quantity, as a pricing variable, or for reporting only).
Usage events
A UsageEvent is one consumption data point. Required fields:
| Field | Notes |
|---|---|
customer_id | Who consumed |
subscription_id | Sets the billing-period context for aggregation and pricing |
product_id | Which metered product the event feeds |
metric_key | Stable string (e.g. api_calls, storage_gb) - the ingestion key a meter matches against |
quantity | Decimal - 1 for a count event, 2.5 for 2.5 GB, 3600 for an hour-in-seconds, etc. |
| Field | Required | Description |
|---|---|---|
customer_id | Yes | UUID. Who consumed the resource. |
subscription_id | Yes | UUID. Subscription context (determines billing period). |
product_id | Yes | UUID. The product consumed (must be metered, with a meter bound to it). |
metric_key | Yes | Identifies the type of consumption (e.g., api_calls, storage_gb). |
quantity | Yes | Decimal numeric quantity (e.g., 1 for a single call, 2.5 for 2.5 GB). Must be non-negative. |
timestamp | No | ISO 8601 timestamp - when consumption occurred. Defaults to server ingest time when omitted. |
idempotency_key | Yes | Unique key for deduplication - critical for retry safety. |
price_key | Conditional | Required when the product is keyed (it carries a key_set_id - see Keyed prices). Selects which sibling price the event maps to. Forbidden for single-price products. |
external_cost_amount / external_cost_currency | No | Pass-through cost from an upstream provider (for cost-plus billing). |
pricing_vars, dimension_vars | No | Variables consumed by formula prices and meter filters. |
hold_id | No | UUID of an active wallet hold this event should consume. Required when a hold is active for the same (subscription_id, metric_key). |
wallet_currency | No | Currency code. Pin which wallet the downstream invoice should settle against. |
metadata | No | Free-form key-value bag for your own use. |
For a keyed product, price_key must be an active, non-excluded entry of the product's key set. A missing key on a keyed product, a price_key on an unkeyed product, or a key that isn't an effective entry of the set is rejected with a 400 validation error at ingest.
Ingesting
The single-event and batch endpoints share one URL: POST /v1/transaction/usage-event. The handler inspects the body and dispatches to single or batch processing automatically. To send a batch, wrap events in { "events": [ ... ] }.
Code
A 409 HOLD_REQUIRED response is returned when an active hold exists for the (subscription_id, metric_key) pair and the request omits hold_id. Either supply the matching hold_id or release the hold first.
Price-key validation
For a keyed product (one linked to a key_set_id), every event must carry a price_key that is an active, non-excluded entry of the product's key set. The ingestion guard checks set membership directly, so a typo'd or stale key fails fast: it returns a 400 validation error at ingest rather than silently finding no matching price. An unkeyed product rejects any price_key with the same 400.
Meters
A Meter is an org-scoped, reusable aggregation definition. It describes what is measured - it does not, on its own, bind to a product. Create one with POST /v1/meters.
| Field | Notes |
|---|---|
key | Org-unique slug for the meter (lowercase letter then [a-z0-9_-], 1-64 chars), e.g. api_calls |
name | Human-readable label |
metric_key | The event ingestion key this meter matches against |
function | One of the eight aggregation functions below |
value_field | The event field aggregated by SUM, MAX, MIN, AVG, P95, LAST (e.g. quantity). Required for those functions; must be absent for COUNT/UNIQUE_COUNT |
unique_by_field | The event field counted distinctly by UNIQUE_COUNT. Required for UNIQUE_COUNT; must be absent otherwise |
window | A { "kind": ... } window descriptor (see Windowing). Defaults to { "kind": "CALENDAR_PERIOD" } when omitted |
filter_tree | Optional AND/OR tree of comparisons - narrows which events the meter picks up (see Filters) |
dimensions | Optional list of event metadata fields exposed for group-by in usage queries |
dedup_key_path | JSON-path used to deduplicate events. Defaults to event.idempotency_key, so at-least-once ingestion is billed exactly once out of the box |
Aggregation functions
| Function | Use for |
|---|---|
SUM | Total consumption (GB transferred, API calls, minutes used) - sums value_field |
COUNT | Number of events regardless of quantity value |
MAX | Peak in the window of value_field (max concurrent connections, max active devices) |
MIN | Minimum value_field in the window |
AVG | Average of value_field over the window |
UNIQUE_COUNT | Distinct count of unique_by_field (e.g. distinct active users) |
LAST | The latest value_field in the window (gauge-style metrics) |
P95 | 95th percentile of value_field |
Code
Windowing
A meter's window is a polymorphic descriptor: { "kind": ... }, optionally with a duration or session_gap. The kind is one of:
kind | Notes |
|---|---|
CALENDAR_PERIOD | (default) The whole current billing period - aligns to the subscription's cycle. Accepts an optional duration to tile the period into shorter sub-windows |
CALENDAR_HOURLY | Calendar-hour buckets |
CALENDAR_DAILY | Calendar-day buckets |
SLIDING | Rolling window; requires duration |
TUMBLING | Fixed non-overlapping window; requires duration |
SESSION is reserved for a future phase and is rejected by the validator until its evaluator ships.
Meter bindings
A ProductMeterBinding wires a meter to a product and declares how the evaluation result is consumed. Create one with POST /v1/products/{productID}/meter-bindings. A product can carry many bindings, but exactly one may have role: BILLABLE_QUANTITY.
| Field | Notes |
|---|---|
meter_id | The meter to bind |
role | How the result is consumed - BILLABLE_QUANTITY, PRICING_VAR, or INFORMATIONAL (see below) |
pricing_var_name | Slug naming the variable the result is exposed as. Required for BILLABLE_QUANTITY/PRICING_VAR; optional for INFORMATIONAL |
overlay_filters | Optional per-binding AND/OR filter tree layered on top of the meter's own filter |
included_quantity_source | Where free-tier / included quantity is read from: PLAN_PIN, METER_WINDOW, or NONE (default) |
rollover_eligible | Whether unused included quantity rolls over. Only valid when role: BILLABLE_QUANTITY |
The role determines what the meter's result feeds:
| Role | Effect |
|---|---|
BILLABLE_QUANTITY | The result becomes the billable quantity walked through the tiers, and drives the allowance ladder and rollover |
PRICING_VAR | The result is exposed as a named variable available to tier rate-expressions (e.g. a cost variable for cost-plus pricing on resold metered services) |
INFORMATIONAL | Evaluated and surfaced in audit/reporting, but not consumed by the formula |
Code
Filters
A meter's filter_tree (and a binding's overlay_filters) narrows which events are aggregated. It is a serializable AND/OR tree: a root node that is either a group node (op: AND / op: OR with children) or a leaf comparison. Same operator grammar across the platform:
| Operator | Meaning |
|---|---|
eq, neq | Equals / not equals |
gt, gte, lt, lte | Numeric / timestamp comparison |
in, not_in | Set membership (value is a JSON array) |
contains, starts_with, ends_with | Substring / prefix / suffix on string fields |
exists, not_exists | Field presence (value ignored) |
regex, jsonpath_exists | Pattern match / JSON-path existence |
A leaf node's field is a dotted JSON path into the event's metadata (e.g. tier or request.region). Trees are limited to a depth of 8.
Code
This matches only premium-tier API calls in the two US regions.
Deduplication semantics
Every meter deduplicates events by dedup_key_path, which defaults to the event's idempotency_key. Two events resolving to the same dedup value are treated as one. Pair this with stable per-event keys on your side (request ID, log line UUID) and at-least-once retry becomes safe. Set dedup_key_path to a different JSON-path when your upstream provides a more authoritative dedup token than idempotency_key.
Querying usage
Query usage events through the top-level /v1/usage endpoint. Filter by customer, subscription, product, metric, or time range using the standard list query parameters (see Pagination).
Code
Response is the standard list envelope:
Code
Use cases for usage queries:
- Build real-time usage dashboards for customers
- Set up alerts when consumption approaches plan limits
- Preview upcoming invoice amounts before billing cycle ends
End-to-end example
Build an API-calls metered product priced with a graduated tier walk.
1. Define the metered product:
Code
2. Create the meter and bind it to the product:
Code
3. Publish the staircase price:
Code
The billing cadence (e.g. monthly) is set on the subscription via billing_interval_unit + billing_interval_count, not on the price.
4. Ingest usage events
Code
5. At period close, the invoice line:
The meter sums the period's events into a billable quantity; the STAIRCASE walk applies; the line lands on the invoice as (used quantity) × tier rates → total. Audit detail (per-tier breakdown) is on the line's atoms field when fetched with ?depth=full - see Invoices.
- Pricing Models - understand tiered and formula pricing
- Subscriptions - how subscriptions drive billing
- API Reference - Meters - meter and meter-binding configuration
- Committed use plans - volume commitments at discounted rates