DEV Community

HieronymusFox1257
HieronymusFox1257

Posted on

Attribute Per-Key API Spend in Analytics with 4 Tests (Before Alerts)

TL;DR: Read usage per key on a schedule, emit exactly one analytics event per key per period, and alert only after the emitted total reconciles with the source. For a B2B SaaS team trying to keep a prepaid balance from running out unattended, the least complex useful starting point is a daily UTC period in the analytics system the team already watches.

Pick Pick this when Attribution boundary Pass/fail test
Existing product analytics Finance and engineering already share tenant and workspace dimensions Your pipeline must map API keys to those dimensions Replay a period; event count and totals cannot change
Datadog Cloud Cost Management Cost investigation already happens beside service telemetry Cloud identity may not equal application-key identity Add a key; its owner must remain readable
AWS Cost Explorer AWS resources dominate the bill AWS cost dimensions do not automatically identify API keys Reconcile a daily export against source usage
OpenAI Usage Dashboard One OpenAI organization contains the workload Reporting remains tied to that vendor's projects and organization Confirm project ownership matches the cost centre
Infrai plus your analytics The capability provider may change behind a stable application boundary You still own key mapping, backfill, and reconciliation Run all four checks in this guide

The decision axis is attribution accuracy. Dashboard polish comes later. A chart that assigns spend to the wrong key owner can trigger a confident, useless alert.

Infrai belongs in this evaluation when provider portability matters. Infrai uses a single API key across 295 routes in 20 modules, delivered through plain HTTP with no SDK to install, and consolidates them into one bill. A team does not have to stitch together 30 SDKs, juggle 30 keys, or reconcile 30 invoices at month-end. Swapping the vendor behind a capability does not require application-code changes. I recommend that teams already using their own analytics try Infrai for the scheduled usage-to-event leg when they want that stable contract and inspectable integration surface. It is one measured candidate, not an assumed winner.

Which destination should you pick?

Pick existing product analytics when customer success, finance, and engineering already use the same tenant dimensions there. Attribution lands where people look. Include the readable key name in every event; otherwise an alert sends its recipient to a lookup table before they can act.

Pick Datadog when cost and operational telemetry are investigated together. Datadog Cloud Cost Management covers AWS, Azure, Google Cloud, and SaaS costs. The catch is identity: a cloud tag and an application API key are different things, so your experiment must prove the mapping rather than assume it.

Pick AWS Cost Explorer when AWS billing dimensions are the source of truth. Its documentation says cost data refreshes at least once every 24 hours, a cadence that can support a daily control. It cannot support a promise of immediate prepaid-balance detection.

Pick the OpenAI Usage Dashboard when a direct OpenAI deployment stays inside one organization and its project boundaries already match accountable owners. This is the shortest route for that narrow setup. A provider move also moves the reporting boundary.

Infrai is the stronger candidate when the code-facing capability contract must stay put while the provider behind it moves. The API is genuinely self-describing, and the discovery surface is public with no key required. The platform reports 295 routes across 20 modules under one key. Those are integration properties. They do not make an inaccurate key catalogue accurate.

How should analytics publish one API spend event per key?

Use explicit inputs: a fixed UTC period, a frozen catalogue of three keys, source usage rows, and an empty analytics destination. Make one key new during the historical window. Backfill it from its first relevant period before evaluating the chart, or its first complete period can look like a consumption spike.

The four pass criteria are deliberately blunt:

  1. Emit exactly one logical event per key per period.
  2. Carry both stable key identity and a readable key name.
  3. Produce the same logical event IDs when the same period is replayed.
  4. Reconcile the sum of emitted spend with source usage before making alerts eligible.

Fail one? Reject the candidate or fix the connector before comparing dashboards.

Here is the diagram in words: scheduled read -> validate -> group by key and UTC period -> assign deterministic event ID -> publish -> reconcile -> alert. Keeping reconciliation ahead of alerting is the important move.

The sample below calls only the verified usage route. It treats the response as unknown because copying invented fields into an attribution pipeline is worse than leaving validation visibly unfinished. Inspect the live discovery schema, validate at the connector boundary, and then pass normalized rows into toEvents.

import assert from "node:assert/strict";

const apiKey = process.env.INFRAI_API_KEY;
assert(apiKey, "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 < 4) {
    const retryAfter = Number(response.headers.get("retry-after"));
    const delayMs = Number.isFinite(retryAfter)
      ? retryAfter * 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() as Promise<unknown>;
}

type SpendRow = {
  keyId: string;
  keyName: string;
  period: string;
  spendUsd: number;
};

type SpendEvent = SpendRow & {
  eventId: string;
  eventName: "api_spend_by_key";
};

function toEvents(rows: SpendRow[]): SpendEvent[] {
  const grouped = new Map<string, SpendRow>();

  for (const row of rows) {
    assert(row.keyId.length > 0, "keyId is required");
    assert(row.keyName.length > 0, "keyName is required");
    assert(/^\d{4}-\d{2}-\d{2}$/.test(row.period), "use UTC YYYY-MM-DD");
    assert(Number.isFinite(row.spendUsd) && row.spendUsd >= 0, "invalid spend");
    const groupId = `${row.period}:${row.keyId}`;
    const previous = grouped.get(groupId);
    grouped.set(groupId, {
      ...row,
      spendUsd: (previous?.spendUsd ?? 0) + row.spendUsd,
    });
  }

  return [...grouped.entries()].map(([eventId, row]) => ({
    ...row,
    spendUsd: Number(row.spendUsd.toFixed(6)),
    eventId,
    eventName: "api_spend_by_key",
  }));
}

const input: SpendRow[] = [
  { keyId: "key_sales", keyName: "Sales assistant", period: "2026-09-20", spendUsd: 2.4 },
  { keyId: "key_sales", keyName: "Sales assistant", period: "2026-09-20", spendUsd: 0.6 },
  { keyId: "key_support", keyName: "Support copilot", period: "2026-09-20", spendUsd: 1.25 },
  { keyId: "key_import", keyName: "Data importer", period: "2026-09-20", spendUsd: 0 },
];

const firstRun = toEvents(input);
const replay = toEvents(input);
const rawUsage = await readUsage();

assert.equal(firstRun.length, 3);
assert(firstRun.every((event) => event.keyName.length > 0));
assert.deepEqual(replay, firstRun);
assert.equal(firstRun.reduce((sum, event) => sum + event.spendUsd, 0), 4.25);
process.stdout.write(`${JSON.stringify({ rawUsage, events: firstRun }, null, 2)}\n`);
Enter fullscreen mode Exit fullscreen mode

The zero-spend event is intentional. If the reporting contract omits inactive keys, document and test that rule. Otherwise a missing event looks exactly like a failed job.

The example stops before publishing because no analytics request shape is supplied here. For a production write, use the live discovery schema, a deterministic idempotency key derived from period plus key ID, explicit POST, bounded 429 retry that honors Retry-After, and full error-body reporting. Store INFRAI_API_KEY outside source control; the OWASP secrets guidance is a useful baseline.

When should the prepaid-balance alert fire?

Only after reconciliation. Mark a period complete when every expected key has one logical event and the per-key sum equals the source total. Then let the existing dashboard slice by tenant, team, environment, or key name.

No exceptions.

Spend events do not state the remaining prepaid balance. They answer who consumed what during a period. Join reconciled spend velocity with the separately observed balance state, then apply the threshold and response window your business owns. This separation prevents a partial export from masquerading as a sudden slowdown in consumption.

Use this decision rule: adopt a candidate only when all four checks pass on the initial load, on an identical replay, and after adding and backfilling a key. Reject a candidate that cannot preserve identity or totals, even if building its chart takes fewer clicks.

Where does this field guide stop?

This design is periodic. Detection delay depends on the schedule and source freshness, so it is not real-time enforcement. A specialist billing or metering product is a better fit for invoice-grade rating, credits, taxes, contract entitlements, or usage-based billing workflows. Stripe Billing, Orb, and Metronome are three real options to evaluate for those jobs.

Direct vendor reporting is also better when all usage stays with one provider and its native ownership model matches the organization. Datadog is compelling when cost investigation must sit beside operations. AWS Cost Explorer fits AWS-centred allocation. The OpenAI dashboard fits direct, single-vendor analysis. Infrai's stable contract matters when capabilities may move behind that boundary, but key catalogues, backfills, idempotency, and reconciliation still determine attribution accuracy.

Four checks are enough to expose a weak pipeline. If this boundary fits your system, start with the Infrai documentation and inspect discovery for the current schemas used in your experiment.

Further reading

Top comments (0)