Every API monetization platform has a billing gap — the interval between when value is delivered and when that value becomes accountable revenue. In most stacks, that gap is not a bug in your deployment. It is baked into the architecture.
The gateway serves the request. An event bus carries usage elsewhere. A billing service aggregates events. Stripe (or your AR team) collects money. Postgres mirrors state for dashboards — eventually.
Each hop is asynchronous. Each hop can fail independently. Over millions of requests, “eventually” is where revenue goes to die.
What the conventional stack looks like
A typical metered API platform looks like this:
Client → API Gateway → Upstream provider
↓
usage event (Kafka/SQS)
↓
billing worker → Stripe / invoice job
↓
reconciliation (nightly)
The gateway’s job is to proxy. Billing’s job is to charge later. Those are different systems with different failure modes, and the handoff between them is where the gap opens.
Common symptoms:
- Completed requests, missing charges. The upstream returned 200. The usage event never landed — broker backpressure, a poison message, a deploy mid-batch.
- Billing lag. Finance sees yesterday’s usage today. Customer support refunds a dispute before your ledger knows the call happened.
- Reconciliation as a product feature. A nightly job compares gateway logs to billing rows. Discrepancies become Jira tickets. Each edge case is engineering time and lost margin.
- Silent overspend. Without a synchronous funds check, consumers can run up usage before a wallet top-up or credit review catches up. You eat the loss or burn trust chasing invoices.
The gap is measured in milliseconds to minutes. At 10M requests/month, even a 0.01% failure rate is 1,000 unbilled calls — and that assumes you detect them at all.
A concrete failure mode
Consider a token-metered LLM proxy:
- Developer sends
POST /v1/chatwith a valid API key. - Gateway forwards to the model provider. Response: 4,800 output tokens.
- Gateway emits
{ consumer_id, tokens: 4800 }to a queue. - The billing consumer crashes during a deploy. The message is retried — but idempotency keys were an afterthought.
- The developer retries the same prompt. The gateway serves again. Two successful responses, one charge.
Who pays for the second inference? Your margin.
Now scale that to auto-scaling workers, partial outages, and customers who script against your API 24/7. The billing gap is not an edge case. It is structural leakage.
Finno’s model: charge in the same process that proxies
Finno does not treat billing as a downstream consumer of gateway events. The proxy and the ledger are one replicated state machine. Every charge is a two-phase commit on the request path:
request_filter
├─ authenticate consumer
├─ resolve funding (quota → wallet → credit)
├─ compute worst-case cost from pricing rules
└─ Reserve(max_micros) — 402 if insufficient
│
upstream call (10–1000 ms — the slow part)
│
logging
├─ meter actual usage (bytes, tokens, calls)
├─ price(actual_units) from the consumer's package
└─ Finalize(actual_micros) — refund over-reservation
│
response returned (Raft entry durable)
Reserve happens before upstream is contacted. If the consumer cannot fund the worst-case cost, they get 402 Payment Required — the provider is never invoked. You do not subsidize inference.
Finalize happens after metering the real response. If you reserved $0.01 for a worst case and the call cost $0.003, the difference is refunded atomically. No separate “adjustment” job.
Durability is committed to a Raft log before the HTTP response returns. There is no “we’ll reconcile tonight” step because there is nothing to reconcile: the charge either committed with the request or it did not ship.
Crash safety without heroics
If a node dies between Reserve and Finalize, the reservation sits in a pending map with a TTL. A background sweeper issues Cancel and refunds the hold. Combined with idempotency keys on the API, client retries are safe — you do not double-charge because the duplicate key returns the stored outcome.
This is not a billing workaround. It is the same invariant-driven ledger that finance expects — enforced on every command.
Why finance should care about architecture
Most engineering posts stop at “we’re fast.” Executives care about explainability:
- Every charge maps to a committed log entry — not a reconstructed aggregate.
- Six double-entry accounts (Float, Wallet, Receivable, DeferredRevenue, Owed, Revenue) satisfy one algebraic invariant after every command. Books balance by construction, not by a spreadsheet next month.
- Receivable and revenue post in the same atomic step as the API response. Your CFO sees reality in real time.
When audit asks “prove this customer was charged for call X,” you point at the ledger entry tied to that request — not a best-effort join between CloudWatch and a Stripe export.
What changes for your platform
| Before (split stack) | After (Finno) |
|---|---|
| Revenue reconstructed from logs | Revenue born at delivery |
| Quota enforced; money billed elsewhere | Quota, wallet, and credit in one funding ladder |
| Nightly reconciliation jobs | Invariant checked after every command |
| 402 is a custom plugin | 402 is native — insufficient funds before upstream |
You still integrate Stripe for top-ups and invoices. You still run dashboards and financial reports. The difference is that the gateway is the billing authority — not a telemetry source hoping billing catches up.
The billing gap is not a performance tuning problem. It is a category mistake: treating revenue as an async side effect of proxying. Finno closes it by making accountable revenue a precondition for serving the request — not a follow-up job.
Deploy once. Charge forever.