Easy Labs
BillingGuides

Meter usage

Report metered usage against a subscription item and reconcile it at the end of a period.

Goal

Bill a customer for variable consumption — API calls, GB ingested, minutes streamed — by reporting usage events against a subscription item with a metered price. The platform aggregates the reports, generates an invoice line at period close, and exposes a reconciliation view so you can match what you reported against what was billed.

Prerequisites

  • A sandbox or production API key
  • @easylabs/node installed
  • A Price created with pricing_model: "metered"
  • A Subscription with at least one item using that price

Implementation

1. Report usage as it happens

Each event references the subscription_item_id and a quantity. Pass action: "increment" (the default) to add to the bucket, or action: "set" to overwrite the period total. Use idempotency_key to make retries safe.

import { createClient } from "@easylabs/node";

const easy = await createClient({ apiKey: process.env.EASY_API_KEY! });

await easy.reportSubscriptionUsage("sub_01HXXXXXXXXXXX", {
  subscription_item_id: "si_01HXXXXXXXXXXX",
  quantity: 1,
  action: "increment",
  timestamp: new Date().toISOString(),
  idempotency_key: `evt_${eventId}`,
});

For high-volume metering, batch on your side and submit in larger chunks rather than one event per API call. quantity is an integer; pre-aggregate fractional units to whole ones at the resolution you're billing.

2. Read the running total

getSubscriptionUsageSummary returns the current period's accumulated usage by item. Use as_of to view a historical snapshot or from/to to bound the window.

const { data: summary } = await easy.getSubscriptionUsageSummary(
  "sub_01HXXXXXXXXXXX",
  { subscription_item_id: "si_01HXXXXXXXXXXX" },
);

Surface this in your customer-facing dashboard so usage is visible before the invoice closes.

3. Reconcile at period end

When the subscription rolls over, the engine generates an invoice line for the metered totals. getSubscriptionUsageReconciliation returns the reported-vs-billed comparison for a closed period — call it from a scheduled job and alert if the deltas exceed your tolerance.

const { data: recon } = await easy.getSubscriptionUsageReconciliation(
  "sub_01HXXXXXXXXXXX",
  {
    subscription_item_id: "si_01HXXXXXXXXXXX",
    period_start: "2026-04-01T00:00:00Z",
    period_end: "2026-05-01T00:00:00Z",
  },
);

Listen for invoice.created and invoice.finalized to know when to run reconciliation.

Tradeoffs

  • increment vs. setincrement is the default and fits event streams. set is the right call only when you have an authoritative period total (e.g. nightly batch from a data warehouse) and you can guarantee one report per period.
  • Latency — usage reports are not invoiced in real time. Reports flushed within the period are billed; reports backfilled after the period closes go on the next invoice (or fail to record, depending on configuration). Bound your reporting lag to less than the billing interval.
  • Idempotency keys are per subscription item, not global. A retry with the same idempotency_key is safely deduped; reusing the key for a new event will silently drop it.

On this page