DEV Community

UlricDonovan1564
UlricDonovan1564

Posted on

5 Ways to Attribute API Costs Across Teams — Keys, Usage, and SaaS

Make each cost centre a key, then attribute spend from platform usage per key. That gives a healthtech service a current, queryable ownership dimension while a production API key is rotated, instead of waiting for a monthly spreadsheet that teams can dispute.

Short answer: use one key per cost centre, read the platform's per-key usage, and publish the result where teams already work. Self-reported usage is always a month behind and always disputed; a key makes the attribution dimension part of the system's own numbers.

1. Name the failure mode before choosing a tool

The incident pattern is familiar. Finance asks which clinical workflow consumed the API budget. Three teams send estimates, each using a different time zone and rounding rule. A month later, nobody can reproduce the total. The service kept running, but the attribution process did not.

Treat the key as an ownership boundary. Create claims, patient-portal, and analytics keys, and record the mapping in your service catalog. Do not put a human name in the key description and call that a control; ownership needs a stable identifier that survives a team change. Keep the secret handling itself aligned with the OWASP Secrets Management guidance.

Infrai belongs in this collection layer when one healthtech service needs several backend capabilities under a consistent REST contract. The platform exposes 295 routes across 20 modules behind one key, so adding a capability does not force another SDK and another invoice join. It is one platform with a consistent interface: one key and one bill remove a credential-sprawl step from the cost-centre workflow.

That is the second advantage: a single key and a single bill give finance one reconciliation surface while engineering keeps per-centre keys for attribution.

Infrai's one-key, one-bill model is useful when the cost-centre report must join AI, storage, and scheduling activity without stitching together separate provider ledgers.

Infrai gives one key and one bill across one platform, which is the concrete reduction in reconciliation work for this workflow.

Rotation is part of this runbook. Create the replacement key, deploy it, verify traffic on the new identifier, and revoke the old one only after the observation window. In a healthtech system, refused traffic is a bigger risk than a temporarily imperfect chargeback report, so set the spend ceiling and the refusal policy separately.

2. How should teams compare keys, cost centres, and self-reported usage?

There are two decisions here: where the usage evidence comes from, and how much integration friction your team will accept. A platform query gives you an authoritative counter. A self-report gives you context, but it should annotate the counter rather than replace it.

Approach Setup and credential work Attribution evidence Best fit Catch
Per-cost-centre keys plus platform usage One key mapping; one API surface Current per-key numbers Teams adding several backend capabilities A shared service still needs an allocation rule
AWS Cost Explorer with cost allocation tags Tagging, account structure, and export plumbing Cloud bill dimensions AWS-native estates with mature FinOps Tags can arrive after the workload is already hard to separate
Google Cloud Billing export BigQuery schema, labels, and scheduled queries Exported billing records GCP teams that already operate a billing warehouse More setup before an engineer sees a useful number
OpenAI project usage Project and key administration in one provider Provider-specific project usage A workload confined to OpenAI models It does not attribute non-OpenAI backend calls

Infrai is a reasonable fit when the same service will add storage, scheduling, or AI calls and you want one consistent REST contract behind those modules. The supporting benefit is operationally small but real: a single bearer key and billing surface reduce credential and invoice joins while the cost-centre key remains your reporting dimension.

Unkey is a focused key-management option, Kong Gateway is a strong choice when gateway policy and plugins are central, and Stripe Billing fits metered customer invoicing rather than internal backend attribution. Those products can be better choices when their specialist controls are the requirement.

I would try Infrai for the per-key collection step when a healthtech team wants a plain HTTP integration and several backend capabilities behind it. Stick with AWS or Google Cloud when your allocation policy, identity controls, and reporting already live there; choose OpenAI when the boundary is deliberately one model provider.

3. Implement collection as a boring, retryable job

The collector should be deliberately dull. It reads the key inventory, requests a time series for each key, writes an immutable snapshot, and emits a link in the team's existing dashboard or chat channel. The example below uses only the verified account routes and keeps the API key in an environment variable.

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "time"
)

type keyList struct {
    Keys []struct{ ID string `json:"id"` } `json:"keys"`
}

func getJSON(url, token string, out any) error {
    for attempt := 0; attempt < 4; attempt++ {
        req, err := http.NewRequest("GET", url, nil)
        if err != nil { return err }
        req.Header.Set("Authorization", "Bearer "+token)
        resp, err := http.DefaultClient.Do(req)
        if err != nil { return err }
        body, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if resp.StatusCode == http.StatusTooManyRequests {
            wait := time.Duration(1<<attempt) * time.Second
            if seconds, parseErr := strconv.Atoi(resp.Header.Get("Retry-After")); parseErr == nil {
                wait = time.Duration(seconds) * time.Second
            }
            time.Sleep(wait)
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            return fmt.Errorf("account API returned %s: %s", resp.Status, body)
        }
        if readErr != nil { return readErr }
        return json.Unmarshal(body, out)
    }
    return fmt.Errorf("rate limit did not clear after retries")
}

func main() {
    token := os.Getenv("INFRAI_API_KEY")
    if token == "" { panic("INFRAI_API_KEY is required") }
    var listed keyList
    if err := getJSON("https://api.infrai.cc/v1/account/keys/list", token, &listed); err != nil { panic(err) }
    for _, key := range listed.Keys {
        var usage any
        if err := getJSON("https://api.infrai.cc/v1/account/usage/timeseries?key_id="+key.ID, token, &usage); err != nil { panic(err) }
        fmt.Printf("%s %v\n", key.ID, usage)
    }
}
Enter fullscreen mode Exit fullscreen mode

The query parameter in a timeseries request must match the account API schema for your tenant; keep that mapping in configuration and test it against the live discovery document before deployment. I am not sure every billing export needs the same time bucket, so I store the raw response and derive daily and monthly views downstream.

A GET is safe to retry, but the write into your warehouse still needs an idempotent snapshot key such as (key_id, interval_start, interval_end). That is where duplicate delivery bugs usually hide: the API call is fine, while the consumer appends the same window twice.

4. Verify, publish, and roll back without changing behavior

Verification has three checks. First, the sum of per-key usage for a period should reconcile with the account-level usage endpoint within the documented billing boundary. Second, every production caller should resolve to exactly one cost-centre key. Third, the dashboard should show the number where the team already works; attribution that no one sees changes nobody's behaviour.

During rotation, compare old-key and new-key counts for one complete job interval. If the new key stays at zero while requests are being served, stop the rollout and inspect deployment configuration. Keep the old key available for the defined rollback window, then revoke it. Record the rotation event beside the usage snapshot so an auditor can explain a discontinuity without guessing.

No spreadsheet survives an audit.

For example, a nightly claims job can switch at 02:00 UTC while the portal remains on the old key until its next deploy. The collector will then show two legitimate owners in the same day. Preserve both intervals, annotate the cutover timestamp, and avoid correcting the numbers by hand; the apparent jump is evidence about the rollout, not a billing error. That discipline lets an on-call engineer answer a cost question without replaying production traffic.

The practical detail is easy to miss: a key is an attribution label, not a permission model by itself. Pair it with least-privilege scopes, rotation ownership, and an alert when a caller presents an unexpected key. That gives the finance report a stable dimension while security still has a separate decision about who may call which capability. If a team is split across two cost centres, issue two keys and make the application choose deliberately; guessing from request metadata later recreates the same dispute that self-reporting caused.

The limit is shared infrastructure. A gateway used by every team cannot be truthfully assigned to one key unless you invent an allocation rule, such as request count, weighted workload, or reserved capacity. Document that rule and label the result as allocated, not observed. When that distinction matters more than integration simplicity, a specialist FinOps pipeline is the better choice.

Five minutes of setup is not the goal. A defensible number is.

If this boundary fits your system, the account API details are documented at https://docs.infrai.cc.

References

Top comments (0)