08 · API Monetization & Developer Portals¶
Some APIs are the product itself — customers pay directly for access. This module covers metering usage, billing for it, and the developer portal experience that makes a paid API self-service.
Metering: counting what customers use¶
curl https://api.example.com/v1/geocode?address=1600+Amphitheatre+Pkwy \
-H "Authorization: Bearer sk_live_..."
Every billable call increments a per-account usage counter, typically recorded asynchronously (so metering never adds latency to the actual response) via the same event-driven pattern from module 7:
Pricing models¶
Tier | Included calls/mo | Overage | Price
------------|--------------------|--------------|----------
Free | 1,000 | blocked | $0
Pro | 100,000 | $0.001/call | $49/mo
Enterprise | Custom | Custom | Custom (negotiated)
{ "error": { "code": "quota_exceeded", "message": "Free tier limit of 1,000 calls/mo reached. Upgrade at https://example.com/pricing" } }
HTTP/1.1 429 Too Many Requests
Unlike the rate limiting in Level 2 (fairness, resets every window), quota exhaustion here is a billing boundary — it resets on the billing cycle, not every minute, and the fix for the customer is upgrading a plan, not waiting 60 seconds.
Usage dashboards: self-service visibility¶
{
"period": "2026-08",
"calls_used": 84213,
"calls_included": 100000,
"projected_overage_cost_usd": 0.00,
"breakdown": { "/v1/geocode": 71000, "/v1/reverse-geocode": 13213 }
}
Giving developers this data directly, in real time, avoids the worst outcome for a paid API: a customer discovering a huge bill at the end of the month with no way to have seen it coming.
The developer portal as the whole self-service loop¶
A monetized API's portal typically bundles:
- Sign-up → key generation (module 4)
- Interactive docs and quickstart (Level 3, module 7)
- Live usage dashboard (above)
- Billing/invoices and plan upgrade, self-service, no sales call needed for the lower tiers
- Support ticket / community forum link
curl -X POST https://api.example.com/v1/account/plan \
-H "Authorization: Bearer $DASHBOARD_TOKEN" \
-d '{"plan": "pro"}'
Worked example: designing quota enforcement without hurting reliability¶
A naive implementation checks quota synchronously against a database on every single call — adding latency and a new point of failure to every request, and risking overselling if two concurrent requests both read "999 of 1000 used" before either writes back 1000.
A better design:
- Maintain a fast, in-memory (Redis) counter per account, incremented
atomically (
INCR) on every call — no read-then-write race. - Check the counter against the plan's limit synchronously (this part
must be fast and correct) — return
429 quota_exceededimmediately if over. - Asynchronously reconcile the fast counter against the durable billing ledger (via the event stream from module 7) for accurate invoicing, decoupled from the hot request path.
- Reset the counter on the billing cycle boundary, not a rolling window.
How It Actually Works¶
Metering usage for billing means every request has to be counted and attributed to a specific customer reliably, even under failures and retries — the mechanism is closer to financial ledger-keeping than simple logging.
A typical metering pipeline:
1. Request arrives with API key -> resolved to customer_id (module 4).
2. Handler completes -> emit a usage event:
{ customer_id, endpoint, timestamp, request_id }
3. Event goes to a durable queue (not counted in-process synchronously,
because an in-memory counter is lost on a crash/restart).
4. A separate aggregation job sums events per customer per billing period.
The request_id in that event is what makes billing idempotent against
duplicate counting: if step 3's queue delivers the same event twice (the
at-least-once guarantee from event-driven systems, module 7), the
aggregation job deduplicates by request_id before summing — without
this, a network retry or a broker redelivery directly inflates a
customer's bill, a bug class with real financial consequences, not just a
display glitch.
Tiered rate limits per plan reuse the token-bucket mechanism (module
6, Level 2) but with capacity/refill_rate looked up per customer's
plan rather than one global constant — the same rate-limiting code path
runs for every customer, just parameterized differently, which is why
billing-plan changes (a customer upgrading tiers) can take effect
immediately: it's a config lookup change, not a code deploy, as long as
the plan-to-limits mapping is read fresh (or from a short-TTL cache) on
each request rather than baked in at startup.
Exercise¶
- Why is a naive read-then-write quota check subject to a race
condition under concurrent requests, and how does an atomic
increment (
INCR) fix it? - Explain the difference between rate limiting (Level 2, module 9) and billing quota enforcement — why do they need different reset windows and different client-facing remedies?
- Design the response body and status code for a customer who's 80% through their monthly quota — should this ever be surfaced proactively, and how?
- A customer disputes an invoice, claiming the usage count is wrong. What data would you need to have been logging all along to resolve this dispute with confidence?