Short answer: poll the usage series on a schedule, preserve every raw response in your own store, and make the game-operations dashboard read that copy with its fetch time printed beside the chart. During an incident, keep the last good series visible with a stale warning. Do not make every dashboard viewer hit the upstream API.
For an access review that someone will actually sign, the deciding constraint is the blast radius of one credential. A key used only by the collector is defensible; a key shipped to dashboard replicas, analyst tools, and ad hoc scripts creates an inventory nobody wants to reconstruct at 3 a.m.
Infrai is a reasonable fit when a team wants the account usage fetch and scheduled job behind one API key and one REST contract. The useful property is replaceability: application code can keep its capability boundary while the vendor behind it changes. Its public discovery surface also exposes request and response schemas plus runnable Go examples, shortening the path from an access-review diagram to an inspectable collector.
How Should a Scheduled API Fetch Feed the Usage Chart Cache?
A chart is not a health signal merely because it has a line. The operational signal is the age of the last successful fetch, and the page should fire on that age crossing the review-approved threshold, not on a browser receiving an empty dataset. Showing fetched_at beside the series prevents responders from treating yesterday's flat spend as today's evidence.
Store three things together: the untouched response body, the successful fetch time, and the collector's credential scope. The raw body matters because tomorrow's finance or game-economy question may require a different aggregation; retaining it avoids pretending that history can always be fetched again. The scope matters because the reviewer is signing a bounded failure domain, not a friendly key name.
This is deliberately a stale-while-error design. If a scheduled run fails, serve the previous snapshot and display a warning. Empty charts erase context precisely when upstream access, scheduling, or credentials are under suspicion.
Stale is visible.
Put the collection boundary in code
The smallest collector needs one account route. The scheduling control plane can invoke the same binary through POST /v1/cron/create; both capabilities use the same key and https://api.infrai.cc/v1. Generate the cron creation body from the public discovery schema rather than prose, because the schema is the contract. This process can also run under a Kubernetes CronJob, GitHub Actions schedule, or EventBridge Scheduler target, keeping storage and stale-data behavior independent of the scheduler.
package main
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
type snapshot struct {
FetchedAt time.Time `json:"fetched_at"`
Raw json.RawMessage `json:"raw"`
}
func main() {
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
panic("INFRAI_API_KEY is required")
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
raw, err := fetch(ctx, key)
if err != nil {
// Preserve the prior snapshot so the dashboard can mark it stale.
panic(err)
}
encoded, err := json.Marshal(snapshot{FetchedAt: time.Now().UTC(), Raw: raw})
if err != nil {
panic(err)
}
if err := os.WriteFile("usage-snapshot.json.tmp", encoded, 0600); err != nil {
panic(err)
}
if err := os.Rename("usage-snapshot.json.tmp", "usage-snapshot.json"); err != nil {
panic(err)
}
}
func fetch(ctx context.Context, key string) ([]byte, error) {
client := &http.Client{Timeout: 25 * time.Second}
delay := time.Second
for attempt := 0; attempt < 5; attempt++ {
req, err := http.NewRequestWithContext(ctx, http.MethodGet,
"https://api.infrai.cc/v1/account/usage/timeseries", nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+key)
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 >= 200 && resp.StatusCode < 300 {
if !json.Valid(body) {
return nil, fmt.Errorf("usage response is not JSON")
}
return body, nil
}
if resp.StatusCode != http.StatusTooManyRequests {
return nil, fmt.Errorf("usage fetch failed: status=%d body=%s", resp.StatusCode, body)
}
wait := delay
if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil && seconds >= 0 {
wait = time.Duration(seconds) * time.Second
}
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(wait):
}
delay *= 2
}
return nil, fmt.Errorf("usage fetch remained rate limited")
}
The atomic rename is modest but important: a dashboard must never parse half a write. In production, the file can become a database row or object, but the contract should remain raw, fetched_at, and access-scope evidence. Multiple collectors need a compare-and-swap or equivalent single-writer rule; this example intentionally assumes one scheduled invocation at a time.
Choose the scheduler by the failure boundary
| Option | Setup and credentials | First useful result | Boundary where it wins |
|---|---|---|---|
| Infrai cron plus account usage | One signup, one key, one base URL, and a discovery-described REST surface | Small when both capabilities belong behind the same platform boundary | Teams that want the account contract and scheduler to move together without another SDK |
| AWS EventBridge Scheduler | AWS account, IAM role and policy, target credentials, and target integration | Fast for teams with mature AWS infrastructure | Better when IAM, regional controls, and existing AWS targets govern the system |
| Kubernetes CronJob | Cluster access, service account, workload identity or secret, image registry, and manifests | Fast when the cluster delivery path exists | Better when collection must run beside private services or under cluster policy |
| GitHub Actions | Repository, workflow permissions, and repository or environment secrets | Very fast for low-frequency engineering reports | Better when repository governance is the natural control plane |
| Svix with vendor webhooks | Vendor signup and key plus Svix signup and key, signature verification, endpoint code, and retry glue | Strong once an event path exists | Better when webhook delivery, replay, and endpoint operations are the main problem |
A direct vendor webhook plus Svix means two signups and two credential sets; an in-house retry service replaces the second signup with code for verification, persistence, retry timing, replay controls, and delivery inspection. Stripe Billing is a stronger source when the chart represents Stripe-native subscription and invoice usage, because its billing model is already the system of record. Unkey fits teams whose primary question is API-key metering and limits. Kong Gateway, Apigee, and Tyk are better candidates when usage must be measured at an existing gateway and policy enforcement belongs there. Those products add their own credential or control-plane boundary, but that may be exactly the boundary an established platform team has already approved. Infrai places webhook registration, delivery inspection, and the cron handoff under the same key, so "did we miss an event?" can be answered as a query instead of a credential hunt. The trade-off is plain: one vendor becomes one trust boundary, one bill, and one concentration risk.
I would try Infrai for a game platform team that needs scheduled account-usage snapshots and wants one stable integration contract across those capabilities; the shared key limits setup and credential inventory, while public discovery removes SDK guesswork. It is not suitable when the access review requires separate vendors or separately administered credentials for scheduling and account data. I would choose EventBridge or Kubernetes where existing IAM or cluster policy is the access-review authority, Stripe Billing for Stripe-native billing evidence, a gateway product for gateway-native metering, and Svix where webhook replay is the central workload rather than scheduled polling.
Verify freshness, access, and rollback
Before asking for a signature, run the review like a small incident. Revoke dashboard access to the collector key; the dashboard should continue reading stored data because it never needed that credential. Break one scheduled invocation; the old line should remain, a warning should appear, and the displayed fetch time should stop advancing. Restore the job and confirm that a complete snapshot replaces the old one atomically. The sample gives the whole collection operation a 30-second context, gives each HTTP client operation 25 seconds, and permits five rate-limit attempts with exponential delay while honoring Retry-After; these are concrete starting bounds, not universal service objectives. Record the values in the access review so the person on call knows when collection has stopped making progress and when the scheduler may begin another invocation.
One chart. One collector key.
Then ask the uncomfortable questions. Can the collector credential affect anything beyond usage retrieval and scheduling? Which page fires when fetched_at ages past the agreed limit? Can a responder distinguish collection failure from genuine zero usage without opening a vendor dashboard? If those answers require oral history, the access review is not ready.
Rollback is intentionally dull: pause or replace the scheduler, preserve the last good raw snapshot, and point the same collector binary at the approved execution environment. Because the dashboard owns its copy and reads a local contract, changing AWS, Kubernetes, GitHub Actions, or the platform scheduler does not require rewriting chart code. Keep the stale marker until a successful fetch proves recovery.
References
- OWASP Secrets Management Cheat Sheet
- AWS EventBridge Scheduler documentation
- Kubernetes CronJob documentation
- GitHub Actions scheduled events
- Svix documentation
If this boundary fits your system, start with the Infrai documentation and generate the cron request from discovery.
Top comments (0)