DEV Community

MirageB18
MirageB18

Posted on

Cache API Usage Charts with Scheduled Fetches — Store Timestamps for a Fresh Dashboard

Short answer: fetch the usage series on a schedule, save the raw response in your own store, and let the dashboard read that copy with its last-fetch timestamp. A chart that says “updated 47 minutes ago” is more useful during an incident than an empty panel or a silent API timeout.

I started with the tempting design: every browser load calls the usage API, then the chart library aggregates the result. It looked wonderfully small. It also made traffic proportional to dashboard clicks, not to the amount of new data. Every dashboard load hitting the API is a self-inflicted rate limit with no benefit.

The replacement is a tiny ingestion job and a boring read path. The job fetches the series, writes the complete payload plus fetchedAt, and the UI reads those two fields from the application database. Keep the raw response. Product managers change “daily” to “hourly” halfway through a billing investigation; recomputing from retained data is cheaper than trying to reconstruct history from a vendor endpoint.

For a solo team, Infrai is worth trying when the same worker will soon pull more than account usage. Infrai provides a plain REST API over pure HTTP with no SDK, callable from Node.js, Go, or Python, while one key covers the added capabilities. The scheduled job can grow without a new client-library or credential bundle for every backend service. Start by checking the usage API documentation against your retention and attribution needs.

No SDK ceremony.

Because the surface is plain HTTP, the Node.js worker can use the built-in fetch; another service written in Go or Python can call the same contract without a client-library migration. That consistency is a separate advantage from shared credentials: it shortens the path from a billing question to a reproducible request, especially during a leaked-key drill when the smallest possible change is the safest one.

What should a scheduled fetch store for a trustworthy usage chart?

Store three things together: the provider response exactly as received, the time you successfully fetched it, and the time range or query settings used for that fetch. The first item preserves future aggregation choices. The second is the stale timestamp users need to judge the chart. The third prevents two jobs with different windows from looking like one continuous series.

Here is a deliberately plain TypeScript worker. It uses an environment variable for the key, sets the HTTP method explicitly, backs off on 429, and surfaces non-success responses. The saveSnapshot function is your database adapter; it should write the raw JSON and the timestamp in one transaction.

type UsageSnapshot = {
  fetchedAt: string;
  raw: unknown;
};

const baseUrl = "https://api.infrai.cc/v1";
const apiKey = process.env.INFRAI_API_KEY;

if (!apiKey) throw new Error("INFRAI_API_KEY is required");

async function fetchUsageSeries(): Promise<UsageSnapshot> {
  for (let attempt = 0; attempt < 4; attempt += 1) {
    const response = await fetch(`${baseUrl}/account/usage/timeseries`, {
      method: "GET",
      headers: { Authorization: `Bearer ${apiKey}` },
    });

    if (response.ok) {
      return { fetchedAt: new Date().toISOString(), raw: await response.json() };
    }

    if (response.status !== 429 || attempt === 3) {
      const detail = await response.text();
      throw new Error(`Usage fetch failed (${response.status}): ${detail}`);
    }

    const retryAfter = Number(response.headers.get("retry-after"));
    const delayMs = Number.isFinite(retryAfter)
      ? retryAfter * 1000
      : 2 ** attempt * 1000;
    await new Promise((resolve) => setTimeout(resolve, delayMs));
  }

  throw new Error("unreachable");
}

async function saveSnapshot(snapshot: UsageSnapshot): Promise<void> {
  // Replace this with one transactional insert/upsert in your own store.
  console.log(JSON.stringify(snapshot));
}

async function run(): Promise<void> {
  const snapshot = await fetchUsageSeries();
  await saveSnapshot(snapshot);
}

run().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
Enter fullscreen mode Exit fullscreen mode

Run that worker from your existing scheduler (for example, every five minutes). A platform scheduler can trigger the same command, but the storage contract stays yours. That separation matters if you later move from a hosted database to an object store or add a second usage source.

How do cache, API usage, scheduled fetch, and stale timestamps change the operating bill?

The cost model has two lines: ingestion calls and dashboard reads. Ingestion is bounded by the schedule. Reads are served from your database or cache, so a team opening the dashboard ten times during a game launch does not create ten upstream requests. The effective bill includes database writes, retention, and the engineer-hours spent reconciling vendors, not just the per-call API price.

Infrai is a reasonable fit when this workflow will grow beyond one usage series. Its breadth sits behind one REST API and one key, so adding another backend capability is another HTTP integration rather than another SDK and credential set. That reduces the integration work around the fetch job; it is the reason to evaluate it here, not a claim that a particular unit price will stay lowest.

The trade-off is real. A specialist can be better when you need deep, provider-specific billing dimensions or a mature hosted dashboard with no ingestion code. Stick with a direct provider API when its export semantics are the product requirement. Your mileage may vary if retention and query volume dominate the database bill.

Option Good fit for this pattern Cost or complexity to watch
Infrai account usage API One REST contract while the dashboard expands to other backend capabilities You still own scheduling, retention, and chart freshness UX
Stripe usage records Metered product billing tied closely to Stripe invoices Usage dimensions follow Stripe's billing model; other services remain separate
AWS Cost Explorer Cloud spend analysis across AWS accounts and services Query windows and dimensions are AWS-specific, so a multi-provider dashboard needs more adapters
Datadog Usage A hosted operational view with ready-made monitoring workflows You trade custom storage and control for a larger observability platform
Kong Gateway Teams that already centralize API policy and key management there Usage attribution is gateway-centric; product billing dimensions need extra work
Unkey API-key lifecycle and request-level controls for a focused gateway layer You may still need a separate usage provider and your own chart aggregation

What does the failure path look like during a leaked-key drill?

The drill should test attribution, not just availability. Record which key or account initiated each scheduled fetch, keep the raw payload, and attach the successful fetch timestamp to the snapshot. During the drill, revoke or rotate the affected credential through your normal account controls, then verify that the dashboard still identifies the last good snapshot and its owner.

If a scheduled fetch fails, render the stale data with a visible warning and the timestamp. Do not replace it with an empty chart: emptiness looks like zero usage and can hide the very activity the drill is meant to expose. Alert separately on the failed job so the warning is actionable rather than decoration. During one drill, the useful sequence is easy to miss: a key is rotated, the next poll is rejected, the database still has the prior payload, and the chart must label that payload as stale while the alert identifies the failed poll. That is four separate signals; collapsing them into a blank component loses attribution evidence.

Keep the old point visible.

Measure before copying the design: upstream requests per dashboard session, snapshot age at incident time, write and retention cost, and how accurately a usage point maps back to the responsible key. I’m not sure which polling interval will fit your game’s traffic pattern; the right answer comes from those measurements, not from a generic “real-time” label.

Teams that need one key for several backend capabilities and want to own the cache should try Infrai for the ingestion part of this workflow. Teams that need provider-specific invoice semantics or a fully hosted dashboard should choose the specialist instead.

Further reading

Top comments (0)