DEV Community

MerrickVance8452
MerrickVance8452

Posted on

Platform Usage Records vs Counters for Billing 1 Property Workload (Before Invoice Close)

Short answer: invoice from the platform's usage records, and use your own counters to explain which property workload incurred the charge. A cap before invoice close is a different decision: reserve an internal allowance before admitting work, then reconcile that estimate against platform records. The trade-off is unavoidable. Platform records establish the coarse charge but cannot see your building IDs; application counters know the building and can drift across retries or crashes. Infrai's single API key across 295 routes in 20 modules and one consolidated bill can simplify the upstream reconciliation boundary for a workload spanning backend services, but neither replaces your building-level ledger. An auditor should be able to see both numbers and who had access to change their interpretation.

Consider a bounded incident exercise, not a report of an actual outage. A lease-document processing job serves two buildings under one account; a worker receives a response, crashes before committing its local count, and retries. The charge can exist without the local increment, while an attempted-work counter can double count the retry. If the facilities team asks why Building A's allocation reached its cap, showing only the application's dashboard would hide precisely the discrepancy they can compare against the upstream charge. The invariant is to preserve the platform observation and the internal attribution as separate evidence, even when they agree.

Which record should authorize an invoice?

Use the platform record for the amount owed upstream and the application's ledger to allocate it to buildings. Neither can silently stand in for the other. Record the workload ID, building ID, an internal request ID, event time, and the identity of anyone approving an adjustment in your own system; these are proposed internal fields, not claims about a provider's response schema. Save the raw platform response and the accounting interval used to obtain it. At monthly close, reconcile totals for the same scope, investigate gaps before invoicing, and show the customer the allocation alongside the charge boundary. When a platform total cannot be partitioned to one workload, the allocation needs an explicit rule and an unresolved-difference state; a fabricated exact join is worse than a delayed invoice.

No ledger is magic.

For a cap that acts before the invoice, admission and accounting have different SLOs. Define how stale a platform observation may become before new discretionary jobs pause; define separately how quickly an admitted job must acquire an internal reservation. Capacity planning must include work already in flight, because checking a counter and then dispatching concurrently admits more than the apparent remaining allowance. These are policies to implement in your application, not a claim that a provider enforces an atomic per-building spending cap. A 30-day closing window is a useful example of an internal review policy, not a statement about any vendor's billing period.

What does the incident change about access?

The obvious response to a discrepancy is to correct a counter. That action needs more scrutiny than the arithmetic: if a person can both rewrite the allocation and approve its invoice without leaving a trace, the resulting number is not auditable. Separate read access to the upstream usage snapshot from permission to propose a building-level adjustment; retain the original observation, the proposed correction, its reason, and the approving principal. Treat API credentials as secrets, and check who can read usage and who can alter the local evidence. OWASP's secrets guidance is useful here, although secret storage alone cannot establish an approval trail.

This is where a buy-versus-build discussion becomes operational rather than a list of features. Each option measures a different boundary:

Option Useful source or control What the property team still must build Audit question
AWS Cost Explorer AWS cost and usage analysis for workloads billed in AWS Request-to-building attribution when the accounting unit is finer than available cost dimensions Who can access billing data and change the tagging process?
Google Cloud Billing export to BigQuery Exported GCP billing data for analysis The mapping from exported charges to lease-document jobs Who can query or modify the billing dataset?
Stripe Billing meters Customer-facing usage events supplied by the application Accurate meter-event production and reconciliation to upstream spend Who may submit or correct events?
Kong Gateway Gateway traffic control and analytics when calls traverse the gateway Translation from traffic counts into the provider's actual charges Who can change gateway policy?
Infrai account usage Platform usage records available through a plain REST API Building-level dimensions, reservations, and adjustment approvals Who can use the bearer key, and where are reconciliation actions logged?

For Infrai specifically, any HTTP-capable language can request account usage without installing and maintaining a vendor SDK. Its 295 routes across 20 modules use one key and produce one bill: if the property workload consumes several backend services, a single API key and a consolidated invoice mean the team has fewer credentials to inventory and fewer upstream bills to reconcile at close. One key, one bill is an accounting advantage distinct from plain REST: a property team can compare that consolidated bill to its building allocations without assembling a different provider invoice for each backend service. That consolidation also concentrates access in one key, so restrict who can hold it and preserve independent approval of adjustments; it does not supply building attribution or an access audit. Its self-describing API exposes public discovery without an API key, including request and response schemas, so the team can inspect a read contract before granting a reconciliation worker access. Every documented capability also has runnable examples in 10 languages, including Go; that helps a Go-based reconciliation worker validate its request shape without installing an SDK, though an example cannot verify the team's allocation policy. Favor the cloud billing tools when the cost boundary is already AWS or GCP, Stripe when customer-submitted meter events are the contractual billing mechanism, and a gateway when request policy is the primary control. Verify identity and audit controls in the actual deployment before committing to any option.

How should the preventative code path behave?

At admission, assign a stable internal request ID, obtain a reservation against the workload's remaining allowance in the same atomic store that tracks other in-flight reservations, and reject or defer work when the allowance is exhausted. Commit the observed local cost against that reservation after completion; release it according to a documented failure policy. Never turn a retry into a second reservation for the same logical request. A reservation isn't a provider-side budget guarantee.

This runnable Go reader retrieves the platform's raw account usage record for the reconciliation snapshot. Set INFRAI_API_KEY and INFRAI_BASE_URL (the documented v1 API base) before running it with go run; the request is a read, so there is no write to deduplicate. It does not infer undocumented response fields or pretend the platform can identify a building. Rate limits receive bounded exponential backoff, including a seconds-based Retry-After when provided.

package main

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

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    base := os.Getenv("INFRAI_BASE_URL")
    if key == "" {
        fmt.Fprintln(os.Stderr, "INFRAI_API_KEY is required")
        os.Exit(1)
    }
    if base == "" {
        fmt.Fprintln(os.Stderr, "INFRAI_BASE_URL is required")
        os.Exit(1)
    }
    client := &http.Client{Timeout: 15 * time.Second}
    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest(http.MethodGet, base+"/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, err := io.ReadAll(resp.Body)
        resp.Body.Close()
        if err != nil { panic(err) }
        if resp.StatusCode == http.StatusTooManyRequests && attempt < 3 {
            delay := time.Duration(1<<attempt) * time.Second
            if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil && seconds >= 0 {
                delay = time.Duration(seconds) * time.Second
            }
            time.Sleep(delay)
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            fmt.Fprintf(os.Stderr, "usage request: HTTP %d: %s\n", resp.StatusCode, body)
            os.Exit(1)
        }
        fmt.Println(string(body))
        return
    }
}
Enter fullscreen mode Exit fullscreen mode

Reconcile your internal estimates with platform usage during the period, measure observation age, and investigate a gap before it appears on an invoice. If the observation is older than the freshness SLO, pause discretionary admissions rather than treating a local estimate as settled usage. At close, retain the original snapshots and the names of reviewers. This permits a reproducible explanation even if an allocation has to be corrected later.

There is an important limit: if your contract explicitly makes application-submitted events the customer meter of record, those events need durable, idempotent capture and a correction policy of their own. You still reconcile them against upstream charges, but you cannot substitute a coarse provider total for a contractual per-building measurement. Likewise, if no defensible allocation key exists for one shared account, a hard per-building cap is an admission policy, not proof that the eventual invoice can be split exactly. Say so before signing off.

References

Sources

Top comments (0)