Billing meters
A billing meter is the definition for one billable thing your customers do. It says: when a customer_signup event lands, treat it as a count; when an api_call event lands, sum up the value field; bill at $0.001 per unit. Every BillingMetricEvent routes to a billing meter via event_name, and the meter governs how the events aggregate and price out.
Naming note: BillingMetric in the code
The underlying model is BillingMetric, not BillingMeter — the slug here uses "meters" because that's the merchant-facing term, but if you go reading frame's source you'll find the class named BillingMetric. Same primitive; different surface naming.
Fields
curl --request POST \
--url https://api.framepayments.com/v1/billing/metering \
--header "Authorization: Bearer $FRAME_SECRET_KEY" \
--header 'Content-Type: application/json' \
--data '{
"event_name": "api_call",
"display_name": "API calls",
"description": "Billed per API call across all endpoints",
"aggregation": "sum",
"value": 0.001,
"active": true
}'
The interesting fields:
event_name— the routing key. Must be unique per merchant. Every BillingMetricEvent logged with thisevent_nameaggregates into this meter. Max 255 chars.display_name— human-facing label, shown on invoices and the dashboard. Max 255 chars.description— long-form description. Max 255 chars.aggregation—sum,count,count_unique,average,time_duration, ormarkup_percentage. See metering.value— the unit price. Forsum/count/count_unique, this is the price per unit. Fortime_duration, price per second. Formarkup_percentage, the base markup multiplier.markup_percentage— used bymarkup_percentageaggregation; supplementsvalue.active— boolean flag, defaults to true. Set false to suspend new event acceptance without deleting.metadata— k/v pairs for your internal tracking.
Lifecycle
Billing meters carry a status state machine:
active(initial) — accepting events; aggregations compute; invoices generate.inactive— paused. Events with thisevent_nameare rejected; existing aggregated data and invoices stand.deleted— soft-deleted. Terminal. Historical data is retained for reporting; no new events.
Transitions:
active ↔ inactiveviaactivate/deactivate.active | inactive → deletedviadiscard.
Deactivate when you want to stop billing for a usage type without losing historical aggregations — common at end-of-life for a feature or during a billing-model migration.
Choosing an aggregation
The right aggregation depends on what you're billing for:
| Pattern | Aggregation | value per event | Notes |
|---|---|---|---|
| Per API call | sum | 1 | Same as count if event value is always 1 |
| Per API call, weighted | sum | call cost (1 for cheap, 5 for expensive) | Lets a single endpoint bill differently |
| Per file uploaded | count | (ignored) | Event arrival = +1 |
| Per active user | count_unique | the user ID | Distinct user IDs per cycle |
| Average response latency | average | the latency in ms | For analytics or quality-of-service tiers |
| Per minute of compute | time_duration | (use start_time/end_time on event) | Sums elapsed wall-clock time |
| Marketplace fee on volume | markup_percentage | transaction amount | Bills (volume × markup %) |
Pick once at meter creation. Aggregation isn't editable after the meter has events flowing through it — you'd need to create a new meter with a different event_name and migrate the event-logging code on your side.
How meters compose with credits
A merchant can attach a meter to billing credits so that usage events deplete the customer's balance instead of (or before) generating an invoice. The composition is product-driven: a Product can declare billing_credits (an allotment), and the BillingProductDetail attached to it sets the billing_type (prepaid, postpaid, subscription, true_up_charge, rollover_subscription). When usage events route to the meter, Frame checks whether the paying customer has active credits scoped to that product and decrements accordingly.
For pure usage-based pricing without credits, skip the Product layer — meters aggregate, the report API surfaces the totals, and your platform handles invoicing on whatever cadence you want.
Webhooks
Meter activity surfaces through the broader billing webhook set (see metering — webhooks). The relevant ones for meter-level signals:
billing.subscriber_usage— periodic snapshot of customer's usage across meters.billing.subscriber_overage— usage exceeded a threshold.
Meter-state transitions (active → inactive, etc.) don't currently fire dedicated webhooks. If you need to detect a deactivation event externally, poll the meter or rely on application-side notifications.
Reporting
To see what a meter has accumulated:
GET /v1/billing/report/event/<event_name>?customer_id=<id>— usage for one meter, one customer, this cycle.GET /v1/billing/report/events?customer_id=<id>— all meters' usage for a customer, itemized.GET /v1/billing/report/customer?customer_id=<id>— total billable across all meters.
These power customer-facing usage dashboards and your platform's internal billing health monitoring.
Gotchas
Symptom: you tried to create two meters with the same event_name and got a uniqueness error. Why: event_name is unique per merchant. Fix: if you're trying to split a meter into two (e.g., free-tier and paid-tier), use different event_names and have your event-logging code route to the right one.
Symptom: meter is active but events aren't being aggregated. Why: most often, the event_name on incoming events doesn't match the meter's event_name exactly (typo, casing). Fix: the routing is exact-match; verify by listing recent events via the metering events endpoint and checking what event_names are present.
Symptom: you tried to delete a meter and historical reports broke. Why: discard is soft-delete; historical data is retained but the meter no longer accepts events. If your reports query by active meters only, deleted meters won't appear. Fix: if you need the historical aggregation in your dashboard, query without the active filter or use deactivate instead of discard so the meter stays queryable.
Symptom: value on a meter feels under-specified for your pricing model (e.g., tiered pricing). Why: a meter's value is a single rate. Frame doesn't model tiered pricing within a single meter. Fix: either compute the tiered price client-side and pass it as value on each event (with aggregation: sum), or model tiers as separate meters with application-side routing logic.
Reference
For the full API surface, see POST/v1/billing/metering.