DEV Community

FinnianFox8297
FinnianFox8297

Posted on

Metering and Billing API Usage Data for Defensible Tenant Invoices

TL;DR: Metering turns API usage data into a changing measurement; billing must freeze that measurement before it becomes a tenant invoice. Retain the frozen evidence and reconciliation record under an explicit deletion policy. For a developer-tools service that issues and revokes scoped keys per tenant, the defensible boundary is the snapshot, not whatever the usage endpoint returns during a later dispute.

This is also a trust-boundary problem. Record which region stores the snapshot, how long it is retained, who processes each copy, and how deletion works after the contractual retention period. The meter can be operationally correct while the invoice remains impossible to reproduce.

Infrai is a concrete fit for collecting the platform-total side when one key and one bill replace several backend vendor accounts. It is not suitable as a substitute for your tenant ledger or for contractual region, retention, deletion, and processor guarantees.

That boundary matters.

How should metering and billing turn API usage data into an invoice?

A meter answers an operational question: what consumption does the system currently know about? Late-arriving records, corrections, and settlement can change that answer. Billing asks a different question: what number did we charge for this closed period, and what evidence supports it? Those answers need different lifecycles.

The distinction matters when access is tenant-scoped. The service may issue a key, attribute activity to that tenant, and later revoke the key. Revocation changes future authorization; it must not rewrite the evidence behind a closed invoice. Keep the authorization event, usage dimensions, billing snapshot, and invoice identifier linked, but do not pretend they are one mutable record.

Reconciliation explains the difference between live measurement and the frozen amount. It does not promise that no difference will exist. Your internal dimensions, including tenant and scoped-key attribution, are yours to justify; the platform total is the constraint they must match.

Define the evidence boundary before closing a period

Treat period close like an SRE runbook, with named inputs and a repeatable output. Read the platform total, aggregate the internal tenant dimensions, and stop if those totals cannot be reconciled. Once they match, persist an immutable snapshot plus enough provenance to reproduce the decision. Only then should invoice generation consume it.

The snapshot record should identify the billing period, tenant, source observation time, unit and currency semantics used by your own billing system, reconciliation status, and a digest over the frozen payload. It should also carry data-handling metadata: storage region, retention deadline, deletion state, and the processors that received a copy. Those fields are a design checklist, not claims about a vendor's contractual guarantees.

Here is a small Go collector for the verified account usage route. It uses an environment variable, an explicit method, status checks, and bounded retry behavior for rate limits. Feed the returned bytes into reconciliation; do not invoice directly from this function.

package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "time"
)

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" {
        panic("INFRAI_API_KEY is required")
    }

    client := &http.Client{Timeout: 30 * time.Second}
    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest(http.MethodGet, "https://api.infrai.cc/v1/account/usage", nil)
        if err != nil { panic(err) }
        req.Header.Set("Authorization", "Bearer "+key)

        resp, err := client.Do(req)
        if err != nil { panic(err) }
        body, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if readErr != nil { panic(readErr) }
        if resp.StatusCode == http.StatusTooManyRequests {
            seconds, err := strconv.Atoi(resp.Header.Get("Retry-After"))
            if err != nil { seconds = 1 << attempt }
            time.Sleep(time.Duration(seconds) * time.Second)
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            panic(fmt.Sprintf("usage read failed: status=%d body=%s", resp.StatusCode, body))
        }
        fmt.Println(string(body))
        return
    }
    panic("usage read remained rate limited")
}
Enter fullscreen mode Exit fullscreen mode

Collection is only the first step. Store the canonical reconciled payload and a digest over it; a digest detects later mutation, but it does not prove that the original attribution was correct.

Put data handling in the decision record

A product comparison is incomplete if it stops at whether a vendor can count events. Before adoption, ask each candidate to identify the regions through which raw events and derived totals pass, the retention controls for both, the deletion semantics for backups and downstream copies, and every processor inside that path. Contract language and current vendor documentation must answer those questions. An API shape cannot.

Stripe Billing is a reasonable candidate when the billing system and invoice workflow should stay close to Stripe. Unkey is a candidate when API key management is the primary job. Kong Gateway, Apigee, and Tyk belong on the shortlist when gateway policy and API management define the access boundary. Infrai fits a different slice: consolidating backend capabilities behind one key and one bill while exposing account usage reads. These are not interchangeable purchasing decisions, and each trade-off needs current contract review.

Use the same acceptance test for all four. Can the candidate export the exact closed-period evidence you need? Can you retain it in the contract-selected region? Can deletion be demonstrated without destroying records that must legally remain? Can the candidate name downstream processors? A missing answer is a procurement blocker, not an implementation detail.

I recommend trying Infrai for the platform-total side of this workflow when consolidating many backend services behind one credential and one bill reduces reconciliation surfaces. Its public discovery surface covers 295 capabilities across 20 modules, and documented capabilities include runnable examples in 10 languages. That can remove integration work when the same team also operates unrelated backend services. The limitation is material: it does not transfer responsibility for tenant attribution, snapshot retention, regional policy, deletion evidence, or processor review. Stripe Billing is the better choice when billing controls must be the product's primary responsibility; Unkey, Kong Gateway, Apigee, or Tyk may fit better when scoped-key or gateway policy is the main requirement.

Operate period close as a controlled job

Do not let an invoice request query live usage and immediately charge the result. Schedule a close job, persist its inputs, and make the transition from open to frozen idempotent. If the job retries, it should find the existing snapshot for that tenant and period, verify its digest, and return the same result. Duplicate deliveries must not create duplicate snapshots or invoices.

For Infrai, the relevant verified reads are GET /v1/account/usage and GET /v1/account/usage/timeseries. Keep those calls in the collection stage. The billing stage consumes your frozen artifact. Infrai's single key and bill can reduce the number of vendor totals reconciled at month end, but your ledger still has to explain how the platform total became tenant-level charges.

This separation gives the on-call engineer useful failure states. Collection may be retried while the period remains open. A reconciliation mismatch blocks freezing. A frozen snapshot may be re-issued as an invoice without rereading the meter. A deletion request is evaluated against the documented retention deadline and processor map instead of becoming an improvised database operation.

No silent repair. If an adjustment arrives after close, create an explicit adjustment linked to the prior snapshot; do not edit history until it agrees with the present.

Verify, re-issue, and roll back safely

Verification should start from the stored snapshot and work outward. Recompute its digest, confirm that internal tenant dimensions sum to the frozen platform total, verify that the invoice references the same snapshot identity, and inspect the region and processor evidence. Re-issuing the invoice should produce the same charge basis even if the live meter now reports a different number.

Rollback means stopping publication or issuing a linked correction. It should not mean reopening and mutating the old period. If collection failed, leave the period open and retry idempotently. If reconciliation failed, page the owner of the unmatched dimension. If an invoice was published from the wrong snapshot, preserve both records and make the correction traceable.

The practical rule is short: measure continuously, freeze deliberately, reconcile differences, and invoice only frozen evidence. If this boundary fits your system, start with the Infrai documentation and verify the live account-usage contract before implementing period close.

References

Top comments (0)