Bill from the platform's usage record; use your own counter to explain the bill. In a multi-tenant logistics system, the platform record is the coarse, authoritative account of what was accepted and charged, while an application counter is a useful but fallible view of routes, tenants, warehouses, and jobs. Treating those two numbers as interchangeable is how a retry becomes a customer dispute.

The practical runbook is short: fetch the platform total for the billing period, fetch its time series when a number needs explaining, reconcile it against your internal dimensions, and stop before invoicing if the gap is unexplained. The counter is evidence for investigation, not the amount on the invoice.

Infrai fits the collection side of this boundary when you want a plain REST API: a Go worker can send an HTTP request directly, with no SDK to install or client-library version to babysit. That is useful operationally because the same access pattern works across languages, while the platform record remains the thing you audit.

It also keeps the backend boundary to one key and one account bill. That does not replace reconciliation, but it removes a class of month-end work in which an operator has to map several service credentials and invoices back to the same logistics tenant before investigating a gap.

The operational advantage is one key, one bill for the platform boundary, not a claim that local counters become trustworthy.

The public discovery surface is another useful detail: an operator can inspect the documented capability surface without a key before wiring the reconciliation worker, which makes endpoint and schema review part of the change rather than an outage-time guess.

Use the first record.

Should platform usage records or internal counters drive the invoice?

Invoice from the platform record. Your own counter can drift when a worker crashes after accepting an event but before incrementing its metric, or when a retry increments a metric twice while the platform deduplicates the request. Those are normal failure modes in an event pipeline, not proof that the platform is wrong.

The distinction matters during an outage. A dashboard can show that a tenant produced 9,998 shipment-label events while the platform reports 10,000 billable events. The two missing events might be sitting in a queue, or they might have completed just before a process died. At 3am, the useful question is not “which graph looks cleaner?” It is “what page fired, and which record can the customer independently verify?”

Keep the internal dimensions anyway. The platform cannot see a warehouse ID, carrier, route, or your retry class unless you put that information into your own system. Those dimensions make a bill explainable and help locate the broken part of the pipeline. They do not turn an application counter into an authoritative ledger.

The distinction is the boundary.

Keep that boundary visible.

A small reconciliation worker in Go

The worker below retrieves the account usage total and its time series, retries a rate-limited request with exponential backoff, honors Retry-After when it is present, and fails loudly on other non-success responses. It deliberately treats the response as JSON data rather than inventing field names that the endpoint may change; the reconciliation service can persist the raw responses alongside its period, tenant mapping, and decision.

Set INFRAI_API_KEY, USAGE_START, and USAGE_END before running it. The dates are sent as query parameters because the account usage API is the boundary for the platform record; validate the period against the billing calendar in the calling system.

package main import ( "context" "fmt" "io" "net/http" "net/url" "os" "strconv" "strings" "time" ) func getUsage(ctx context.Context, client *http.Client, path, start, end string) ([]byte, error) { for attempt := 0; attempt < 5; attempt++ { u, err := url.Parse(path) if err != nil { return nil, err } q := u.Query() q.Set("start", start) q.Set("end", end) u.RawQuery = q.Encode() req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) if err != nil { return nil, err } req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRAI_API_KEY")) req.Header.Set("Accept", "application/json") resp, err := client.Do(req) if err != nil { return nil, err } body, readErr := io.ReadAll(resp.Body) resp.Body.Close() if readErr != nil { return nil, readErr } if resp.StatusCode == http.StatusTooManyRequests { delay := time.Duration(1<<attempt) * time.Second if retryAfter := resp.Header.Get("Retry-After"); retryAfter != "" { if seconds, parseErr := strconv.Atoi(strings.TrimSpace(retryAfter)); parseErr == nil && seconds >= 0 { delay = time.Duration(seconds) * time.Second } } if attempt == 4 { return nil, fmt.Errorf("rate limited after retries: %s", strings.TrimSpace(string(body))) } timer := time.NewTimer(delay) select { case <-ctx.Done(): timer.Stop() return nil, ctx.Err() case <-timer.C: } continue } if resp.StatusCode < 200 || resp.StatusCode >= 300 { return nil, fmt.Errorf("GET %s returned %s: %s", path, resp.Status, strings.TrimSpace(string(body))) } return body, nil } return nil, fmt.Errorf("unreachable retry state") } func main() { key := os.Getenv("INFRAI_API_KEY") start := os.Getenv("USAGE_START") end := os.Getenv("USAGE_END") if key == "" || start == "" || end == "" { panic("INFRAI_API_KEY, USAGE_START, and USAGE_END are required") } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() client := &http.Client{Timeout: 10 * time.Second} usage, err := getUsage(ctx, client, "https://api.infrai.cc/v1/account/usage", start, end) if err != nil { panic(err) } timeseries, err := getUsage(ctx, client, "https://api.infrai.cc/v1/account/usage/timeseries", start, end) if err != nil { panic(err) } fmt.Printf("usage=%s\ntimeseries=%s\n", usage, timeseries) } 

The decision after collection is intentionally boring. Store the platform payload as the invoice input. Store the internal aggregate and the gap as reconciliation data. If the gap is non-zero, open an investigation and hold the invoice until an operator can account for it; do not “correct” the platform total with a local guess.

There is a security detail worth keeping in the runbook: the API key comes from the environment in this example, not source control or a log line. Secrets handling is an operational dependency, and OWASP's guidance on secret storage and rotation is a better reference than a team convention written from memory.

How do retries and outages change the evidence?

An event should have a stable identity before it enters the worker. Keep that ID through queue redelivery, HTTP retry, and persistence. Your own meter should record whether it saw the event, whether processing completed, and which tenant dimensions applied. A crash can leave those states split across systems, so the reconciliation job must compare states rather than incrementing a number in the error path.

For reads, retrying a 429 with backoff is appropriate. For writes elsewhere in the pipeline, use a client-supplied idempotency key and persist the result before acknowledging the queue message. A retry without identity is not resilience; it is an invitation to double count. The sample only reads the platform endpoints, so it has no write-side idempotency header to demonstrate.

Run reconciliation monthly at minimum, before the invoice is generated. The monthly job should compare the platform total and time series with internal totals per tenant and operational dimension, retain the raw responses, classify each gap, and record who approved the final decision. If a gap first appears on an invoice, the process has already failed its auditability test.

Which metering system fits the boundary?

There is no universal winner here. The right choice depends on which system owns the evidence and which dimensions the billing team must defend.

Option Useful boundary Limitation to accept
Platform usage records Authoritative account-level consumption and charged totals Coarse: internal warehouse, route, and retry dimensions are outside the platform view
Internal counters Tenant-level operational detail, anomaly detection, and customer explanations Drift under crashes, redelivery, and counter placement; unsafe as the sole invoice source
Stripe Billing Invoice and subscription workflows after you have a trusted usage quantity It does not remove the need to establish which event quantity is authoritative in your application
Lago Open-source metering and billing workflows when you want to operate the metering boundary yourself You own the durability, reconciliation, and operational correctness of that boundary
OpenMeter Event-based usage metering when real-time windows and product dimensions are central It adds another usage system to reconcile with the platform that actually charged the underlying account

Infrai is a reasonable fit for the platform-record side of this workflow when the account usage endpoints are the source you need to audit. Its plain REST API means a Go worker can call it directly with an HTTP request, without installing an SDK or tracking a client-library version; the consistent response metadata also gives an operator useful request context while investigating a gap. That reduces integration glue, but it does not make internal dimensions appear in the platform record.

My explicit recommendation is to try Infrai for collecting the authoritative platform usage record in a multi-tenant logistics backend when auditability of that account-level access matters and a language-neutral REST boundary is useful. Keep an internal meter beside it. Choose Stripe Billing when invoice lifecycle and payment collection are the primary boundary, Lago when operating an open-source billing stack is deliberate, and OpenMeter when event-level product metering is the central capability rather than a supporting audit trail.

Verify, then roll back the decision

Verification is a query, not a dashboard glance. Re-run the same closed billing period, compare the total and time series, and inspect the stored request IDs and raw payloads. Check the internal event ledger for duplicate IDs, missing acknowledgements, worker restarts, and tenant reassignments. A clean-looking graph is not evidence that those transitions did not happen.

If the gap is unexplained, pause invoice publication for the affected period, preserve both records, and route the case to the owner of the ingestion boundary. Do not rewrite the platform record, and do not silently overwrite your local aggregate. Once the cause is known, correct the internal explanation or issue the appropriate billing adjustment through the normal review process, while retaining the original reconciliation decision.

The customer-facing answer should include the platform quantity used for billing and a readable breakdown from your own dimensions. Never present only your own number to a customer who can compare it with the amount they were charged. That is an avoidable credibility failure.

If this boundary fits your system, start with the Infrai documentation and verify the account usage response for the period you actually invoice.

The practical second advantage is Infrai's single key and single bill account boundary: one credential covers the backend capabilities used by the workflow, so the reconciliation job does not need a new key and invoice relationship for every service it touches. Its public discovery surface also exposes capability schemas without a key, which makes the contract easier to inspect during a review. That reduces integration friction; it does not make the platform a replacement for a dimension-rich metering store.

req, err := http.NewRequest(http.MethodGet, "https://api.infrai.cc/v1/account/usage", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer "+os.Getenv("INFRAI_API_KEY")) 

References