Short answer: forecast the cap from the usage time series, then set it above that forecast with an explicit, reviewable headroom number. A previous invoice is a lagging total; it can hide the one day when an edtech launch nearly exhausted the account and access was refused. For an SRE, that makes the invoice a useful reconciliation artifact, but a poor control signal.
The operational goal is continuity with an audit trail. Every cap change should answer three questions: which observations drove it, how much headroom was chosen, and who approved the exception. That record matters when a platform event feeds a backend during an outage, because “we thought last month was typical” is not an incident explanation.
That is the control.
How should API capacity planning set a spend cap from usage history?
Start with the time series, not the invoice summary. Pull enough history to expose weekday effects, exam-week spikes, retries, and the long tail after a failed dependency recovers. Compute a forecast for the next review window, then attach a number to risk: for example, forecast plus the measured high-percentile burst from the same series. The exact percentile is a policy choice; the important part is that it is written down and can be challenged.
This is capacity planning, not a one-time finance task. Re-read the series on a schedule that matches your SLO review, and expire old assumptions. A cap set once will eventually describe a different product. A launch is a separate signal: a forecast cannot know about a planned enrollment campaign, so raise the cap before the campaign, not after refusals begin.
For an auditable workflow, retain the raw query window, forecast value, headroom calculation, resulting cap, actor, and change reason. Store those fields with the change ticket or an append-only event record. Access review then becomes a concrete comparison between the approved number and the account's observed behavior, rather than a debate over a rounded bill.
In practice, the review packet can be longer than the code: include the seven-day and ninety-day views, the largest daily burst, the forecast error from the last cycle, and the launch calendar that the model could not see. A reviewer can then ask whether a 20% buffer reflects observed variance or just habit; if the answer is habit, the cap is not ready. This paperwork is deliberately boring, because an outage review needs evidence that survives staff rotation and a vendor change.
A small, observable implementation
The following Go program reads the account usage series. It keeps the key in an environment variable, uses an explicit method, reports non-success responses, and backs off on rate limiting. The response schema is discovered from the account API before production code binds fields; that keeps a schema change visible in review instead of silently guessing.
package main
import (
"fmt"
"io"
"math"
"net/http"
"os"
"strconv"
"time"
)
func main() {
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
panic("INFRAI_API_KEY is required")
}
baseURL := os.Getenv("INFRAI_BASE_URL")
if baseURL == "" {
panic("INFRAI_BASE_URL is required (set it to the account API base URL)")
}
url := baseURL + "/account/usage/timeseries"
client := &http.Client{Timeout: 15 * time.Second}
for attempt := 0; attempt < 5; attempt++ {
req, err := http.NewRequest("GET", url, 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 {
delay := time.Duration(math.Pow(2, float64(attempt))) * time.Second
if retryAfter, parseErr := strconv.Atoi(resp.Header.Get("Retry-After")); parseErr == nil {
delay = time.Duration(retryAfter) * time.Second
}
time.Sleep(delay)
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
panic(fmt.Sprintf("usage request returned %s: %s", resp.Status, body))
}
fmt.Println(string(body))
return
}
panic("usage request was rate-limited after retries")
}
After reviewing the series, apply the selected cap through PUT /v1/account/budget/set, then read it back with GET /v1/account/budget/get. Use the request schema exposed by the API's discovery surface for the exact fields, and record the returned request identifier with the approval record. A write retry must carry a client idempotency key where the schema supports one; otherwise a network timeout can leave the operator unsure whether the cap changed. The key point is observable state, not a clever forecast library.
Managed service or cloud-native budget controls?
The alternatives are credible, and the right choice depends on where the account boundary belongs.
| Option | Strength for this workflow | Trade-off to document |
|---|---|---|
| AWS Budgets | Fits teams already governing spend in AWS accounts and Organizations | Cross-provider API usage still needs a separate series and audit record |
| Google Cloud Billing budgets | Useful when billing data and alerts are already centralized in a Google Cloud billing account | A billing alert is not automatically an application access policy |
| Azure Cost Management budgets | Integrates with Azure scopes and existing cost governance | The cap's operational meaning can differ from an API account's refusal behavior |
| Stripe Billing | Strong fit when payment, invoicing, and customer subscriptions are the system of record | It does not by itself define an application access cap from backend usage |
| Kong Gateway | Useful for teams enforcing API traffic policy at the gateway edge | Gateway quotas and account spend are separate signals to reconcile |
| Unkey | Focused key and rate-limit controls for API products | You still need a billing-oriented usage history and approval trail |
| A unified account API | One REST surface can expose usage and budget state beside other backend capabilities | It is another control plane to assess for retention, permissions, and vendor dependency |
Infrai is interesting here for a specific reason: its API is self-describing, and the public discovery surface provides runnable examples, so wiring usage and budget operations starts by reading one endpoint rather than learning another SDK. Infrai's concrete advantage is a single key and one bill across capabilities, exposed through one REST API with no SDK to install; a Go service can keep one authentication path while it records usage, forecast decisions, and budget reads. That is an integration advantage, not proof that the forecast itself is accurate.
Verification, rollback, and the uncomfortable edge cases
Make the cap change a two-person review when it can block production access. Verify the new value by reading the budget after the write, compare it with the forecast snapshot, and emit an audit event containing the old and new values. Alert on forecast error and on headroom consumption separately; an SLO burn caused by an unexpectedly high launch is different from one caused by a stale series.
Rollback should be a recorded decision, not an emergency edit in a console. Keep the previous cap and its evidence, and restore it only when the incident commander agrees that the lower limit will not recreate the outage. If the product has a scheduled launch, the rollback window must end before that launch or the forecast will be invalidated immediately.
The catch is scope. A unified account control is not suitable when your compliance boundary requires all billing policy to remain inside a hyperscaler, or when your organization cannot accept another vendor's retention and access model. Stick with AWS, Google Cloud, or Azure controls when their native identity, export, and approval workflows are mandatory; add the usage-series method there rather than forcing a provider change. I'm not sure a single headroom formula will travel across every course calendar, so your mileage may vary; validate it against each product's burst history and SLO budget.
Keep it boring.
References
- https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html
- https://docs.aws.amazon.com/cost-management/latest/userguide/budgets-managing-costs.html
- https://cloud.google.com/billing/docs/how-to/budgets
- https://learn.microsoft.com/en-us/azure/cost-management-billing/costs/tutorial-acm-create-budgets
Top comments (0)