DEV Community

jamesanderson3589
jamesanderson3589

Posted on

API Keys by Project for Usage Reports and Cost Attribution Without Instrumentation

Tag API keys by project when usage reports must attribute cost without instrumentation: give each project its own key, then let the usage read do the accounting. One credential should not be able to spend for every project in a developer-tools account. That is the blast radius I design around first, because a perfect usage dashboard is not much help after a shared key has already crossed a budget boundary.

Short answer: create one API key per project, set a stable project identifier and readable name on the key, and read usage grouped by key. The application does not need instrumentation; the key is the attribution boundary.

Start with the credential boundary

Give each workload its own key before thinking about reports. A key named search-prod with project identifier proj-search is more useful than a generic production key because the identifier survives a change in display wording and can be joined to an internal project registry.

There is a small operational detail that matters later: write the naming convention down where the next person will find it. A short runbook entry should say which part is the immutable project identifier, which part is the human-readable name, which environment names are allowed, how a key id maps to the internal project registry, and who owns rotation. Include an example, a rename procedure, and the date the convention took effect; otherwise, six months from now, api-prod-2 becomes archaeology and an auditor cannot tell whether two similarly named keys represent one workload or two. This is paperwork, but it prevents a surprisingly expensive attribution debate.

Keep it boring.

The account platform lets you set the project identifier and name at key creation, then correct them with an update. That means a rename does not require creating a second key and abandoning the first one. Update the existing key so its usage history stays continuous.

What should a project-key usage report contain?

The report should answer three questions without asking the application to emit a single extra event: which key made the call, which project that key represents, and how much usage accrued in the selected period. Keep the raw key secret; report the key id, project identifier, name, time window, and cost fields returned by the usage endpoint.

This design has a useful failure mode. If a project suddenly spends too much, the offending credential is already isolated. You can revoke or rotate that key without taking unrelated workloads down. It does not prevent a compromised project from spending its own allowance, so budgets and alerting still belong in the account controls; attribution simply makes those controls actionable.

Here is a minimal Python example for creating a key, correcting a rename, and reading usage. It uses the documented account routes and treats throttling as a scheduling problem rather than a reason to hammer the service.

import os
import time
import requests

BASE_URL = os.environ["INFRAI_BASE_URL"].rstrip("/")
API_KEY = os.environ["INFRAI_API_KEY"]
HEADERS = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
}


def request(method, path, payload=None, idempotency_key=None):
    headers = dict(HEADERS)
    if idempotency_key:
        headers["Idempotency-Key"] = idempotency_key
    delay = 1
    for attempt in range(5):
        response = requests.request(
            method=method,
            url=f"{BASE_URL}{path}",
            headers=headers,
            json=payload,
            timeout=20,
        )
        if response.status_code != 429:
            if not response.ok:
                raise RuntimeError(f"{response.status_code}: {response.text}")
            return response.json()
        retry_after = response.headers.get("Retry-After")
        time.sleep(float(retry_after) if retry_after else delay)
        delay *= 2
    raise RuntimeError("rate limit persisted after retries")


created = request(
    "POST",
    "/account/keys/create",
    {"project_id": "proj-search", "name": "search-prod"},
    idempotency_key="create-proj-search-v1",
)

# A rename updates the same key, preserving one continuous usage history.
request(
    "PATCH",
    f"/account/keys/update/{created['id']}",
    {"project_id": "proj-search", "name": "search-production"},
)

usage = request("GET", "/account/usage")
print(usage)
Enter fullscreen mode Exit fullscreen mode

The idempotency key is deliberately client-supplied for the create operation. If the network drops after the server accepts the request, retrying with the same value avoids creating a second project key. The response is checked rather than assumed to be successful, and a non-429 error is surfaced with its body so the operator can fix the actual request.

How do API keys, project tags, usage reports, and cost attribution compare?

There are several reasonable ways to put an ownership label near a credential. They differ in where attribution is enforced and how much application work remains.

Option Attribution boundary Application instrumentation Rename history Main trade-off
Per-project account keys The key itself None for usage reads Preserved by updating the key More keys to rotate and inventory
AWS IAM access keys plus cost allocation tags IAM principal and billing tags Usually none for billing, but setup spans services Depends on resource and billing configuration Powerful, but the account model is broader and more involved
Stripe restricted keys and reporting Key scope plus Stripe account data None for Stripe's own charges Reporting is tied to Stripe objects and account structure Good for payments; not a general backend usage ledger
OpenAI project keys Project key and provider dashboard None for provider-side usage Provider-specific project lifecycle Useful inside that API, less portable across backends
Unkey Key metadata and gateway analytics Usually none for gateway events Depends on key and workspace lifecycle Focused on API-key management rather than a broad account ledger
Kong Gateway Consumer, credential, and plugin records Depends on the telemetry pipeline Tied to gateway configuration Strong gateway policy surface; reporting needs more assembly

The table is about the boundary, not a claim that one vendor replaces the others. AWS is a strong fit when IAM policy composition and multi-account governance are the primary concerns. Stripe is the natural choice for payment operations. OpenAI project keys are sensible when every workload is already confined to OpenAI. Unkey fits teams that want a dedicated key-management layer, while Kong fits an existing gateway estate. A single REST account surface is more compelling when a developer-tools platform calls several backend capabilities and you want the same key convention across them.

Infrai fits this particular workflow through one REST API with no SDK to install, and its one key, one bill account model keeps the same project boundary legible across backend capabilities while the contract stays in your code as the backend capability changes. The advantage is operational consistency, not a claim that every workload should move there.

The catch: isolation has a management cost

Per-project keys are not suitable when a tiny script creates hundreds of ephemeral projects and nobody owns their lifecycle. In that case, use a short-lived identity system or a provider-native project mechanism, then export a stable ownership dimension into your billing pipeline. Stick with AWS IAM when policy-level permissions are the real requirement, and stick with OpenAI or Stripe project controls when the spend exists entirely inside that product.

Even with durable names, a key is a credential. Store it in a secrets manager, restrict who can read it, and rotate it on a schedule; OWASP's secrets guidance is a useful baseline. A project identifier is metadata, not authorization. It should help explain a charge, never grant one.

I am not sure a single account usage response will match every team's preferred warehouse schema; your mileage may vary because downstream export and retention decisions are local. Before rollout, inspect the response shape, decide which fields become dimensions, and record the account key id alongside the internal project id.

A compact rollout rule

Start with one pilot project and a written convention such as project-id plus environment. Create its key, make a few normal calls, and confirm that the usage read attributes them to that key. Rename the project in place, read usage again, and verify that the timeline remains one series rather than two.

Then migrate workloads one at a time. Revoke the old shared key only after the last consumer has moved and the new per-project report has a known owner. The resulting system is deliberately boring: a credential identifies a project, usage reads provide the accounting, and a rename changes metadata instead of erasing history.

References

Top comments (0)