Short answer: Set each marketplace tenant's API spend cap from a rolling history of attributed usage, using an upper daily quantile plus known scheduled demand; don't copy the last month invoice, because an invoice mixes price, timing, shared traffic, and usage into one backward-looking number.
The bill is made of billable units multiplied by their rates, plus any fixed or tier effects in the applicable contract. For capacity planning, the dominant term is the one that contributes the largest amount in your own ledger, not the line item that looks largest in a generic pricing example. Start by grouping the ledger by tenant, credential, operation, and day. If 78% of an illustrative tenant's variable charge comes from one class of requests, changing retention for audit rows won't materially lower that tenant's required ceiling; reducing or scheduling that request class might. The percentage is an example calculation, not an industry benchmark.
This distinction matters in a marketplace account platform. A tenant key is an attribution boundary before it is a convenience. Issue one scoped key per tenant, bind its immutable internal tenant ID in the key registry, and revoke it without disturbing neighbors. Shared keys make the invoice easy to receive and nearly impossible to explain.
Measure first.
What should API capacity planning use to set a spend cap from usage history?
Use a time series at the same granularity as enforcement. If the control evaluates daily spend, build daily observations; a monthly total hides the day on which a promotion, import, or retry wave consumed the allowance. Keep raw billable quantity and calculated charge as separate fields. A rate change should let you reprice old usage for a planning comparison without pretending that demand changed.
The calculation needs three inputs: attributed historical usage, a policy for tolerated exceedance risk, and known future work. It does not need last month's invoice as its forecast. An invoice remains useful for reconciliation, but it is a poor capacity signal when it includes credits, shared platform traffic, minimums, delayed adjustments, or a different number of calendar days.
Here is a small Python example. The numbers are deliberately illustrative. It selects an observed upper quantile rather than assuming a probability distribution, adds a scheduled-demand allowance, and rounds the result to an operational unit. In production, the input should come from the metering ledger after late events have passed the team's stated reconciliation window.
from math import ceil
def observed_quantile(values: list[int], probability: float) -> int:
if not values:
raise ValueError("usage history cannot be empty")
if not 0 < probability <= 1:
raise ValueError("probability must be in (0, 1]")
ordered = sorted(values)
index = ceil(probability * len(ordered)) - 1
return ordered[index]
def propose_daily_cap(
daily_billable_units: list[int],
scheduled_units: int,
unit_rate: float,
rounding_increment: int = 100,
) -> dict[str, float | int]:
baseline = observed_quantile(daily_billable_units, 0.95)
planned_units = baseline + scheduled_units
capped_units = ceil(planned_units / rounding_increment) * rounding_increment
return {
"baseline_units": baseline,
"scheduled_units": scheduled_units,
"cap_units": capped_units,
"cap_amount": round(capped_units * unit_rate, 2),
}
tenant_usage = [
910, 880, 940, 1020, 970, 1110, 860,
900, 930, 995, 1080, 920, 890, 1040,
960, 975, 1010, 1200, 950, 915, 980,
1030, 1090, 925, 945, 990, 1060, 935,
]
proposal = propose_daily_cap(
daily_billable_units=tenant_usage,
scheduled_units=300,
unit_rate=0.002,
)
print(proposal)
Don't mistake the 95th percentile for a universal answer. A tenant running a time-sensitive catalog launch may choose more headroom than a tenant whose batch can pause until tomorrow. A new tenant with three observations has no credible tail estimate at all; place it under a conservative onboarding policy, collect enough attributed observations, and review the cap instead of laundering guesswork through a percentile function. I'm not sure any fixed window wins across seasonal marketplaces. Backtesting several candidate windows against holdout days is what resolves that uncertainty.
The cap should be stored in both billable units and money. Units preserve the demand decision when rates change; money is what the account owner approves. Record the rate-card version used for conversion, the history interval, the chosen quantile, scheduled additions, policy version, and approval time. Then a reviewer can reproduce the decision rather than reverse-engineer it from a total.
Consider a hypothetical seller whose ordinary traffic stays inside a narrow band, except for a catalog import every second Tuesday. A monthly invoice collapses the ordinary days, the import, any service credit, and the month length into one amount; copying that amount into a new ceiling neither identifies the burst nor tells the enforcement system when it is expected. The usage ledger does. It lets the planner calculate an ordinary-day baseline, attach a dated allowance to the two known import days, and leave the permanent ceiling alone. If the import is postponed, the allowance moves with the job rather than lingering as unexplained headroom. If the seller rotates its key between imports, the tenant history stays continuous because attribution follows the tenant ID, while the credential history still shows which key authorized each job. And if an event arrives without either identifier, it goes to quarantine rather than into a shared bucket. That single scenario exercises forecasting, scheduling, rotation, reconciliation, and enforcement; treating each as a separate dashboard metric would miss the billing relationship among them.
Attribution comes before forecasting
A forecast built on unattributed traffic is precise-looking fiction. Every accepted request should resolve to exactly one tenant before billable work begins, and every usage event should carry a stable event ID, tenant ID, credential ID, operation class, billable quantity, event time, and rate-card version. Credential ID and tenant ID are separate on purpose: rotation changes the credential but must not split one tenant's history.
Keep the tenant assignment server-side. Allowing a caller to supply a billing tenant header without binding it to the authenticated key turns allocation into a claim rather than a fact. During issuance, create a narrowly scoped credential, store only the secret material needed by the chosen verifier, and associate the public credential identifier with the tenant and scopes. During revocation, change the credential state atomically and deny subsequent authentication. OWASP's secrets guidance treats creation, rotation, revocation, expiration, and auditing as parts of the same lifecycle, which is the right frame here.
Metering also needs an idempotency rule. Retries can otherwise become duplicate usage records, while aggressive deduplication can erase two legitimate requests that happen to look alike. Use an event identifier created at the trusted metering boundary and make ledger insertion unique on that identifier. Late events should update the attribution ledger and reconciliation reports; they should not silently rewrite a cap decision without a new policy evaluation.
One awkward failure mode deserves more attention than it gets: a request authenticates under tenant A, launches asynchronous work, and records usage after the key has been revoked. Charging the currently active credential owner is wrong because there may be no current owner. The work record must carry the tenant and credential attribution captured at acceptance, while authorization state determines whether new work may start. This is a temporal boundary, not a database join to whatever the key table says now.
Stop on ambiguity.
Quarantine usage events that lack a resolvable tenant instead of spreading them proportionally across tenants. That makes the provisional bill incomplete, but it exposes an instrumentation failure rather than converting it into confident misbilling. Alert on the count and billable quantity of quarantined events, and make a zero-unattributed-usage check part of settlement readiness.
A cap is a policy state machine, not one number
Define behavior before deployment. A soft threshold can notify the tenant and account team; a hard threshold can reject new optional work while preserving authentication, revocation, usage export, and other control-plane operations. If every endpoint is blocked, the customer may be unable to inspect or contain the condition that triggered the cap.
| Decision | Useful default | Failure mode to test |
|---|---|---|
| Window | Rolling days aligned to enforcement granularity | A monthly aggregate hides a one-day burst |
| Baseline | Observed upper quantile | Sparse history creates false confidence |
| Future demand | Explicit scheduled allowance | A known import is mistaken for random growth |
| Attribution | One authenticated tenant per event | Shared or caller-asserted identity shifts charges |
| Enforcement | Soft alert before hard restriction | Retries amplify work near the boundary |
| Recalculation | Versioned policy and rate card | A rate change appears to be demand growth |
Model the states as normal, warning, and restricted, with hysteresis or a review interval so that delayed usage does not flap a tenant between states. Decide which operations remain available in restricted. Then test the transition with concurrent requests, duplicate events, late events, key rotation, key revocation, clock skew, and a rate-card change. A test that only sends requests sequentially under one credential proves very little.
Observability should answer four different questions: how much demand arrived, how much was accepted, how much was billed, and why the policy changed state. Those are separate counters. Log the policy decision ID on enforcement events and expose per-tenant consumption against the unit ceiling, not only a currency total. For privacy and incident response, avoid putting raw secret values in logs; credential identifiers are enough for correlation.
Deployment works best in shadow mode first. Compute decisions and alerts without restricting requests, compare the proposed states with actual attributed usage, and inspect every unattributed event. Move to soft notifications only after reconciliation is explainable. Hard enforcement comes last, with an explicit rollback of the policy version rather than ad hoc edits to individual counters.
Rates move.
How much history should the marketplace retain?
Retain enough aggregated history to cover the demand cycles used by the policy and enough immutable decision metadata to reproduce each active cap. Raw request-level records are useful for disputes and debugging, but keeping them forever increases storage, access-control, and deletion obligations. Aggregate daily billable units by tenant, operation, and rate-card version once the reconciliation window closes; preserve event IDs only as long as the audit and deduplication policy requires.
The catch is lost resolution. If the platform deliberately drops raw request events after aggregation, a later dispute can be answered at daily operation-class granularity, not by replaying every call. That trade is unsuitable when a contract, regulatory duty, or dispute process requires request-level evidence for longer. In that case, retain the raw ledger under stricter access and lifecycle controls, and accept its operational burden. For low-value, reversible workloads with strong daily reconciliation, the aggregate may be enough; your mileage may vary because the deciding obligation is contractual, not architectural.
There is another boundary: a historical policy is not suitable for a tenant whose next event is intentionally unlike its past. A newly onboarded seller, a one-off migration, or a scheduled marketplace campaign needs an explicit capacity reservation. Stick with manual approval or a separate workload quota when the financial ceiling must never interrupt that event. Conversely, do not inflate every tenant's permanent cap to cover one temporary job; attach a dated allowance and let it expire.
Review cost by retained data class, not by row count alone. Request payloads, verbose logs, and high-cardinality labels can dominate storage even when the metering record is tiny. Measure bytes written and queried for each class, then shorten or aggregate the dominant one if the audit contract permits it. What you deliberately stop keeping is request-level detail beyond the approved retention window. What it costs during a later incident is forensic precision, so write that limitation into the policy before deleting anything.
The decision rule is compact: use attributed daily units for the baseline, add only known forward demand, convert through a versioned rate card, and refuse automated enforcement when attribution completeness or sample depth falls below the team's declared threshold. The invoice checks the ledger afterward. It does not plan the next tenant's capacity.
Top comments (0)