Usage-Based Billing

Track consumption with meters, ingest usage events, and bill customers based on actual usage

Usage-based billing (UBB) lets you charge customers based on what they actually use rather than a fixed price. RevKeen tracks consumption through meters, aggregates usage over billing periods, and automatically generates invoices with the correct charges. This page covers the complete UBB lifecycle from meter creation to invoice generation.

Start here

Follow the metering quickstart, then inspect usage, configure pricing and checkout, and handle usage webhooks.

The TypeScript snippets below use the published @revkeen/sdk@1.20260822.1541 helper usage.ingest. Initialise client with RevKeenClient as shown in the quickstart. Meter CRUD, event inspection, and balance examples use REST until the regenerated SDK release is verified.

What is Usage-Based Billing?

Instead of charging a flat rate, usage-based billing measures actual consumption and bills accordingly. This model is common for API platforms, SaaS with variable workloads, and service businesses with per-session or per-unit pricing.

Fixed PricingUsage-BasedHybrid
Revenue modelPredictable flat ratePay-per-useBase fee + overage
Best forStandard access plansVariable consumptionIncluded allowance + extra
Example£50/month membership£0.01 per API call£50/mo + £5 per class over 10

Real-world examples: API calls, compute hours, messages sent, storage GB, class bookings, patient appointments, transactions processed.

How It Works

Create Meter → Attach Price → Add to Subscription → Ingest Events → Period Ends → Invoice Generated
  1. Create a Meter -- Define what you're tracking (e.g., "api_calls") and how to aggregate it (sum, count, max)
  2. Attach a Usage Price -- Set the pricing model (per-unit, graduated tiers, volume, or package)
  3. Add to a Subscription -- Wire the meter to a subscription as a metered item
  4. Ingest Events -- Send usage events via API as consumption happens
  5. Period Ends -- RevKeen aggregates all events for the billing period
  6. Invoice Generated -- Usage charges appear as line items on the subscription invoice

You can add metered usage to existing subscriptions -- no need to create new ones. Just add a metered subscription item alongside your existing fixed-price items.

Meters

What is a Meter?

A meter is a named counter that tracks a specific type of usage. Each meter has an event name it listens for, an aggregation method, and optional filter conditions. When usage events arrive, RevKeen matches them to the appropriate meter and updates the running total.

The chain is: Meter → Events → Usage Records → Invoice Line Items.

Aggregation Types

TypeDescriptionExample
sumSum of the value_key property across eventsTotal GB transferred
countCount of matching eventsNumber of API calls
count_uniqueCount of distinct values of the unique_count_keyUnique active users
maxMaximum value of the value_key in the periodPeak concurrent connections
lastLast reported value of the value_keyCurrent storage used

Creating Meters

Via Dashboard

Navigate to Products > Meters in the sidebar

Click + New Meter

Enter a name (e.g., "API Calls") and event name (e.g., api_call)

Select the aggregation type (sum, count, count_unique, max, or last)

For sum, max, or last -- specify the value key (the property in the event that holds the numeric value)

Add filter conditions if needed (e.g., only count events where environment = production)

Save the meter

Via API

await request("/meters", {
  method: "POST",
  body: JSON.stringify({
  name: 'API Calls',
  event_name: 'api_call',
  aggregation: 'count',
  slug: 'api-calls',
  unit_name: 'calls',
  description: 'Tracks API calls per customer',
}),
});

Use the authenticated request helper from the metering quickstart.

Meter Fields

FieldTypeRequiredDescription
namestringYesDisplay name for the meter
event_namestringYesEvent name to match incoming events against
aggregationenumYessum, count, count_unique, max, or last
slugstringNoURL-friendly identifier (unique per merchant)
value_keystringNoProperty key for sum/max/last aggregations
unique_count_keystringNoProperty key for count_unique aggregation
unit_namestringNoDisplay unit (e.g., "calls", "GB", "messages")
filter_conditionsarrayNoConditions to filter which events are counted
carry_forwardbooleanNoWhether to carry the last value into the next period
metadataobjectNoCustom key-value data

The event_name and aggregation settings are immutable after creation. If you need to change these, create a new meter and archive the old one.

Listing, retrieving, and updating meters

# List meters
curl "https://api.revkeen.com/v2/meters" \
  -H "x-api-key: $REVKEEN_API_KEY"

# Get one meter
curl "https://api.revkeen.com/v2/meters/{meter_id}" \
  -H "x-api-key: $REVKEEN_API_KEY"

# Update display fields (event_name and aggregation cannot change)
curl -X PATCH "https://api.revkeen.com/v2/meters/{meter_id}" \
  -H "x-api-key: $REVKEEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Production API Calls", "status": "active" }'

Meter quantities

GET /v2/meters/{id}/quantities returns zero-filled UTC buckets for a window. Query names match Polar (start_timestamp, end_timestamp, interval). interval is hour, day, week, month, or year. Week buckets start Monday (ISO / PostgreSQL date_trunc('week')). Only UTC is supported; any other timezone returns 400. The window cannot produce more than 1,000 buckets.

curl "https://api.revkeen.com/v2/meters/{meter_id}/quantities?start_timestamp=2026-03-01T00:00:00Z&end_timestamp=2026-03-08T00:00:00Z&interval=day&customer_id={customer_id}" \
  -H "x-api-key: $REVKEEN_API_KEY"

Optional filters: customer_id, external_customer_id, subscription_id.

A 200 body looks like:

{
  "object": "meter_quantities",
  "meter_id": "mtr_xxxxxxxx",
  "start_timestamp": "2026-03-01T00:00:00.000Z",
  "end_timestamp": "2026-03-08T00:00:00.000Z",
  "interval": "day",
  "quantities": [
    { "timestamp": "2026-03-01T00:00:00.000Z", "quantity": 12 }
  ]
}

Filter Conditions

Filter conditions let you narrow which events a meter counts. For example, you can create a meter that only counts API calls in the production environment.

Supported operators: eq, neq, gt, gte, lt, lte, in, not_in, contains.

await request("/meters", {
  method: "POST",
  body: JSON.stringify({
  name: 'Production API Calls',
  event_name: 'api_call',
  aggregation: 'count',
  filter_conditions: [
    { key: 'environment', operator: 'eq', value: 'production' },
  ],
}),
});

Use the authenticated request helper from the metering quickstart.

Pricing Models

Per-Unit Pricing

The simplest model: a fixed price multiplied by quantity.

Example: £0.01 per API call. 15,000 calls = £150.00.

Configure this pricing model in Products > Meters, select the meter, and add the price. Meter-price management is not part of the public REST/SDK contract; do not call an invented meters.createPrice SDK method. See pricing controls.

Graduated Tiers

Each tier applies only to the units within that range. Lower tiers are charged at their rate, higher tiers at theirs.

Example:

TierRangeUnit Price
11 – 1,000£0.10
21,001 – 10,000£0.08
310,001+£0.05

Calculation for 15,000 units:

  • Tier 1: 1,000 × £0.10 = £100.00
  • Tier 2: 9,000 × £0.08 = £720.00
  • Tier 3: 5,000 × £0.05 = £250.00
  • Total: £1,070.00

Configure this pricing model in Products > Meters, select the meter, and add the price. Meter-price management is not part of the public REST/SDK contract; do not call an invented meters.createPrice SDK method. See pricing controls.

Volume Tiers

The total quantity determines a single price that applies to all units. Unlike graduated tiers, you don't mix rates.

Same tier table, different calculation for 15,000 units:

  • Total lands in tier 3 → 15,000 × £0.05 = £750.00

Graduated charges each tier separately. Volume picks one tier for everything. Graduated typically results in a higher total for the same usage.

Configure this pricing model in Products > Meters, select the meter, and add the price. Meter-price management is not part of the public REST/SDK contract; do not call an invented meters.createPrice SDK method. See pricing controls.

Package Pricing

Charge per block of units. Partial blocks are charged in full.

Example: £5.00 per 100 API calls.

  • 250 calls = 3 packages × £5.00 = £15.00

Configure this pricing model in Products > Meters, select the meter, and add the price. Meter-price management is not part of the public REST/SDK contract; do not call an invented meters.createPrice SDK method. See pricing controls.

Flat Fee + Usage (Base Charge)

Add a minimum monthly charge on top of usage. The flat fee is charged regardless of consumption.

Example: £20/month base + £0.05 per message.

  • 500 messages = £20.00 + (500 × £0.05) = £45.00

Configure this pricing model in Products > Meters, select the meter, and add the price. Meter-price management is not part of the public REST/SDK contract; do not call an invented meters.createPrice SDK method. See pricing controls.

Connecting Meters to Subscriptions

This is the critical bridge between tracking usage and billing for it. The wiring is: meter → usage price → subscription item.

Create a meter that tracks the usage you want to bill for (e.g., API calls, class bookings)

Attach a usage price to the meter with your chosen pricing model

Add the meter as a subscription item on a product -- this links the meter to a customer's billing cycle

Ingest events as consumption happens -- RevKeen aggregates usage automatically

At renewal, RevKeen calculates the usage charge and adds it as a line item on the invoice alongside any fixed charges

In the dashboard, attach the fixed price and the metered price to the customer subscription. Verify the customer, billing period, allowance, and selected usage price before activation. Use only fields declared by the public subscription API when automating this step.

Metered products don't charge upfront -- they accumulate usage throughout the billing period and add the calculated charge to the invoice at renewal.

Hybrid Billing: Subscription + Usage

The most common UBB pattern for service businesses. Combine a fixed subscription fee with metered overage charges.

Complete Example: Fitness Studio

A fitness studio charges £50/month membership that includes 10 classes. Additional classes cost £5 each.

Create the fixed product: "Studio Membership" at £50/month

Create a meter: class_booking with aggregation count

Create a graduated usage price on the meter:

TierRangeUnit Price
10 – 10£0.00 (included in membership)
211+£5.00 per class

Create the subscription with both items:

In the dashboard, attach the fixed price and the metered price to the customer subscription. Verify the customer, billing period, allowance, and selected usage price before activation. Use only fields declared by the public subscription API when automating this step.

Send events when a class is booked:

await client.usage.ingest({
  events: [{
    name: 'class_booking',
    customer_id: 'cus_xxxxxxxx',
    quantity: 1,
    idempotency_key: 'class_booking_cus_xxxxxxxx_20260315',
  }],
});

At billing period end, invoice shows:

Line ItemAmount
Studio Membership£50.00
Class bookings (14 classes, 10 included)£20.00 (4 × £5)
Total£70.00

The £0.00 first tier is key -- it represents the included allowance. Without it, every class would be charged from the first booking.

Ingesting Usage Events

Event Schema

FieldTypeRequiredDescription
namestringYesEvent name matching a meter's event_name
customer_idstringNoRevKeen customer ID
external_customer_idstringNoYour system's customer ID (alternative to customer_id)
subscription_idstringNoAssociate with a specific subscription
meter_idstringNoTarget a specific meter (if multiple meters share the same event name)
quantitynumberNoNumeric value for the event (default: 1)
timestampISO 8601NoWhen the event occurred (default: now)
idempotency_keystringYesUnique key for safe retries. Required on every ingested event.
metadataobjectNoCustom properties (filterable by meter filter conditions)

Sending Events

// Single event
await client.usage.ingest({
  events: [{
    name: 'api_call',
    customer_id: 'cus_xxxxxxxx',
    quantity: 1,
    idempotency_key: 'evt_20260315_abc123',
    metadata: { endpoint: '/v2/users', region: 'eu-west-1' },
  }],
});

Batch Ingestion

Send up to 1,000 events in a single request:

await client.usage.ingest({
  events: [
    { name: 'api_call', customer_id: 'cus_aaa', quantity: 1, idempotency_key: 'api_call_aaa_1' },
    { name: 'api_call', customer_id: 'cus_bbb', quantity: 3, idempotency_key: 'api_call_bbb_1' },
    { name: 'storage_used', customer_id: 'cus_aaa', quantity: 1024, idempotency_key: 'storage_aaa_1' },
    // ... up to 1,000 events, each with its own idempotency_key
  ],
});

Retrieving events

POST /v2/usage-events ingests a batch (up to 1,000). GET /v2/usage-events lists events for the merchant. GET /v2/usage-events/{id} returns one event. Unknown ids and ids that belong to another merchant both return 404 — the lookup is merchant-scoped.

curl "https://api.revkeen.com/v2/usage-events/{event_id}" \
  -H "x-api-key: $REVKEEN_API_KEY"

Idempotency

Every ingested event must include an idempotency_key. Reusing a key returns a per-event duplicate result and does not create another billable event. Inspect every result: batches can return partial success (207). Keep the same key and payload when retrying the same business event; never generate a fresh timestamp-based key on retry.

// Safe to retry on network failure
await client.usage.ingest({
  events: [{
    name: 'api_call',
    customer_id: 'cus_xxxxxxxx',
    idempotency_key: 'booking_7c0de8_attended',
  }],
});

Late-Arriving Events

Preserve the original event timestamp when sending late events. Inspect the event, its billing matches, and invoice-line provenance to determine the period actually billed. Do not assume a late event has changed an already-finalised invoice or automatically moved to the next invoice.

Events at the exact period boundary belong to the next period. Periods use half-open intervals: [start, end).

Usage Records and Billing Periods

How Usage Records Work

RevKeen maintains one usage record per (meter, customer, billing period). As events arrive, the record's aggregated value is updated in real-time. At the end of the billing period, the record is finalized and locked for invoicing.

Usage Record Statuses

StatusDescription
pendingActive billing period. Accumulating events. Value can still change.
finalizedBilling period ended. Value is locked. Ready for invoicing.
invoicedInvoice has been generated with this usage record's charges.
voidedRecord was voided (e.g., subscription cancelled mid-period).

Viewing Current Usage

Via Dashboard

Navigate to Products > Meters, click on a meter to see real-time aggregated usage per customer, daily usage charts, and recent events.

Via API

Use GET /v2/usage/balance with the filters in its API reference. For dashboard-style consumption and allowance, use customer meters.

Time-bucketed totals use GET /v2/meters/{id}/quantities (see Meter quantities). Polar has no separate aggregate-events route; RevKeen's dashboard dry-run and aggregate helpers are not part of the public merchant API.

Per-customer meter totals (Polar customer_meters) are GET /v2/customer-meters?customer_id={id} and GET /v2/customer-meters/{customer_id}/{meter_id}. These are computed live from usage events — there is no separate customer-meters table.

curl "https://api.revkeen.com/v2/customer-meters?customer_id={customer_id}" \
  -H "x-api-key: $REVKEEN_API_KEY"

Billing Period Alignment

Usage periods align with subscription billing cycles. If a customer's subscription renews on the 15th of each month, the usage period runs from the 15th to the 14th of the following month.

Usage Invoices

When a billing period ends, RevKeen generates an invoice that includes usage charges as line items. Each meter generates a separate line item showing:

  • Meter name
  • Billing period (start and end dates)
  • Total quantity consumed
  • Unit price or tier breakdown
  • Calculated charge

For tiered pricing, the line item details include a breakdown of how many units fell into each tier and the charge for each.

Usage invoices are generated automatically when the billing period ends. For hybrid subscriptions, usage charges appear alongside fixed charges on the same invoice.

Dashboard Guide

Meters List

Navigate to Products > Meters in the sidebar. The meters list shows all your meters with their event counts, status, and aggregation type.

Meter Detail

Click on any meter to see:

  • Usage chart -- Daily, weekly, or monthly aggregation of usage over time
  • Customer breakdown -- Usage per customer, sorted by consumption
  • Recent events -- Latest events matched to this meter
  • Pricing configuration -- Attached usage prices and tier configuration

Usage Analytics

A dedicated usage-analytics dashboard (revenue-by-meter, margin analysis) is not live. Treat Products → Meters as the operator surface: usage charts, customer breakdown, recent events, and pricing on each meter. Do not send merchants to a "usage billing insights" page as if it shipped.

Customer-facing balances use GET /v2/usage/balance.

Best Practices

  1. Always include idempotency keys -- Safe to retry on network failure without double-counting events.

  2. Send events in real-time -- Don't batch events to end-of-day. Real-time ingestion gives your customers accurate usage dashboards and prevents surprises at billing time.

  3. Use filter conditions for segmentation -- Add properties like region, environment, or plan_tier to events, then create separate meters with filter conditions for granular billing and analytics.

  4. Start with per-unit pricing -- The simplest model to reason about. Graduate to tiered pricing once you understand your customers' usage patterns.

  5. Use graduated tiers with a £0.00 first tier -- This creates "included allowance" pricing (e.g., 100 free API calls, then £0.01 each after).

  6. Use the usage balance endpoint for customer dashboards -- Build customer-facing usage displays with GET /v2/usage/balance so customers can monitor their consumption in real-time.

  7. Validate a sample event in the dashboard first -- On Products → Meters, use Send test event to check meter filters and customer attribution without creating billable usage.

Webhooks

Subscribe to usage lifecycle events on a webhook endpoint. Delivery is at-least-once — dedupe on data.dedupeKey.

EventWhen it fires
usage.event.ingestedEvent accepted. High-volume — opt in with narrow filters.
usage.event.rejectedValidation failed. Not billable.
usage.event.excludedMeter filter excluded the event.
usage.event.quarantinedNo single active meter matched.
usage.cap.exceededHard cap refused the event. Nothing persisted, nothing charged.
usage.threshold.reachedA configured usage threshold was crossed.
usage.period_finalizedThe billing period closed.
usage.invoice.createdA usage invoice was generated.
meter.created / meter.updated / meter.archivedMeter lifecycle.

Trace a usage invoice line back to the events that produced it with GET /v2/invoice-line-items/{id}/usage-events.

API reference for this workflow

On this page