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 this event_name aggregates 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.
  • aggregationsum, count, count_unique, average, time_duration, or markup_percentage. See metering.
  • value — the unit price. For sum/count/count_unique, this is the price per unit. For time_duration, price per second. For markup_percentage, the base markup multiplier.
  • markup_percentage — used by markup_percentage aggregation; supplements value.
  • 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 this event_name are rejected; existing aggregated data and invoices stand.
  • deleted — soft-deleted. Terminal. Historical data is retained for reporting; no new events.

Transitions:

  • active ↔ inactive via activate / deactivate.
  • active | inactive → deleted via discard.

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:

PatternAggregationvalue per eventNotes
Per API callsum1Same as count if event value is always 1
Per API call, weightedsumcall cost (1 for cheap, 5 for expensive)Lets a single endpoint bill differently
Per file uploadedcount(ignored)Event arrival = +1
Per active usercount_uniquethe user IDDistinct user IDs per cycle
Average response latencyaveragethe latency in msFor analytics or quality-of-service tiers
Per minute of computetime_duration(use start_time/end_time on event)Sums elapsed wall-clock time
Marketplace fee on volumemarkup_percentagetransaction amountBills (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 (activeinactive, 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.