# Metering Usage for Billing — Multi-Tenant SaaS Architecture

Source: https://www.skillbyai.com/en/multi-tenant-saas/mt-metering-billing

> Record per-tenant usage events reliably, deduplicate retries with idempotency keys, and aggregate them into billable quantities.

## Counting what each tenant uses

Usage-based pricing, quotas and cost analysis all need **metering**: recording how much of each billable unit a tenant consumes, such as API calls, active users, storage, messages or compute minutes. The usual design emits a **usage event** whenever something billable happens, containing the tenant, the metric, the quantity, a timestamp and a unique **idempotency key**. Events flow through a queue into a store where they are aggregated per tenant per billing period. Two properties matter more than anything. **No double counting**: networks retry, so the same event may arrive twice, and deduplicating by idempotency key prevents overbilling. **No silent loss**: if events are dropped you undercharge and quotas stop working, so producers should write events durably (for example via an outbox table) rather than fire-and-forget. Keep raw events long enough to answer billing disputes, and reconcile totals against independent sources such as database storage reports.

## Deduplicating and aggregating events

The duplicate `e1` is a retry and is counted once.

```python
from collections import defaultdict

events = [
    {"key": "e1", "tenant": "acme", "metric": "api_calls", "qty": 120},
    {"key": "e2", "tenant": "zen",  "metric": "api_calls", "qty": 15},
    {"key": "e1", "tenant": "acme", "metric": "api_calls", "qty": 120},  # retried duplicate
    {"key": "e3", "tenant": "acme", "metric": "storage_gb", "qty": 4},
]

seen = set()
usage = defaultdict(int)
for e in events:
    if e["key"] in seen:          # idempotency: count each event once
        continue
    seen.add(e["key"])
    usage[(e["tenant"], e["metric"])] += e["qty"]

for (tenant, metric), qty in sorted(usage.items()):
    print(tenant, metric, qty)
```

Output:

```
acme api_calls 120
acme storage_gb 4
zen api_calls 15
```

## Bill from facts, not from logs

Application logs are sampled, rotated and reformatted, which makes them unreliable for money. Emit purpose-built usage events with a stable schema, and treat changing that schema as carefully as changing a database table.

**Quiz:** A usage event for 120 API calls is delivered twice because of a network retry. What prevents the tenant being billed for 240?

- [x] Deduplicating on the event's idempotency key
- [ ] Rounding usage down to the nearest hundred
- [ ] Billing only once per year
- [ ] Dropping all events that arrive within one second of each other

*Answer:* Deduplicating on the event's idempotency key. Each logical event has a unique key, so a retried delivery is recognised and ignored. Time-based dropping would lose genuine events.
