RevKeen Docs

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 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 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

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.

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:

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
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:

TierRangeUnit Price
10 – 10£0.00 (included in membership)
211+£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 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_keystringNoUnique key for safe retries
metadataobjectNoCustom 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

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 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_minor is set on meters)

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 with dry runs first -- Use POST /v2/usage-events/dry-run to verify your event schema matches your meters before sending production events.

On this page