AI agents · cost allocation · FinOps · vendor API spend · multi-tenant

AI agent cost allocation: attributing vendor API spend to agents, users, and features

When AI agents call Stripe, Twilio, and Resend using shared API keys, every call is attributed to a single account. Your Stripe dashboard shows $4,200 in payment intent fees this month — but you have no way to know whether that's from your billing automation agent, your customer onboarding flow, or a loop that fired the same charge 40 times. Vault key labels give every proxied call a structured cost attribution tag, enabling you to answer "how much did the billing agent cost this week?" with a single query — without manual log parsing.

TL;DR

Design your vault key labels as a structured string: {tenantId}:{agentType}:{feature}:{runId}. Every API call proxied through Keybrake is tagged with the issuing vault key's label. Query the audit log with GROUP BY label_prefix to get per-tenant, per-agent-type, or per-feature spend breakdowns. Use daily spend caps per vault key as budget reservations to prevent any single label from consuming a disproportionate share of the monthly vendor budget.

Why shared keys create cost allocation blind spots

Three types of AI agent vendor cost are impossible to attribute without per-run credential tagging:

Cost questionShared key answerVault key label answer
How much did tenant A's agent spend on Stripe this week? Unknown — all tenants share one key Query audit log WHERE label LIKE 'tenantA:%'
Which agent type caused the spike on Tuesday? Unknown — Stripe shows timestamps, not caller identity Query WHERE label LIKE '%:billing-agent:%' AND date = Tuesday
Is the $400 Twilio bill from the notification agent or the onboarding agent? Unknown — one Twilio account, one bill GROUP BY label_prefix on the audit log for the billing period
Did any single run spend more than $50? Requires Stripe log scraping + manual correlation Query audit log WHERE label LIKE '%:{runId}' GROUP BY run

Designing vault key labels for cost allocation

Labels are free-form strings — design them as structured tags you can parse:

def build_vault_key_label(
    tenant_id: str,
    agent_type: str,   # e.g. "billing-agent", "onboarding-agent", "notify-agent"
    feature: str,      # e.g. "invoice-create", "trial-charge", "sms-send"
    run_id: str,       # unique per agent invocation
) -> str:
    # Colon-separated for easy GROUP BY LIKE queries
    # Max 120 chars to fit in most logging systems
    return f"{tenant_id}:{agent_type}:{feature}:{run_id}"[:120]

# Examples:
# acme_corp:billing-agent:invoice-create:run_7f3k9
# globex:onboarding-agent:trial-charge:run_4p2m1
# initech:notify-agent:sms-send:run_8q5x3

Issuing labeled vault keys per agent invocation

import os
import httpx

async def issue_agent_vault_key(
    tenant_id:   str,
    agent_type:  str,
    feature:     str,
    run_id:      str,
    vendor:      str,
    cap_usd:     int,
    ttl:         str = "30m",
) -> dict:
    label = f"{tenant_id}:{agent_type}:{feature}:{run_id}"

    resp = httpx.post(
        "https://api.keybrake.com/v1/keys",
        headers={"Authorization": f"Bearer {os.environ['KEYBRAKE_TOKEN']}"},
        json={
            "label":             label,
            "vendor":            vendor,
            "daily_usd_cap":     cap_usd,
            "allowed_endpoints": allowed_endpoints_for_feature(feature),
            "expires_in":        ttl,
        },
        timeout=3.0,
    )
    resp.raise_for_status()
    return resp.json()


def allowed_endpoints_for_feature(feature: str) -> list[str]:
    return {
        "invoice-create":  ["/v1/invoices", "/v1/invoiceitems"],
        "trial-charge":    ["/v1/payment_intents", "/v1/payment_intents/*"],
        "subscription":    ["/v1/subscriptions", "/v1/subscriptions/*"],
        "sms-send":        ["/2010-04-01/Accounts/*/Messages"],
    }.get(feature, [])

Querying spend by attribution dimension

The Keybrake audit log is queryable via the REST API. Each entry includes the vault key label, vendor, endpoint called, and cost parsed from the vendor response:

import httpx
from collections import defaultdict
from datetime import datetime, timedelta

def get_spend_by_tenant(days: int = 7) -> dict[str, float]:
    since = (datetime.utcnow() - timedelta(days=days)).isoformat() + "Z"

    resp = httpx.get(
        "https://api.keybrake.com/v1/audit",
        headers={"Authorization": f"Bearer {os.environ['KEYBRAKE_TOKEN']}"},
        params={"since": since, "limit": 10000},
    )
    entries = resp.json()["entries"]

    spend_by_tenant = defaultdict(float)
    for entry in entries:
        # Label format: tenantId:agentType:feature:runId
        parts     = entry["label"].split(":", maxsplit=1)
        tenant_id = parts[0] if parts else "unknown"
        cost_usd  = entry.get("cost_usd", 0.0)
        spend_by_tenant[tenant_id] += cost_usd

    return dict(sorted(spend_by_tenant.items(), key=lambda x: x[1], reverse=True))


def get_spend_by_agent_type(days: int = 7) -> dict[str, float]:
    entries = fetch_audit_entries(days)
    spend   = defaultdict(float)
    for entry in entries:
        parts      = entry["label"].split(":")
        agent_type = parts[1] if len(parts) > 1 else "unknown"
        spend[agent_type] += entry.get("cost_usd", 0.0)
    return dict(spend)

Spend cap as budget reservation

Each vault key's daily_usd_cap is a budget reservation: the maximum any single agent run can spend on that vendor in a day. Design caps at each attribution level:

# Cap hierarchy: platform-level > tenant-level > per-run
# Use the most specific level that makes business sense

def calculate_vault_key_cap(
    tenant_plan:     str,   # "free" | "hobby" | "team" | "scale"
    agent_type:      str,
    feature:         str,
) -> int:
    # Base cap by tenant plan
    base = {"free": 5, "hobby": 50, "team": 200, "scale": 1000}[tenant_plan]

    # Agent-type multipliers — billing agent can spend more than notification agent
    multiplier = {
        "billing-agent":    1.0,
        "onboarding-agent": 0.5,
        "notify-agent":     0.1,
        "reporting-agent":  0.0,  # reporting agents should call read-only endpoints only
    }.get(agent_type, 0.5)

    return max(1, int(base * multiplier))


# Result examples:
# free plan, billing-agent:    $5/day
# hobby plan, billing-agent:   $50/day
# team plan, notify-agent:     $20/day
# scale plan, billing-agent:   $1,000/day

Showback vs chargeback model

ModelWhat it doesWhen to use
Showback Show each team/tenant how much vendor API spend their agents generated — no billing impact Internal FinOps reporting; multi-team platforms; pre-chargeback phase
Chargeback Add vendor API spend to the tenant's invoice — they pay for what their agents use SaaS with usage-based billing; platforms where agents do work on tenants' behalf
Budget alerts Alert when a tenant or agent type exceeds a threshold before month-end Complement to showback; prevents surprise overage bills

For chargeback in a usage-based SaaS, integrate the Keybrake audit log query into your billing metering pipeline. Query nightly, aggregate spend by tenant, and add line items to the metering system (Stripe Meters, Lago, OpenMeter):

async def sync_tenant_vendor_spend_to_billing(billing_date: str) -> None:
    spend_by_tenant = get_spend_by_tenant(days=1)

    for tenant_id, cost_usd in spend_by_tenant.items():
        if cost_usd < 0.01:
            continue  # Skip sub-cent amounts

        # Report to Stripe Meters (or your billing system)
        stripe.billing.meter_event.create(
            event_name="vendor_api_spend",
            payload={
                "value":      str(int(cost_usd * 100)),  # cents
                "stripe_customer_id": tenant_stripe_id(tenant_id),
            },
            timestamp=int(datetime.utcnow().timestamp()),
        )

Get early access

Related questions

How do I handle Stripe refunds and credits in the cost allocation?

Stripe refunds appear as negative charges in the API response — the payment intent amount is negative or the refund object has a positive amount. In the Keybrake audit log, refunds are tracked as separate entries with cost_usd set to a negative value (the reversal of the original charge cost). When aggregating spend by tenant, sum all entries including negative ones — the net spend per tenant will reflect refunds. For chargeback purposes, use the net per billing period: if an agent created a $50 charge and a $20 refund in the same period, the chargeable amount is $30. If refunds from one period relate to charges from a prior period, track them as credits in your billing system rather than reopening the prior period's invoice.

Can I allocate costs by feature within a single agent run?

Yes, but it requires issuing separate vault keys for each feature within a run rather than one vault key for the entire run. This increases issuance overhead (one API call to Keybrake per feature instead of per run), but gives you feature-level attribution in the audit log. The tradeoff: per-run keys are simpler and have lower overhead; per-feature keys give finer-grained attribution. A hybrid is to issue one vault key per agent invocation with a label that includes the agent type and feature, and then use run IDs in the label for per-run attribution. If you need per-feature attribution within a single run, issue feature-specific vault keys for the features that have high or variable costs, and use a single key for low-cost features.

How do I handle cost allocation for multi-vendor agent runs?

Issue one vault key per vendor per run: one for Stripe, one for Twilio, one for Resend. Each key gets the same structured label (same tenant, agent type, feature, run ID) but a different vendor. The Keybrake audit log will have entries for each vendor with the cost per vendor separately. When aggregating spend, group by label first (to get total cost per run), then by vendor within the label (to see Stripe vs Twilio vs Resend breakdown per run). This also gives you per-vendor caps independently: a single run can be capped at $50 Stripe and $5 Twilio without needing a combined cross-vendor cap that's harder to reason about.

Further reading