Usage meters: definitions, threshold breaches, and consuming products.
List meters
Returns a paginated list of meters. Supports the unified field.op=value filter grammar.
query Parameters
created_atFilter on created_at (date-time). Operators: eq, gt, gte, lt, lte — dot grammar, e.g. created_at.gt=value; a bare created_at=value means eq.
functionFilter on function (enum). Operators: eq, in — dot grammar, e.g. function.in=value; a bare function=value means eq. A bare comma-separated value is in-sugar: function=a,b means function.in=a,b. Legal values: SUM, COUNT, MAX, MIN, AVG, UNIQUE_COUNT, LAST, P95.
keyFilter on key (string). Operators: eq, in, contains — dot grammar, e.g. key.in=value; a bare key=value means eq. A bare comma-separated value is in-sugar: key=a,b means key.in=a,b.
late_event_policyFilter on late_event_policy (enum). Operators: eq, in — dot grammar, e.g. late_event_policy.in=value; a bare late_event_policy=value means eq. A bare comma-separated value is in-sugar: late_event_policy=a,b means late_event_policy.in=a,b. Legal values: DROP, CLAMP_TO_PERIOD, REBILL_PRIOR.
metric_keyFilter on metric_key (string). Operators: eq, in, contains — dot grammar, e.g. metric_key.in=value; a bare metric_key=value means eq. A bare comma-separated value is in-sugar: metric_key=a,b means metric_key.in=a,b.
nameFilter on name (string). Operators: eq, contains — dot grammar, e.g. name.contains=value; a bare name=value means eq.
updated_atFilter on updated_at (date-time). Operators: eq, gt, gte, lt, lte — dot grammar, e.g. updated_at.gt=value; a bare updated_at=value means eq.
limitPage size. Values above the server-side cap are clamped (standard default 50, cap 200; a few document-heavy lists use larger windows). Invalid values fall back to the default.
cursorOpaque continuation token from the previous response's pagination.cursor. Omit for the first page. Cursors are stateless and do not expire, but are only valid for the list and filters that produced them.
searchCase-insensitive substring match over key, name, and metric_key. Filterable fields (key, name, metric_key, function, late_event_policy, created_at, updated_at) accept apifilter operator suffixes
sortOrdering: created_at, name, metric_key, function or updated_at, each with an optional :asc/:desc suffix (default created_at:desc). An unknown value is rejected. Cursors are bound to the ordering that issued them, so changing sort mid-walk needs a fresh first page.
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
List meters › Responses
OK
Create a meter
Creates an org-scoped meter usable by any product.
Headers
Idempotency-KeyUnique key that makes this POST safe to retry: repeats with the same key replay the first response instead of re-executing. Replays are scoped to the retrying principal (same API key / user) and kept for 24 hours. Required on every POST.
Client-generated idempotency key (e.g. a UUID).
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Create a meter › Request Body
functionAggregation applied over the window: SUM, COUNT, MAX, MIN, AVG, UNIQUE_COUNT, LAST, or P95. Matched case-insensitively: the server also accepts the all-uppercase and all-lowercase form of each value. The form published here is the canonical one and is what reads back.
keyOrg-unique slug identifying the meter; lowercase letter then [a-z0-9_-], 1-64 chars.
metric_keyEvent ingestion key this meter aggregates over.
nameHuman-readable label for the meter.
When set, defines this as a derived meter computed from other meters via a formula. An empty object is the same as omitting it.
dedup_key_pathJSON-path to the event field used for deduplication; default uses the event's idempotency_key.
dimensionsEvent metadata fields exposed for group-by.
event_schemaJSON-Schema document validating ingested events; null or omitted disables validation. Must compile as a JSON Schema — a document that does not is rejected here rather than failing every later ingest.
Predicate tree restricting which events the meter includes.
late_event_policyHow events arriving after period close are handled: DROP, CLAMP_TO_PERIOD, or REBILL_PRIOR. Matched case-insensitively: the server also accepts the all-uppercase and all-lowercase form of each value. The form published here is the canonical one and is what reads back.
negative_allowedWhether the aggregated value may go negative (e.g. for credits/reversals).
How the billable quantity is snapped before pricing (increment + mode). Omitted, or an empty object, means exact: priced at the metered precision, up to 8 decimal places.
Alert conditions evaluated against the meter's output.
unique_by_fieldEvent field whose distinct values are counted; required for UNIQUE_COUNT.
unit_currency_fieldEvent field carrying the currency when the metric measures money.
unit_labelDisplay label for one metric unit (e.g. "calls").
value_fieldEvent field aggregated; required for SUM/MAX/MIN/AVG/P95/LAST, forbidden for COUNT/UNIQUE_COUNT.
Reset cadence for the aggregation; null aggregates over the whole billing period.
Create a meter › Responses
Created
List threshold-breach events across every meter in the org
The org-wide breach inbox: threshold-breach events across every meter, newest first, keyset-paginated.
query Parameters
limitPage size. Values above the server-side cap are clamped (standard default 50, cap 200; a few document-heavy lists use larger windows). Invalid values fall back to the default.
cursorOpaque continuation token from the previous response's pagination.cursor. Omit for the first page. Cursors are stateless and do not expire, but are only valid for the list and filters that produced them.
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
List threshold-breach events across every meter in the org › Responses
OK
Test one event against a filter tree and event schema
Answers two authoring questions about one event, using the production implementations of both. Would this filter count it: the tree is compiled by the same compiler that builds the invoice's WHERE clause and run against a one-row synthetic usage_events, so every operator, cast and NULL rule is Postgres' own; per-leaf verdicts say which leaf failed and whether it failed because the value differed, because the path is absent, or because the leaf is not evaluable at all (a gt against a non-numeric value, a malformed jsonpath, an invalid regex). Would THIS schema accept it: the supplied JSON Schema is compiled by the same jsonschema compiler ingest uses and validated against the event's metadata. That is narrower than what ingest does, deliberately - ingest validates an event against EVERY meter in the org sharing its metric_key, and this endpoint is asked from the create sheet where the meter does not exist yet, so it takes the schema as a document and answers about that one document. A green answer here means this schema accepts the event; it is not a promise that no OTHER meter's schema rejects it. The tree and the schema travel in the BODY for the same reason: there is no meter id to reference while authoring.
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Test one event against a filter tree and event schema › Request Body
A usage-event-shaped object, read one of two ways. Send a metadata object (optionally with dimension_vars) and it is treated as an ingest BODY: metadata is bound to the usage_events metadata column the filter compiler reads, dimension_vars to its own column, and every other top-level field is envelope no filter can reach. Send a flat object and it IS the metadata — the properties, without the envelope around them. Two or more envelope-only field names (customer_id, product_id, subscription_id, metric_key, price_key, idempotency_key, hold_id, wallet_currency, price/cost fields) also mark it a body; a single one does not, so a bag holding quantity or timestamp stays properties. The response reports which reading was taken in event_interpreted_as.
event_schemaJSON Schema to validate the event against, as a document rather than a meter reference, for the same reason. Omit to skip schema checking.
The predicate to test. Sent in the body rather than referenced by meter id because the question is asked while AUTHORING — in the create sheet where no meter exists yet, and on unsaved draft trees in the filter workbench. An empty tree matches everything.
Test one event against a filter tree and event schema › Responses
OK
List metric keys arriving for no meter
Usage events whose metric_key matches no meter are accepted (201) and aggregated by nothing - a silent revenue leak, because neither a meter's key nor its metric_key can be renamed after creation. This returns the keys that happened to, biggest first, with how many events each swallowed and over what window. Filled hourly by the unmetered_metric_key_scan sweep and bounded at 100 rows per workspace (metric_key is client-supplied and unbounded, so the board keeps the highest-volume findings, not every distinct key). The fix for an entry is to create a meter carrying that exact metric_key: the evaluator reads raw usage_events over the billing period, so events already stored start billing.
query Parameters
include_resolvedAlso return keys a meter now exists for. Those rows are the re-bill worksheet: they say how much usage arrived before the meter did.
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
List metric keys arriving for no meter › Responses
OK
Get a meter
Returns a single org-scoped meter with its aggregation configuration (function, value field, filter tree, window, composed sources).
path Parameters
idMeter UUID
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Get a meter › Responses
OK
Delete a meter
Fails with 409 if any product binding still references this meter.
path Parameters
idMeter UUID
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Delete a meter › Responses
No Content
Update a meter
Merge-patch update. Meters are shared, de-versioned entities: billing-relevant fields (function, value_field, filter_tree, window, unique_by_field, late_event_policy, composed_of) go live immediately and trigger a republish of every ACTIVE product consuming the meter (see /meters/{id}/consumers).
path Parameters
idMeter UUID
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Update a meter › Request Body
New derived-meter formula; {} clears it (the meter becomes a plain one); omitted leaves it unchanged. null is indistinguishable from omission on the wire and also leaves it unchanged.
dedup_key_pathNew dedup JSON-path; omitted leaves it unchanged.
dimensionsNew group-by dimension fields; omitted leaves them unchanged.
event_schemaNew event JSON-Schema; null clears it (ingest validation off); omitted leaves it unchanged. Must compile as a JSON Schema.
New event predicate tree; omitted leaves it unchanged.
functionNew aggregation function; omitted leaves it unchanged. Matched case-insensitively: the server also accepts the all-uppercase and all-lowercase form of each value. The form published here is the canonical one and is what reads back.
late_event_policyNew late-event policy; "" clears it back to unset; omitted leaves it unchanged. Matched case-insensitively: the server also accepts the all-uppercase and all-lowercase form of each value. The form published here is the canonical one and is what reads back.
nameNew human-readable label; omitted leaves it unchanged.
negative_allowedNew negative-value setting; omitted leaves it unchanged.
New billable-quantity rounding (increment + mode); {} clears it back to exact; omitted leaves it unchanged. A snapshot field: saving it republishes every consuming product, like a function or filter edit.
New alert thresholds; [] clears them; omitted leaves them unchanged.
unique_by_fieldNew distinct-count event field; omitted leaves it unchanged.
unit_currency_fieldNew unit-currency event field; omitted leaves it unchanged.
unit_labelNew unit display label; omitted leaves it unchanged.
value_fieldNew aggregated event field; omitted leaves it unchanged.
New aggregation window; omitted leaves it unchanged.
Update a meter › Responses
OK
List bindings for a meter
Returns every product-meter binding that references the meter, across products.
path Parameters
idMeter UUID
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
List bindings for a meter › Responses
OK
List threshold-breach events for a meter
Reads from the shared domain_event_outbox; newest first; capped at the standard pagination limit.
path Parameters
idMeter UUID
query Parameters
limitPage size. Values above the server-side cap are clamped (standard default 50, cap 200; a few document-heavy lists use larger windows). Invalid values fall back to the default.
cursorOpaque continuation token from the previous response's pagination.cursor. Omit for the first page. Cursors are stateless and do not expire, but are only valid for the list and filters that produced them.
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
List threshold-breach events for a meter › Responses
OK
Daily breach counts for a meter
Daily count of threshold-breach events for one meter over a recent window. Powers the threshold-rail sparkline.
path Parameters
idMeter UUID
query Parameters
daysWindow in days (1-90, default 30)
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Daily breach counts for a meter › Responses
OK
List products consuming a meter
The consumer census: every ACTIVE product that bills through this meter (via a live binding, or a composed-source snapshot at the product's head version).
path Parameters
idMeter UUID
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
List products consuming a meter › Responses
OK
Evaluate a meter for one subscription
Runs the meter through the SAME evaluator and the SAME product-version snapshot the invoice pipeline uses, and returns the buckets it produced — per (reset window, price_key, wallet_currency) — plus the windows the period tiled into and the aggregation definition that produced them. Subscription-scoped because usage events are keyed by (product, subscription): the same meter under two products measures two different numbers. billed is what the invoice will carry (the product-version snapshot); head is what the LIVE meter row would carry, which is what you are editing and what no invoice reads until the consuming products are republished. Nothing is persisted and no threshold notification is emitted.
path Parameters
idMeter UUID
Headers
X-Workspace-IdSelects the workspace this request operates in, by workspace UUID or slug (e.g. a sandbox workspace for test integrations). Omitted: the organization's default live workspace. Unknown workspace: 404; workspace outside your organization: 403. Discover workspaces via GET /workspaces.
Workspace UUID or slug.
Evaluate a meter for one subscription › Request Body
subscription_idSubscription to evaluate against. Required: usage_events are keyed by (product, subscription), so "what does this meter compute" is not a well-formed question without one.
fromInclusive start. Defaults to the subscription's current period start.
include_headAlso evaluate the LIVE meter row alongside the billed snapshot, so an unpublished edit is visible as a difference. Defaults to true; set false to skip the second aggregation.
product_idWhich of the subscription's metered products to scope to. Optional when exactly one of them bills through this meter; required when more than one does.
toExclusive end. Defaults to the subscription's current period end.
Evaluate a meter for one subscription › Responses
OK