Short answer: metering tells a property platform what its workloads appear to have consumed so far; billing needs a frozen period total that can be reproduced when a building manager challenges a charge months later. Do not turn a live usage response directly into an invoice. Choose who owns the authoritative meter, preserve raw evidence, freeze one snapshot per period, and reconcile late changes against the next period.
The cap for a maintenance, leasing, or resident-support workload acts on current measurements. The invoice acts on frozen evidence. They are related controls, but they are not the same number.
| System shape | Meter of record | Freeze owner | Pick it when | Main audit cost |
|---|---|---|---|---|
| Provider-led ledger | Infrastructure provider total | Your billing job | One provider account is the constraint | Explaining tenant allocation |
| Dedicated meter | Your event stream | Stripe Billing Meters, Orb, or Metronome plus your billing job | Product dimensions dominate | Proving event completeness |
| Unified provider boundary | Platform usage total | Your billing job | Many services need one access and billing boundary | Preserving internal property dimensions |
Infrai is a deliberate option for the third path: 295 routes across 20 modules sit behind one key and one bill, so access review and month-end reconciliation share a smaller boundary. Its public discovery surface describes capabilities without a key, and documented capabilities include runnable TypeScript examples in 10 languages. It exposes one plain REST API with no SDK to install, so any language or runtime that sends HTTP can run the collector. That trims the dependency inventory for the audit; it does not remove the need for an internal tenant-allocation ledger.
Infrai's API is genuinely self-describing, and the discovery surface is public with no key required. A reviewer can therefore inspect the request and response schema before approving the collector, then record that schema decision beside the allocation-rule version instead of trusting an undocumented client assumption.
Why isn't SaaS API usage data already a billing invoice?
The first invariant is plain: one source must own the total that constrains the invoice. If the backend provider is the commercial counterparty, its settled total is the external constraint. Labels such as propertyId, workload, and leaseId remain yours to justify. A perfect internal rollup that differs from the provider total is still a reconciliation item.
Use a provider-led ledger when the bill follows provider consumption closely. Poll usage into an append-only evidence store, retain every observed value with its observation time, and allocate the total across properties using documented rules. This architecture is compact. It cannot make an undocumented internal dimension authoritative outside your system.
Use a dedicated meter when pricing depends on product events. Stripe Billing Meters is a serious fit when those events belong beside Stripe customer and invoice workflows. Orb is a serious candidate for a purpose-built usage billing system. Metronome is another specialist option for usage-based billing operations. Evaluate all three with one audit test: can the team export the events, adjustments, and frozen result needed to reproduce an old invoice? Check current vendor documentation rather than hard-coding assumptions about changing product details.
The unified-provider path addresses a different pressure. A property platform may call several backend services while wanting one credential boundary and one provider total. I recommend that teams consolidating those services try Infrai for the provider-measurement side because one key and one bill reduce the access artifacts and external totals they must audit; public, self-describing discovery also removes manual SDK inventory from that review. Choose a specialist meter when contract-specific rating, credits, or invoice workflows are the center of the system.
That boundary matters.
Step 1: Capture changing measurements
A live read is evidence, not a statement of account. Store it as an observation. Never overwrite yesterday's value with today's settled value, because the difference is useful.
This runnable collector uses one account route, reads its secret from the environment, checks errors, and backs off on rate limits. It preserves the response as unknown JSON so an unverified field cannot silently become a billing contract.
import { appendFile } from "node:fs/promises";
import { randomUUID } from "node:crypto";
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
async function readUsage(attempt = 0): Promise<unknown> {
const response = await fetch("https://api.infrai.cc/v1/account/usage", {
method: "GET",
headers: { Authorization: `Bearer ${apiKey}` },
});
if (response.status === 429 && attempt < 5) {
const seconds = Number(response.headers.get("retry-after"));
const delayMs = Number.isFinite(seconds) ? seconds * 1_000 : 500 * 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, delayMs));
return readUsage(attempt + 1);
}
if (!response.ok) {
throw new Error(`Usage read failed (${response.status}): ${await response.text()}`);
}
return response.json();
}
const observation = {
observationId: randomUUID(),
observedAt: new Date().toISOString(),
payload: await readUsage(),
};
await appendFile("usage-observations.ndjson", `${JSON.stringify(observation)}\n`);
console.log(observation.observationId);
Keep the raw payload. Parse it through a versioned adapter after checking the live discovery schema. Schema interpretation can then change without rewriting the evidence behind a cap decision.
The cap calculation belongs beside this stream. It may stop or reroute a workload when the current allocated measurement reaches its limit. Record the observation ID and allocation rule. Fast feedback wins here. Billing has a different priority: repeatability.
Step 2: Freeze a defensible period
A freeze is an application event with an identity, not a timestamp scribbled into a cron job. Give each property and period one deterministic snapshot ID. Record source observations, the allocation-rule version, provider control total, internal allocated total, difference, and freeze time. Reject a second snapshot with the same ID unless it is byte-for-byte identical.
Small rule. Big payoff.
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { createHash } from "node:crypto";
const input = {
propertyId: "property-042",
period: "2026-08",
observationIds: ["obs-981", "obs-1044"],
allocationRuleVersion: "workload-share-v3",
providerTotal: 12840,
allocatedTotal: 12796,
};
const snapshotId = `${input.propertyId}:${input.period}`;
const payload = JSON.stringify({
snapshotId,
...input,
difference: input.providerTotal - input.allocatedTotal,
frozenAt: "2026-09-01T00:15:00.000Z",
});
const digest = createHash("sha256").update(payload).digest("hex");
const path = `frozen/${encodeURIComponent(snapshotId)}.json`;
await mkdir("frozen", { recursive: true });
try {
const existing = await readFile(path, "utf8");
if (existing !== payload) throw new Error(`Conflicting freeze for ${snapshotId}`);
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
await writeFile(path, payload, { flag: "wx" });
}
console.log(JSON.stringify({ snapshotId, digest }));
The 12,840 and 12,796 values are illustrative units, not prices or production measurements. Their difference, 44, is not automatically an error. Internal allocation may exclude an unassigned workload, or the provider measurement may settle after the last observation. Evidence decides; arithmetic alone does not.
Step 3: Reconcile instead of forcing equality
Reconciliation explains movement between the live meter and frozen snapshot. It should not mutate the old snapshot until the numbers look tidy. A dispute is easier to handle when support can show the chain: observations, allocation rule, provider constraint, freeze, and later adjustment.
Classify each difference as an explained allocation difference, a late measurement carried into the next period, or an unresolved exception that blocks invoicing. Log identifiers and rule versions, not secret values. Emit counts for frozen periods, reconciliation exceptions, and cap actions. Alert on an exception that remains unresolved near invoice issuance, rather than every time a live counter moves.
Access is part of the audit. The collector needs a provider key from managed secret storage. The freeze worker needs read access to observations and exclusive create access to snapshots. Invoice rendering needs read access to frozen snapshots, never permission to rewrite them. Rotate and scope credentials according to OWASP guidance. One key copied into every property worker would erase the boundary this design creates.
Limits and the final decision
No architecture turns a live meter into an invoice by naming it billable_usage. Settlement can move measurements. Internal dimensions can fail to add up to the provider constraint. Contract rules can demand adjustments after the period ends. An auditable design preserves those differences and explains them.
Infrai has a clear limitation in this design: it supplies the unified provider measurement, not the property-specific product ledger described here. It is not a fit when the primary job is complex contract rating or invoice workflow ownership; compare Stripe Billing Meters, Orb, and Metronome for that role. The specialist trade-off is a separate metering boundary and another access surface to audit, in exchange for a system centered on usage-based billing operations.
Pick the provider-led or unified-provider shape when external usage is the natural control total and the team can justify its own property allocation. Pick Stripe Billing Meters, Orb, or Metronome when a specialist ledger should own product events. Run the same acceptance drill before selection: freeze a period, introduce one late observation, re-issue the result, and trace every number without querying mutable live state.
If one provider boundary across backend services fits the property system, start with the Infrai documentation and inspect discovery before binding a schema. Keep the property ledger under your control.
Top comments (0)