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.
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 Pricing | Usage-Based | Hybrid | |
|---|---|---|---|
| Revenue model | Predictable flat rate | Pay-per-use | Base fee + overage |
| Best for | Standard access plans | Variable consumption | Included 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- Create a Meter -- Define what you're tracking (e.g., "api_calls") and how to aggregate it (sum, count, max)
- Attach a Usage Price -- Set the pricing model (per-unit, graduated tiers, volume, or package)
- Add to a Subscription -- Wire the meter to a subscription as a metered item
- Ingest Events -- Send usage events via API as consumption happens
- Period Ends -- RevKeen aggregates all events for the billing period
- 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
| Type | Description | Example |
|---|---|---|
sum | Sum of the value_key property across events | Total GB transferred |
count | Count of matching events | Number of API calls |
count_unique | Count of distinct values of the unique_count_key | Unique active users |
max | Maximum value of the value_key in the period | Peak concurrent connections |
last | Last reported value of the value_key | Current storage used |
Creating Meters
Via Dashboard
Navigate to Usage > 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
const meter = await revkeen.meters.create({
name: 'API Calls',
event_name: 'api_call',
aggregation: 'count',
slug: 'api-calls',
unit_name: 'calls',
description: 'Tracks API calls per customer',
});meter = client.meters.create(
name="API Calls",
event_name="api_call",
aggregation="count",
slug="api-calls",
unit_name="calls",
description="Tracks API calls per customer",
)curl -X POST "https://api.revkeen.com/v2/meters" \
-H "x-api-key: rk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "API Calls",
"event_name": "api_call",
"aggregation": "count",
"slug": "api-calls",
"unit_name": "calls"
}'Meter Fields
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name for the meter |
event_name | string | Yes | Event name to match incoming events against |
aggregation | enum | Yes | sum, count, count_unique, max, or last |
slug | string | No | URL-friendly identifier (unique per merchant) |
value_key | string | No | Property key for sum/max/last aggregations |
unique_count_key | string | No | Property key for count_unique aggregation |
unit_name | string | No | Display unit (e.g., "calls", "GB", "messages") |
filter_conditions | array | No | Conditions to filter which events are counted |
carry_forward | boolean | No | Whether to carry the last value into the next period |
metadata | object | No | Custom 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.
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.
const meter = await revkeen.meters.create({
name: 'Production API Calls',
event_name: 'api_call',
aggregation: 'count',
filter_conditions: [
{ key: 'environment', operator: 'eq', value: 'production' },
],
});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.
// Create a per-unit usage price
const price = await revkeen.meters.createPrice(meter.id, {
pricing_model: 'per_unit',
unit_amount_minor: 1, // £0.01
currency: 'GBP',
});Graduated Tiers
Each tier applies only to the units within that range. Lower tiers are charged at their rate, higher tiers at theirs.
Example:
| Tier | Range | Unit Price |
|---|---|---|
| 1 | 1 – 1,000 | £0.10 |
| 2 | 1,001 – 10,000 | £0.08 |
| 3 | 10,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
const price = await revkeen.meters.createPrice(meter.id, {
pricing_model: 'graduated',
currency: 'GBP',
tiers: [
{ first_unit: 1, last_unit: 1000, unit_amount_minor: 10 },
{ first_unit: 1001, last_unit: 10000, unit_amount_minor: 8 },
{ first_unit: 10001, last_unit: null, unit_amount_minor: 5 },
],
});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.
const price = await revkeen.meters.createPrice(meter.id, {
pricing_model: 'volume',
currency: 'GBP',
tiers: [
{ first_unit: 1, last_unit: 1000, unit_amount_minor: 10 },
{ first_unit: 1001, last_unit: 10000, unit_amount_minor: 8 },
{ first_unit: 10001, last_unit: null, unit_amount_minor: 5 },
],
});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
const price = await revkeen.meters.createPrice(meter.id, {
pricing_model: 'package',
package_size: 100,
unit_amount_minor: 500, // £5.00 per package
currency: 'GBP',
});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
const price = await revkeen.meters.createPrice(meter.id, {
pricing_model: 'per_unit',
unit_amount_minor: 5, // £0.05 per message
flat_fee_minor: 2000, // £20.00 base charge
currency: 'GBP',
});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
// Create a subscription with both fixed and metered items
const subscription = await client.subscriptions.create({
customerId: 'cus_xxxxxxxx',
items: [
{
priceId: 'price_fixed_monthly', // Fixed £50/month
quantity: 1,
},
{
meterId: 'mtr_api_calls', // Metered usage
priceId: 'price_per_unit',
},
],
});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:
| Tier | Range | Unit Price |
|---|---|---|
| 1 | 0 – 10 | £0.00 (included in membership) |
| 2 | 11+ | £5.00 per class |
Create the subscription with both items:
const subscription = await client.subscriptions.create({
customerId: 'cus_xxxxxxxx',
items: [
{ priceId: 'price_studio_membership' }, // £50/month fixed
{ meterId: 'mtr_class_booking', priceId: 'price_class_usage' }, // Usage-based
],
});Send events when a class is booked:
await revkeen.usageEvents.ingest({
events: [{
name: 'class_booking',
customer_id: 'cus_xxxxxxxx',
quantity: 1,
}],
});At billing period end, invoice shows:
| Line Item | Amount |
|---|---|
| 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
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Event name matching a meter's event_name |
customer_id | string | No | RevKeen customer ID |
external_customer_id | string | No | Your system's customer ID (alternative to customer_id) |
subscription_id | string | No | Associate with a specific subscription |
meter_id | string | No | Target a specific meter (if multiple meters share the same event name) |
quantity | number | No | Numeric value for the event (default: 1) |
timestamp | ISO 8601 | No | When the event occurred (default: now) |
idempotency_key | string | No | Unique key for safe retries |
metadata | object | No | Custom properties (filterable by meter filter conditions) |
Sending Events
// Single event
await revkeen.usageEvents.ingest({
events: [{
name: 'api_call',
customer_id: 'cus_xxxxxxxx',
quantity: 1,
idempotency_key: 'evt_20260315_abc123',
metadata: { endpoint: '/v2/users', region: 'eu-west-1' },
}],
});client.usage_events.ingest(events=[
{
"name": "api_call",
"customer_id": "cus_xxxxxxxx",
"quantity": 1,
"idempotency_key": "evt_20260315_abc123",
"metadata": {"endpoint": "/v2/users", "region": "eu-west-1"},
}
])curl -X POST "https://api.revkeen.com/v2/usage-events" \
-H "x-api-key: rk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"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 revkeen.usageEvents.ingest({
events: [
{ name: 'api_call', customer_id: 'cus_aaa', quantity: 1 },
{ name: 'api_call', customer_id: 'cus_bbb', quantity: 3 },
{ name: 'storage_used', customer_id: 'cus_aaa', quantity: 1024 },
// ... up to 1,000 events
],
});Dry Run
Validate events without persisting them. Use this to test your event schema before production ingestion:
const result = await revkeen.usageEvents.dryRun({
events: [{
name: 'api_call',
customer_id: 'cus_xxxxxxxx',
quantity: 1,
}],
});
console.log(result.summary);
// { would_ingest: 1, would_skip: 0, would_fail: 0 }Idempotency
Including an idempotency_key on events makes ingestion safe to retry. If you send the same key twice, the second request returns a 409 Conflict and the event is not double-counted.
// Safe to retry on network failure
await revkeen.usageEvents.ingest({
events: [{
name: 'api_call',
customer_id: 'cus_xxxxxxxx',
idempotency_key: `${customerId}_${Date.now()}`,
}],
});Late-Arriving Events
When an event arrives after the billing period has been finalized, RevKeen automatically rolls it into the next billing period. The event is preserved with its original timestamp for auditability, but the charge appears on 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
| Status | Description |
|---|---|
pending | Active billing period. Accumulating events. Value can still change. |
finalized | Billing period ended. Value is locked. Ready for invoicing. |
invoiced | Invoice has been generated with this usage record's charges. |
voided | Record was voided (e.g., subscription cancelled mid-period). |
Viewing Current Usage
Via Dashboard
Navigate to Usage > Meters, click on a meter to see real-time aggregated usage per customer, daily usage charts, and recent events.
Via API
// Get current usage balance for a customer
const balance = await revkeen.usageBalance.get({
meter_id: 'mtr_xxxxxxxx',
customer_id: 'cus_xxxxxxxx',
});
console.log(`Current usage: ${balance.meters[0].current_value}`);
console.log(`Estimated charge: £${balance.meters[0].estimated_amount_minor / 100}`);// Get aggregated usage over a time range
const aggregate = await revkeen.usageEvents.aggregate('mtr_xxxxxxxx', {
customer_id: 'cus_xxxxxxxx',
start_date: '2026-03-01T00:00:00Z',
end_date: '2026-03-31T23:59:59Z',
});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 Usage > 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
The usage analytics dashboard shows:
- Revenue by meter
- Top customers by usage
- Usage trends over time
- Margin analysis (when
default_cost_per_unit_minoris set on meters)
Best Practices
-
Always include idempotency keys -- Safe to retry on network failure without double-counting events.
-
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.
-
Use filter conditions for segmentation -- Add properties like
region,environment, orplan_tierto events, then create separate meters with filter conditions for granular billing and analytics. -
Start with per-unit pricing -- The simplest model to reason about. Graduate to tiered pricing once you understand your customers' usage patterns.
-
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).
-
Use the usage balance endpoint for customer dashboards -- Build customer-facing usage displays with
GET /v2/usage-balanceso customers can monitor their consumption in real-time. -
Validate with dry runs first -- Use
POST /v2/usage-events/dry-runto verify your event schema matches your meters before sending production events.
Related
- Products and Pricing -- Define products with usage-based prices
- Subscriptions -- Attach metered items to subscriptions
- Invoices and Orders -- How usage charges appear on invoices
- Usage API Reference -- Full API reference for meters, events, and pricing