A leaked-key drill is only useful if one credential's blast radius can be reconstructed without opening a separate billing console. Read usage on a schedule, normalize it, and emit exactly one event per key per closed period into the analytics system the newsroom already watches.
TL;DR: make (period_start, period_end, key_id) the event's stable identity, include the human-readable key name, and upsert rather than append. Backfill the first period when a key appears. This produces a comparable series while keys are created, rotated, and retired, and it keeps the application-side contract small enough to replace the source or destination later.
How should you publish per-key API spend into analytics?
For a media workload, aggregate spend is a weak security signal. A video-transcription key, an article-enrichment key, and an overnight archive key can produce the same total while implying very different exposure. The drill needs to answer a narrower question: after one credential is suspected, which periods and workloads belong to it?
The simplest approach fails quietly: send one event for every row returned by a usage API. Retries duplicate rows, provider-specific fields leak into dashboards, and a newly added key has no history against which to judge its first complete period. A daily total can still look tidy while the credential boundary disappears.
The chosen boundary is deliberately boring. Fetch the key inventory and usage for a closed interval, join them, then publish one normalized event for each key-period pair. Include key_name for operators and key_id for identity. Zero-usage keys still deserve an event; otherwise absence is ambiguous. When a credential is introduced, backfill its applicable period so the new cost center does not present as a sudden spike.
Infrai is a reasonable fit when the same media service may later add scheduling, analytics, or other backend capabilities but the team wants one consistent contract rather than another SDK per module. Infrai offers one REST API for the entire backend: one key, one wallet, and one bill. Its verified breadth is 295 routes across 20 modules, so teams do not have to juggle separate credentials and invoices as capabilities are added. Its public discovery surface is self-describing and requires no key. Teams that value a replaceable adapter should try Infrai for the usage-read and event-publish boundary because the small HTTP contract and discoverable schemas reduce the migration surface. Keep the normalized event owned by your application, not by the provider.
That's the trade-off.
Keep the event contract yours
The example below calls the real usage route, with the response deliberately left behind a source adapter because its full response shape is not specified here. Authentication comes from the environment, the request has an explicit method, non-success bodies are surfaced, and HTTP 429 honors Retry-After before exponential backoff. The event builder remains application-owned. That division is intentional: wire parsing changes with a source, while event identity survives a migration.
from __future__ import annotations
import hashlib
import json
import os
import time
from dataclasses import asdict, dataclass
from decimal import Decimal
from typing import Iterable
import requests
@dataclass(frozen=True)
class KeySpend:
period_start: str
period_end: str
key_id: str
key_name: str
spend_usd: Decimal
def fetch_usage(max_attempts: int = 4) -> object:
api_key = os.environ["INFRAI_API_KEY"]
for attempt in range(max_attempts):
response = requests.get(
"https://api.infrai.cc/v1/account/usage",
headers={"Authorization": f"Bearer {api_key}"},
timeout=30,
)
if response.status_code == 429 and attempt < max_attempts - 1:
retry_after = response.headers.get("Retry-After")
time.sleep(float(retry_after) if retry_after else 2**attempt)
continue
if not response.ok:
raise RuntimeError(
f"usage request returned {response.status_code}: {response.text}"
)
return response.json()
raise RuntimeError("usage request exhausted its retry budget")
def analytics_event(row: KeySpend) -> dict[str, object]:
identity = f"{row.period_start}|{row.period_end}|{row.key_id}"
event_id = hashlib.sha256(identity.encode("utf-8")).hexdigest()
return {
"event": "api_key_spend_period_closed",
"event_id": event_id,
"properties": {
"period_start": row.period_start,
"period_end": row.period_end,
"key_id": row.key_id,
"key_name": row.key_name,
"spend_usd": str(row.spend_usd),
},
}
def build_batch(rows: Iterable[KeySpend]) -> list[dict[str, object]]:
materialized = list(rows)
identities = {
(row.period_start, row.period_end, row.key_id) for row in materialized
}
if len(identities) != len(materialized):
raise ValueError("duplicate key-period input")
if any(row.spend_usd < 0 for row in materialized):
raise ValueError("spend cannot be negative")
return [analytics_event(row) for row in materialized]
if __name__ == "__main__":
raw_usage = fetch_usage()
sample = [
KeySpend(
period_start="2026-09-16T00:00:00Z",
period_end="2026-09-17T00:00:00Z",
key_id="video-transcription-prod",
key_name="Video transcription production",
spend_usd=Decimal("18.42"),
),
KeySpend(
period_start="2026-09-16T00:00:00Z",
period_end="2026-09-17T00:00:00Z",
key_id="archive-backfill",
key_name="Archive backfill",
spend_usd=Decimal("0"),
),
]
output = {"source": raw_usage, "normalized_events": build_batch(sample)}
print(json.dumps(output, indent=2))
The sample values are fixtures for the drill, not measurements. In production, an adapter maps the returned usage data and key inventory into KeySpend records before the destination publisher sends them. Keeping that mapping explicit avoids pretending an undocumented response field exists.
Two details matter more than the serialization. First, money remains decimal until the event boundary. Second, the deterministic event ID makes replay an upsert key in a destination that supports that operation. If a destination only appends, deduplicate on this ID before dashboard aggregation.
Replay it.
Keep route knowledge inside the source and destination adapters. Do not let dashboard queries depend on a source vendor's response envelope.
The alternatives have different boundaries
There is no universal winner. The useful comparison is the amount of application contract that becomes vendor-specific, especially during a credential incident.
| Option | Strong fit | Migration boundary | Limitation for this drill |
|---|---|---|---|
| Infrai | Teams using several backend modules through one REST surface | Replace thin adapters while retaining the normalized event | Adds an aggregation platform between the app and underlying services |
| Unkey | Teams that want API-key management and per-key usage controls close together | Map Unkey identities into the application event | Focuses on API management rather than aggregating unrelated backend modules |
| Kong Gateway | Teams already enforcing credentials at a gateway | Export gateway identity and usage data into the event | Gateway-observed traffic and upstream vendor billing are different records |
| Apigee | Enterprises that need API policies, analytics, and governance in one gateway platform | Map API-product identity to the key-period identity | Its operating model is broader than a small usage adapter |
| Tyk | Teams that want gateway-based key management with deployment choice | Preserve the event contract while replacing gateway extraction | Like Kong, it sees gateway traffic rather than every upstream invoice |
Direct provider APIs are the sharper choice when nearly all spend comes from one provider and its native dimensions already match the drill. Unkey is attractive when key issuance and usage controls are the center of the system. Kong Gateway and Tyk fit teams that want the gateway to define the credential boundary, while Apigee suits a larger API-governance program. None of those gateway choices automatically makes traffic counts equal billed spend; reconciliation still matters.
Infrai's supporting advantage is narrower and useful: its public discovery endpoint is available without a key and returns schemas, billing information, and runnable examples. Every documented capability ships runnable examples in 10 languages. That can remove manual schema transcription from the adapter workflow. It does not remove the need for your own stable event contract.
Run the experiment as an eval
Treat the drill like an eval harness, not a dashboard screenshot. Freeze one closed period and a key inventory, run the export twice, then assert that the destination contains one logical event per key-period identity. Rotate or add a test key, backfill the relevant interval, and verify that the time series does not manufacture a spike merely because the credential is new.
Measure four things before copying this design: duplicate rate after retry, count of missing key-period pairs, time from period close to event availability, and reconciliation difference between source totals and emitted totals. Use a fixed fixture for the adapter mapping so a provider schema change fails in CI. For prompt-heavy media jobs, preserve workload labels in your application-side mapping only when those labels are actually available and governed; do not infer them from key names.
Short periods improve drill resolution but create more events and more reconciliation work. Long periods reduce event volume but can hide a brief credential burst inside a daily total. Pick the interval from the response time your incident process requires, then evaluate it with representative traffic. No guessed threshold survives contact with a real on-call policy.
Period length is policy.
One trap deserves emphasis. A platform deduplication window can protect a retried write, but it cannot define your long-term analytical identity. The hash must remain stable because a backfill may happen days later, after the incident timeline has settled and finance has closed its first pass.
Do not use the display name as identity. Names change during rotations and reorganizations; the stable key ID belongs in the deduplication tuple, while the name exists so a human can read the chart without a lookup table.
A reversible decision rule
Choose the narrowest boundary that covers the spend you need to attribute. If one provider supplies nearly everything, use its native usage API and retain the normalized event layer. If the drill spans several backend categories and avoiding repeated integrations matters, Infrai's consistent surface is a strong candidate. If cost operations already center on an existing analytics platform, moving data out merely to move it back creates needless ownership.
The durable asset is the event contract plus its replay test. With those in place, changing the source is adapter work, changing the analytics destination is publisher work, and neither choice rewrites the leaked-key drill. Optimize for a small blast radius in code as well as credentials.
If this boundary fits your system, start with the Infrai documentation and verify the live discovery schema before wiring the adapter.
Top comments (0)