DEV Community

Cover image for How to Track API Usage for Billing in Real Time
Flexprice
Flexprice

Posted on Originally published at flexprice.io

How to Track API Usage for Billing in Real Time

Tracking API usage in real time reads like a counter problem. It stops being one the first time a customer asks why the invoice says 1.4M calls when their logs say 1.2M.

Most teams start with counters and log lines, then discover they need event capture, deduplication, aggregation windows, credit enforcement, rating, and reconciliation. That is analytics, billing, and accounting rebuilt at once, and each piece has its own failure mode.

With tokenised APIs and AI workloads, billing runs continuously rather than monthly. Missed events are lost revenue. Late aggregation is lost trust.

Short answer

Real-time API usage billing runs as a pipeline: emit an event at the API layer with an idempotency key, buffer through a queue, deduplicate and validate on ingest, aggregate on fixed windows, check credits and quotas before allowing more consumption, rate against versioned pricing rules, invoice with line items that trace back to raw events, and monitor every stage.

Step 1: define the billable unit

For an AI API this is tokens or GPU minutes. For an integration platform it might be API calls, workflows executed, or records processed. Pick the unit that matches how the customer perceives value rather than the one that is easiest to count.

Teams commonly start with requests per second because the counter already exists, then find out customers reason about minutes of compute. The metric decides how transparent the pricing feels, and changing it later means reworking billing logic, dashboards, and every plan built on it.

Step 2: emit at the source

The most reliable usage data comes from the layer that generates it. Emit the billing event the moment a request completes successfully or a billable action is confirmed.

Emitting after a job queue drains instead of at the request layer is a common early mistake, because failures downstream of the queue never produce an event and never get billed.

Keep emission off the critical path. Background jobs, a message queue, or fire-and-forget HTTP all work. The request must complete even when the metering service is slow.

curl -X POST https://api.cloud.flexprice.io/v1/events \

  -H "x-api-key: $FLEXPRICE_API_KEY" \

  -H "Content-Type: application/json" \

  -d '{

    "event_id": "evt_req_8814a2",

    "event_name": "api_request",

    "external_customer_id": "acct_2213",

    "timestamp": "2026-09-11T09:14:33Z",

    "properties": {

      "endpoint": "/v1/completions",

      "model": "llama-3-70b",

      "input_tokens": 1840,

      "output_tokens": 612

    }

  }'
Enter fullscreen mode Exit fullscreen mode

Only event_name and external_customer_id are required. The caller generates event_id, which is what makes a retry safe: events are idempotent on event id, so a retry or a replay converges to the same state instead of a second charge. A successful ingest returns 202 with the accepted event id, because processing is asynchronous.

For streaming or long-running work, emit on both start and end, or send periodic heartbeats that the aggregator merges into total compute time.

At volume, batch. POST /v1/events/bulk takes an events array of the same objects, and the two endpoints have separate rate limits: 1000 requests per minute for single events, 100 per minute for bulk with up to 1000 events per request. Batching is the difference between hitting a limit at a thousand events a minute and clearing a hundred thousand.

Step 3: ingestion and buffering

Events need a safe path before any billing logic touches them.

App -> Queue -> Ingestion service -> Store

The queue is a shock absorber. If the billing or analytics side slows down, events wait rather than disappear. Kafka, Pub/Sub, and Redis Streams all provide ordering and replay, which is what you want when reprocessing a window.

Validate before storing. Required fields present, timestamps inside expected bounds, quantities numeric. Anything invalid goes to a dead letter queue for inspection, never silently dropped.

Deduplicate on the idempotency key. A SETNX in Redis with a short TTL, or a unique constraint in the database, both work.

def record(event):

    key = f"dedupe:{event['event_id']}"

    if not redis.set(key, 1, nx=True, ex=86400):

        return  # already processed

    queue.publish(event)
Enter fullscreen mode Exit fullscreen mode

The TTL should comfortably exceed the longest plausible retry window. Flexprice ingestion endpoints accept concurrent fire-and-forget events and deduplicate on the same key, documented in event ingestion.

Step 4: aggregation

Raw events are not a bill. They become one when grouped into totals per customer, per meter, per window.

Most systems aggregate on fixed tumbling windows, hourly or daily. Two details cause most of the pain here. The window boundary has to align with the billing period, or the last partial window gets counted in the wrong cycle. And the timezone has to be consistent across every meter, because a customer billed on a local calendar month and metered in UTC will always be slightly wrong.

Choose the aggregation function per meter: sum for cumulative consumption, count for requests, count unique for distinct entities, max for peaks, latest for current state. The aggregation reference covers each.

Step 5: credits, quotas, and entitlements

Aggregated usage tells you what happened. These three decide whether more is allowed.

A **credit **is a prepaid unit of value, for example one credit equals 1,000 tokens. A **quota **is a fixed limit inside a billing cycle, like 100K calls per month. An **entitlement **defines what the plan includes and whether those calls are paid, capped, or pooled across users.

The check has to be fast, so most teams keep this state in Redis or another low-latency store: before processing, verify the balance allows it, deduct atomically if so, throttle or queue if not, then reconcile against the database on a schedule.

Soft caps warn and keep serving. Hard caps stop immediately. For high-value workloads, soft caps paired with an overage rate usually beat both, because stopping production traffic to protect a quota is rarely the right trade.

Flexprice handles this through the wallet system, where each wallet holds recurring or one-time grants with expiry rules and entitlement grants define which events consume which credits.

Step 6: rating

Rating turns verified usage into money by applying pricing rules to each metric.

Per unit. One flat price per call or token.

Tiered. The rate changes as usage crosses thresholds, each tier priced separately.

Volume. The whole quantity prices at the rate its total qualifies for.

Hybrid. A base subscription plus metered overage.

Model-based. Modifiers by model, region, or priority tier.

Rating must be deterministic. The same input has to produce the same charge, including after the price list changes. That means versioning pricing rules and storing which version rated each line, so a bill can be recomputed exactly as it was rated at the time.

This matters the first time a plan changes mid-cycle. Replaying the same events through the correct pricing version produces a defensible number. Recomputing against today's prices produces an argument. Flexprice versions price updates and supports price overrides per customer without cloning the plan.

Step 7: invoicing and reconciliation

An invoice line item should carry the usage period, the meter, the pricing version, the quantity, the amount, and an identifier that traces back to raw events.

That chain is the whole point:

Invoice -> Line item -> Rated record -> Usage event

Reconciliation verifies three things before a period closes: the sum of rated usage equals the invoice total, no events are unrated or duplicated, and delayed events have been processed.

Handle late or disputed events as replays rather than manual edits. Mutating historical data destroys the audit trail and makes the next dispute unanswerable. Invoice calculation documents how charges are assembled.

Calendar billing, where every customer shares the same dates, simplifies accounting. Anniversary billing, keyed to each customer's start date, is more flexible and requires aggregation windows aligned per subscription.

Step 8: observability

A billing pipeline that fails silently costs money invisibly. Five metrics catch most of it:

Ingestion lag. Time from event emission to appearing in storage.

Duplicate rate. Share of events blocked by idempotency. A jump means a retry bug upstream.

Event drop rate. Missing or invalid records per interval.

Reconciliation gap. Difference between aggregated usage and billed totals.

Burn rate deviation. Sudden change in a customer's consumption, which is either an incident or an upsell.

Export these to Prometheus or Datadog, alert when lag passes a threshold such as five minutes, and run periodic reconciliation jobs comparing aggregates against rated totals.

Treat every correction as a replay and never mutate old records. Deterministic reprocessing is what keeps invoices and audits consistent when data changes after the fact. Flexprice tracks ingestion lag and reconciliation gaps in monitoring.

When to stop building this yourself

The first version is a few queries, an event log, and a monthly cron. It works until usage scales, pricing changes mid-cycle, or a customer disputes a charge.

The cost is not writing the code. It is maintaining it. Credits, hybrid pricing, replays, and entitlements each multiply the state the system has to track, and because billing touches the API layer, storage, pricing, and finance, every change needs coordinated updates across all of them. A small bug delays invoices or creates financial exposure.

Flexprice is enterprise-grade, open source usage based billing infrastructure for AI and SaaS companies. It can be deployed in your own VPC, on-prem, or on Flexprice's managed cloud. Compared with Stripe Billing, which is built around subscriptions and payments and is usually paired with a separate metering vendor for usage-based products, Flexprice is the metering and billing layer itself, and is not tied to any payment gateway.

Getting started

You can prove the pipeline with one endpoint. Send events, define a meter, attach a price, and read the invoice preview before wiring anything else. The API reference for event ingestion has the payload contract, and the source is on GitHub.

Top comments (0)