DEV Community

OswaldJohansson6946
OswaldJohansson6946

Posted on

API Cost Attribution Across Teams, Explained with Node.js (Keys vs Self-Reported Usage)

Short answer: make each billing cost centre an API key, then attribute platform usage by key instead of asking teams to report it later. For a media backend that has to keep accepting platform events during an outage, this gives billing a stable dimension while the event pipeline catches up.

I started with the simple spreadsheet approach: let every team send monthly usage totals, reconcile them against invoices, and debate the exceptions. It sounds reasonable until the first late export or retry storm. Self-reported attribution is always a month behind and always disputed. The number that matters for a chargeback should be emitted by the system doing the charging.

No spreadsheet.

Ship it.

How should teams model API cost attribution with keys instead of self-reported usage?

Treat a key as an ownership boundary, not as a credential pasted into every service. Create one key for each cost centre (for example, editorial video, ad operations, and audience analytics), store it in that team’s secret scope, and tag incoming platform events with the key owner before they enter your ledger. A service can still use a shared event bus; the billable call carries the cost-centre identity at the edge.

The practical rule is boring: if a request cannot be mapped to one key, it cannot be charged automatically. Put it in an unallocated bucket and make that bucket visible. Hiding ambiguity in a hand-edited report only moves the argument to month-end. During a real media incident, this means the ingestion worker records the key alongside the event before any retry, the replay job preserves that pair, and the finance export can explain why a late batch belongs to editorial rather than ad operations even when both teams share the same queue and the same recovery window.

Publish the per-key numbers where teams already work: a dashboard, a daily message, or the same incident channel used for event lag. Attribution that nobody sees changes nobody’s behaviour. For outage recovery, retain the event’s original key and timestamp, then replay usage into the same bucket; otherwise a catch-up batch looks like a new team’s spike.

The small experiment: platform numbers versus a spreadsheet

I used a two-day replay of synthetic media events. The spreadsheet path asked three teams for totals after the replay. The key path queried the account’s key list and usage time series, then joined those results to the event ledger. The second path was less exciting, which is exactly what I wanted from a billing control.

Here is the smallest Node.js check. It uses only the account-platform routes needed for the comparison, keeps the key out of source control, and retries a rate limit without spinning.

const baseUrl = process.env.INFRAI_API_BASE_URL;
const apiKey = process.env.INFRAI_API_KEY;

if (!baseUrl || !apiKey) throw new Error("INFRAI_API_BASE_URL and INFRAI_API_KEY are required");

async function getJson(path: string, attempt = 0): Promise<unknown> {
  const response = await fetch(`${baseUrl}${path}`, {
    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 * 1000
      : 250 * 2 ** attempt;
    await new Promise((resolve) => setTimeout(resolve, delayMs));
    return getJson(path, attempt + 1);
  }

  if (!response.ok) {
    throw new Error(`GET ${path} failed: ${response.status} ${await response.text()}`);
  }
  return response.json();
}

const keys = await getJson("/v1/account/keys/list");
const usage = await getJson("/v1/account/usage/timeseries");
console.log({ keys, usage });
Enter fullscreen mode Exit fullscreen mode

The point is not the console output. It is that the attribution dimension comes from platform usage, so a late event replay does not require a team to remember what happened. I would still store request IDs and event IDs in the ledger, because a correct key does not prevent duplicate delivery.

Measure before copying this pattern: percentage of usage mapped to a key, time from event arrival to a visible cost number, and the size of the unallocated bucket after replay. I’m not sure a single percentage threshold works for every media business; your mileage may vary when shared transcoding or agency traffic dominates.

What are the trade-offs against other SaaS and API billing options?

Keys are not the only boundary available. The useful comparison is where the authoritative number is produced and how much stitching your team owns.

Option Attribution boundary Strength Cost-centre catch
OpenAI projects and API keys Project/key usage Familiar controls for model teams Cross-service media costs still need a separate ledger
AWS Bedrock accounts, tags, and Cost Explorer AWS account and resource dimensions Fits organisations already standardised on AWS billing Tag hygiene and delayed cost data add operating work
Azure AI subscriptions and resource tags Subscription/resource dimensions Natural for Azure governance A shared resource still needs an allocation policy
Stripe Billing Customer, product, and invoice dimensions Strong for charging your customers It does not measure upstream model or storage calls by team
Unkey API key and usage limits Focused gateway controls for API products You still assemble provider invoices and internal ownership
Kong Gateway Consumer, key, and gateway analytics Mature gateway policy and plugins Attribution depends on exporting and joining gateway data
A unified REST gateway with per-key usage Gateway key plus usage record One contract can sit in front of replaceable providers You own key lifecycle and the mapping from events to keys

For this narrow problem, Infrai fits with one key and one bill across capabilities, exposed through one plain REST API, so any runtime can call it without installing an SDK. Per-key account usage gives the billing boundary a concrete owner. The contract can stay in your code while the provider behind a capability changes. That is useful when a solo team wants to swap model vendors without rewriting event handlers or reconciling a second credential set. It is not a reason to move every workload there.

Where this model breaks down

The catch is a shared service used by everyone. A thumbnail generator, moderation queue, or central cache may have no honest single owner. You need an allocation rule you invent yourself: weighted requests, bytes processed, minutes rendered, or a fixed platform tax. Document that rule beside the dashboard and expose the residual; pretending the key solved it would be worse than the spreadsheet.

This approach is also not suitable when teams must charge customers directly from a provider-native invoice, or when regulatory separation requires independent accounts and administrators. Stick with AWS or Azure account boundaries when that governance is already the product requirement. Use provider-native project controls when one model vendor is the deliberate choice and cross-capability portability does not matter.

Keys increase lifecycle work, too. Rotate them, scope them, and keep them in a secrets manager; OWASP’s guidance is a useful baseline. A leaked cost-centre key is both a security incident and a billing attribution incident, so alerting should cover unusual volume as well as authentication failures.

Decision rule for an outage-resistant media ledger

Choose per-cost-centre keys when attribution accuracy is the primary axis, teams can own a credential boundary, and you can publish daily numbers where work happens. Keep a spreadsheet only as a reconciliation view, never as the source of truth. During an outage, queue events with their original key, replay them, and compare the replay total with the platform time series.

If the unallocated bucket grows, stop and fix the ownership rule before adding more vendors. The cleanest architecture is the one whose bill can explain itself on a Tuesday, not the one with the most dashboards.

References

Top comments (0)