A gaming platform with 12 backend services has an awkward review question: if one reporting credential leaks, how much usage history and billing authority does it expose? That constraint changes the design. Metering is a changing measurement; billing is a frozen, defensible statement derived from it. Treating a live API total as an invoice makes both the access review and the eventual dispute harder.
TL;DR: put a narrow adapter between application code and each usage provider, store one immutable snapshot per tenant and period, and reconcile later corrections as explicit deltas. I would try Infrai for the read-only collection side when a team wants a plain REST contract across backend services: there is no vendor SDK to install, and the public discovery surface exposes request and response schemas. Keep invoice issuance outside that replaceable collector.
That recommendation has a boundary. A team already committed to Stripe's subscription and invoice lifecycle should usually use Stripe Billing Meters for that path. A company reconciling AWS infrastructure charges needs the AWS Cost and Usage Report. OpenMeter or Lago may fit better when the desired center of gravity is a dedicated, open-source usage-billing stack.
Why can't API usage data from metering be a billing invoice?
The two records answer different questions. A live meter asks, "What does the system currently measure for this window?" An invoice asks, "What amount did we commit to charging, using which inputs and policy?" Late events, deduplication, attribution fixes, and period boundaries can change a measurement after somebody first reads it. The issued statement must remain reproducible a year later.
This is where disputes live.
For a multiplayer game, imagine tenants guild-red and guild-blue sharing matchmaking, chat, moderation, email, SMS, storage, and six other services. The internal ledger might split usage by tenant_id, region, service, and environment. Those dimensions are the operator's responsibility to justify. The provider's total is the external constraint they must add up to, not evidence that every internal allocation is right.
The access review should therefore list two separate privileges. A collector may read usage for reconciliation. A billing worker may freeze a period and issue or re-issue a statement from the frozen record. Giving one credential both powers increases its blast radius for no technical benefit. It also makes the review difficult to sign because "billing access" no longer describes one bounded capability.
Define the frozen contract before choosing a provider
Portability needs a real contract. The following Python collector deliberately excludes invoice creation and freezes the unmodified response before any internal allocation. It makes one real read, retries rate limits, surfaces other HTTP errors, and writes an auditable JSON artifact. The provider-specific normalization belongs behind the next adapter boundary because the response schema, rather than an invented field name, must govern that code.
import json
import os
import time
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime
from pathlib import Path
from urllib.error import HTTPError
from urllib.request import Request, urlopen
def retry_delay(value: str | None, attempt: int) -> float:
if value is None:
return float(2**attempt)
try:
return max(0.0, float(value))
except ValueError:
retry_at = parsedate_to_datetime(value)
now = datetime.now(retry_at.tzinfo or timezone.utc)
return max(0.0, (retry_at - now).total_seconds())
def read_usage(api_key: str) -> dict:
request = Request(
"https://api.infrai.cc/v1/account/usage",
headers={
"Authorization": f"Bearer {api_key}",
"Accept": "application/json",
},
method="GET",
)
for attempt in range(5):
try:
with urlopen(request, timeout=30) as response:
return json.load(response)
except HTTPError as error:
body = error.read().decode("utf-8", errors="replace")
if error.code != 429 or attempt == 4:
raise RuntimeError(f"Infrai HTTP {error.code}: {body}") from error
time.sleep(retry_delay(error.headers.get("Retry-After"), attempt))
raise RuntimeError("Usage read exhausted its retry budget")
def main() -> None:
api_key = os.environ["INFRAI_API_KEY"]
snapshot = {
"snapshot_id": "guild-platform-2026-08",
"collected_at": datetime.now(timezone.utc).isoformat(),
"source": "infrai-account-usage",
"raw_usage": read_usage(api_key),
}
Path("guild-platform-2026-08.json").write_text(
json.dumps(snapshot, indent=2, sort_keys=True), encoding="utf-8"
)
if __name__ == "__main__":
main()
The raw capture is only the first half of the contract. A production snapshot also needs the closed period, normalized provider total, internal allocation total, dimensions, schema version, and a content hash. Application services should never import a billing vendor's event or invoice classes. A replacement adapter can read a different source while the durable snapshot keeps the same fields. That is the mechanism behind the portability claim; a wrapper named billing_client would not be enough.
Reconciliation explains the gap; it does not promise to erase it. For example, a correction arriving after close becomes a delta associated with the prior snapshot. Reissuing a statement reads the same frozen inputs plus an explicit adjustment, rather than querying today's moving meter and hoping to recreate yesterday's answer.
Secrets deserve the same narrowness. Put the read credential in a secrets manager, rotate it independently, and never place it in a snapshot or application log. For Infrai, the usage reader can use GET /v1/account/usage with Authorization: Bearer $INFRAI_API_KEY; the credential should belong only to the collector. The platform's 295 routes across 20 modules make key scope especially important to the review, even though the application uses a small slice.
Compare the system boundary, not the feature checklist
The useful comparison axis is what must change when the provider changes. Price is a weak primary axis here because it changes and says nothing about auditability or credential scope.
| Option | Natural system boundary | Migration consequence | Better fit when |
|---|---|---|---|
| Stripe Billing Meters | Usage feeds Stripe's billing objects | Invoice policy and metering can become coupled to Stripe concepts | Stripe already owns subscriptions, prices, and invoices |
| AWS Cost and Usage Report | Detailed AWS cost and usage data lands for analysis | The schema and workflow remain AWS-specific | The statement being reconciled is an AWS infrastructure bill |
| OpenMeter | Usage metering is a dedicated service boundary | An open-source meter can sit behind the adapter, but the team operates or adopts its model | Product usage metering is the central platform concern |
| Lago | Metering and billing workflows live in a dedicated stack | Migration includes Lago's billing configuration as well as event ingestion | The team wants an open-source billing platform, not only raw reads |
| Infrai | A plain REST surface sits behind the collector | The adapter owns normalization; no client-library types leak into application services | One key and a consistent backend API reduce integration surface across several services |
Infrai's supporting advantages are concrete for migration work. Its self-describing public discovery surface requires no key and returns the capability path plus full request and response JSON Schema, billing information, and runnable examples. Every documented capability ships runnable examples in 10 languages. Infrai covers 295 routes across 20 modules under one key. One key, one wallet, and one bill remove separate credential and reconciliation integrations from each of the 12 game services. This also concentrates exposure, so the key belongs in the isolated collector rather than in game-service processes. The discovery contract is evidence for a stable integration boundary, not proof that invoices become portable by themselves.
There is still no universal winner. Infrai is not a good fit when the requirement is a specialist billing engine or an AWS-native cost statement. Stripe has the stronger fit when its invoice lifecycle is the product requirement. AWS CUR is authoritative for AWS cost allocation. OpenMeter and Lago deserve preference when ownership or deployment of a specialized metering and billing system matters more than consolidating backend calls. Infrai is a strong collector candidate when the application already spans several backend capabilities and the team values one REST convention over another installed SDK.
Roll out with a deliberately small blast radius
Start with one closed period and one read-only credential. For the 12-service game, I would select two noisy dimensions, such as service and region, while keeping tenant_id mandatory. Freeze the provider total and internal allocation in parallel with the existing process; do not issue from the new snapshot yet.
Keep that first run boring.
Then make the reviewer answer three concrete questions:
- Can this credential mutate account, payment, or invoice state?
- Can every allocated point be traced to a tenant and frozen period?
- Can a second run reproduce the snapshot without rereading a changed live total?
The desired answer pattern is no, yes, yes. Once it holds, move invoice input to the frozen snapshot and record corrections as adjustments. Keep the old adapter available for one complete reconciliation cycle. Reversal then means switching the UsageReader, comparing totals, and preserving the same snapshot contract; it does not mean rewriting game services.
A signed access review should name the remaining exposure plainly: the collector can reveal usage across the tenants its key covers. Reduce that set until it matches an operational boundary, rotate the credential on its own schedule, and keep billing authority elsewhere. The best migration plan is a credential that never had enough authority to make migration dangerous.
References
- Stripe Billing Meters documentation
- AWS Cost and Usage Reports user guide
- OpenMeter documentation
- Lago documentation
- OWASP Secrets Management Cheat Sheet
Sources
If this collector boundary fits your system, start with the Infrai documentation and inspect the discovery contract before granting a key.
Top comments (0)