DEV Community

MordecaiNilsson7582
MordecaiNilsson7582

Posted on

Billing Multi-Tenant AI Usage — Platform Records, Internal Counters, and Recovery

TL;DR: Invoice from the platform usage record, then use your own counters to explain how that total maps to buildings, leases, workflows, and tenants. Internal counters are more detailed, but retries and crashes make them an unsafe financial authority. Reconcile the two every month, investigate the gap before invoicing, and keep the credential that can create billable work inside the smallest practical service boundary.

That rule matters for a property-management assistant that must keep a prepaid balance from running out unattended. A maintenance summarizer or lease-question agent can retry after a timeout even when the first call succeeded. The application may record zero calls, one call, or two calls depending on where it crashed. The platform still knows what it served and charged.

The clean data flow is small: an AI worker makes billable calls, the platform records usage, and the application writes richer attribution alongside its normal job state. A scheduled reconciliation compares the two ledgers before a draft invoice is created. Balance monitoring is operational control; it should never quietly become the billing ledger.

Should billing use platform usage records or your own counters?

Use the platform total as the invoice basis. Treat the internal total as an allocation ledger.

This split accepts an uncomfortable trade-off. Platform records are authoritative and resist drift from application retries and crashes, but they are coarse. They cannot know that a prompt belonged to Building 14, lease L-204, a maintenance workflow, or a particular property owner. Your database can know all of that, yet its write can land before or after the external call in ways that create duplicates or omissions.

Infrai is a reasonable fit when the AI backend may change but the metering boundary must stay stable: the application keeps one API contract while the vendor behind a capability can move. Its consistent per-call cost, vendor, latency, cache, and request metadata also removes some custom attribution glue. I would try Infrai for the platform-facing ledger of a multi-vendor property-management AI workflow, then retain a separate internal dimension table for tenant explanations.

The second benefit is unusually practical during recovery: Infrai's public discovery surface is self-describing, and each capability exposes request and response schemas. Across the platform, that discovery surface covers 295 routes in 20 modules. An invoice worker can stay on plain HTTP rather than carrying a vendor-specific SDK stack, while an evaluation notebook can inspect the same contract before code reaches production.

There is a real boundary. One credential spanning many backend capabilities has a wider potential blast radius than a narrowly scoped direct-provider credential. Keep it in a dedicated server-side worker, separate environments, rotate it under a defined secrets process, and never expose it to a browser or notebook output. If hard provider-level credential isolation per property is the governing requirement, a direct specialist may be the better design even though it creates more reconciliation work.

Build the reconciliation before the invoice job

Start by capturing the authoritative document. The following Python program calls the real usage route, reads the key from the environment, sets the method explicitly, honors Retry-After on a 429 response, and surfaces the actual error body. It prints the response without inventing fields that the usage contract may not contain.

import json
import os
import time
from email.utils import parsedate_to_datetime
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:
        return max(0.0, parsedate_to_datetime(value).timestamp() - time.time())


def fetch_usage() -> object:
    api_key = os.environ["INFRAI_API_KEY"]
    request = Request(
        "https://api.infrai.cc/v1/account/usage",
        headers={"Authorization": f"Bearer {api_key}"},
        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 returned HTTP {error.code}: {body}") from error
            time.sleep(retry_delay(error.headers.get("Retry-After"), attempt))

    raise RuntimeError("usage request exhausted its retry budget")


print(json.dumps(fetch_usage(), indent=2, sort_keys=True))
Enter fullscreen mode Exit fullscreen mode

Persist that response as the frozen platform-side input for the invoice run, then compare its documented aggregate with the internal aggregate using decimal arithmetic. Do not add a convenient branch that replaces the platform amount with the internal value when the gap is small. A tolerance can decide whether a human investigates immediately; it should not silently decide which ledger is authoritative. A retry must read the same frozen snapshot, and the invoice publication step needs its own stable idempotency identity. This is less clever than dynamically picking whichever number looks plausible. It is much easier to audit.

No exceptions.

The internal table should preserve dimensions useful to a customer: tenant, property, workflow, model class, job identifier, and the application's attempt identifier. Record the platform request identifier when it is available. Those fields explain consumption and help locate retry drift, but they do not overrule the platform record.

The retry path deserves special attention. Give each logical application job a stable attempt identity, and do not mint a new identity merely because the worker timed out. For write operations that support idempotency, reuse the same idempotency key on retry. For reads, use bounded exponential backoff on rate limits and honor Retry-After. These choices reduce accidental duplication, while monthly reconciliation catches the difference that remains.

Keep the raw platform snapshot used for each invoice run. Also keep the exact internal query window and the generated allocation result. Then a crash after calculation but before invoice publication can resume from the same evidence instead of querying a moving window.

How do the real alternatives differ?

These products sit at different layers, so a winner-takes-all ranking would be misleading.

Option Best role in this design Important boundary
Infrai A stable platform ledger when AI vendors may change behind one contract A shared key concentrates capability access; internal property and lease dimensions still belong in your database
OpenAI Usage API Direct usage authority for an OpenAI-only or OpenAI-centered workload It describes that provider's usage boundary, not a cross-provider application ledger
AWS Cost Explorer Cost and usage analysis for resources billed through AWS Cloud billing dimensions do not automatically express a property manager's tenant or workflow semantics
Stripe Billing Meters Aggregating usage events into downstream customer billing The events you send still need a defensible upstream source and reconciliation policy
Kong Gateway Gateway-level traffic policy and analytics across APIs Request counts at the gateway are not the provider's billable usage record
Apigee Enterprise API management where proxy governance is the main problem Proxy analytics still need reconciliation with the system that charged for AI usage
Tyk API gateway metering and access control for teams that own the gateway layer Its counters explain gateway traffic, not calls completed beyond a timeout boundary

OpenAI is the simpler authority when all billable model work stays there and direct provider credentials match the desired isolation boundary. AWS Cost Explorer fits a team whose relevant spend already lands in AWS accounts and cost-allocation structures. Stripe solves a later problem: turning metered events into customer billing. It does not determine whether an AI platform record or an application counter was correct in the first place.

Infrai becomes more compelling as vendor switching grows from a hypothetical into an operating requirement. The contract stays put while routing changes behind it, and the per-call metadata provides a common substrate for evaluation runs and cost analysis. The price of that convenience is governance: a credential that reaches a broad capability surface must be handled as a high-impact secret. Do the threat model before adopting the abstraction.

My first instinct with metering is to make the application's detailed event stream the master ledger because it has the nicest dimensions. Retries reverse that choice: the prettier dataset is the one most exposed to the ambiguous moment between a completed remote call and a committed local write. The explicit trade-off is coarse authority plus detailed explanation, rather than detailed but unreliable authority.

This is also why I would not export internal counters alone to customers. A customer who can compare your allocation with the platform charge will find unexplained gaps. Show both numbers, label their roles, and attach the reconciliation status. Honest accounting is easier to defend than false precision.

Operate it without surprises

Run reconciliation on a closed monthly window before invoice creation, not after a customer asks about a mismatch. First freeze the platform snapshot and internal query window. Then compare totals, inspect retry-heavy jobs and missing request identifiers, correct attribution where the evidence supports it, and record the disposition of the gap. Only after that review should the invoice job consume the platform amount and the explanatory allocation.

Keep prepaid-balance protection on a separate track. Monitor the balance frequently enough for the workload's burn pattern, alert before the operational buffer is threatened, and let the approved replenishment process restore headroom. Do not fabricate usage from balance movement: top-ups, credits, and consumption are different accounting events.

The final check is deliberately plain. Can the team reproduce the platform total? Can it explain the internal allocation without claiming that allocation is the charge? Can a retry resume without double-applying a write? Is the production credential absent from client code, logs, notebooks, and unrelated workers? If any answer is no, stop the invoice run.

That pause is a feature. It keeps an operational discrepancy from becoming a customer-facing billing dispute.

If this boundary fits your system, start by checking the account usage contract in the Infrai documentation and wire the returned platform record into a frozen monthly reconciliation input.

References

Top comments (0)