{"slug": "invoice-grade-attribution-on-amazon-bedrock-with-agentgateway", "title": "Invoice-Grade Attribution on Amazon Bedrock with Agentgateway", "summary": "Agentgateway, an open-source AI gateway, now supports invoice-grade cost attribution for Amazon Bedrock by passing caller identity and session tags through AWS Security Token Service (STS) AssumeRole calls, enabling per-request attribution in AWS Cost and Usage Report (CUR) 2.0 and Cost Explorer. The feature, introduced in pull requests #2435 and #2447, allows static or dynamic (CEL expression) session tags derived from validated JWT claims or request headers, ensuring that spend is labeled at request time and cannot be backfilled. This closes the gap where gateway traffic previously collapsed into a single IAM identity on AWS bills.", "body_md": "A gateway is both the best and the worst thing that happens to your AI bill. The best, because every model request flows through one place, so tagging and budgets become possible at all. The worst, because on AWS all of that traffic typically authenticates through one IAM role, so the provider sees exactly one caller. Finance opens the bill and Amazon Bedrock is a single line item. Your gateway dashboard knows who spent what, but it knows it as an estimate, computed from token counts and price sheets, and finance does not reconcile against estimates. Finance reconciles against the numbers AWS actually bills.\n\nWe call attribution **invoice-grade** when the “who-is-this-for” tag survives all the way to the cloud provider’s billing data, so the numbers finance slices are the numbers AWS actually bills. Anything that stops in the gateway’s internal records, however accurate, is an estimate of the bill rather than the bill.\n\nAWS now [attributes Bedrock inference costs to the IAM principal that made the call](https://aws.amazon.com/blogs/machine-learning/introducing-granular-cost-attribution-for-amazon-bedrock/), per request, in Cost and Usage Report (CUR) 2.0 and Cost Explorer. For traffic that arrives through a gateway, the documented pattern is per-caller sessions. The caller’s identity rides as the AWS Security Token Service (STS) `RoleSessionName`\n\n, which lands in `line_item_iam_principal`\n\n. Business dimensions like team and cost center ride as STS session tags, which surface as cost allocation tags.\n\nGateway traffic did not benefit. Agentgateway called AssumeRole with a role ARN and an SDK-generated random session name, so every caller through a given role collapsed into one identity, in AWS CloudTrail and on the bill. It looked something like this:\n\nSo let’s close that gap. As of pull requests [#2435](https://github.com/agentgateway/agentgateway/pull/2435) and [#2447](https://github.com/agentgateway/agentgateway/pull/2447), the AssumeRole backend auth accepts two optional fields: a session name and a list of session tags. The identity the gateway already established now rides the credential exchange into AWS, and the picture changes to this:\n\nIn config, the simplest version is two static values on the backend:\n\n```\nassumeRole:\n roleArn: arn:aws:iam::123456789012:role/bedrock-invoke\n sessionName: checkout-service\n tags:\n - key: team\n value: data-science\n - key: cost-center\n value: \"12345\"\n```\n\nThis covers the topology where each team has its own route and role. The route’s spend now arrives on the bill already labeled. When the fields are unset, behavior is unchanged.\n\nStatic tags are the change. The important extension is that the values can also be derived dynamically, from identity the gateway has already validated. The common enterprise architecture is not one route per team. It is thousands of apps and users behind one shared route, where the dimensions that matter exist only per request, in a validated JSON Web Token (JWT) claim or in the metadata of the key the gateway issued. So a tag value can also be a Common Expression Language (CEL) expression, evaluated against each request.\n\n```\nassumeRole:\n roleArn: arn:aws:iam::123456789012:role/bedrock-invoke\n tags:\n - key: Team\n expression: 'request.headers[\"x-team\"]'\n - key: App\n expression: 'request.headers[\"x-app\"]'\n - key: User\n expression: 'jwt.sub'\n - key: CostCenter\n value: \"12345\"\n```\n\nThe header examples are the shortest and easiest to read, but the ones doing the real work are `jwt.sub`\n\nand its siblings. These are values proven by a token the gateway itself validated, or assigned by the operator on the key it issued, rather than trusted from whatever the caller chose to send. A tag someone can fat-finger is a tag someone will fat-finger, and a month of misattributed spend cannot be repaired afterwards.\n\nIt’s important to remember that attribution is stamped at request time and cannot be backfilled. Which is why this implementation fails closed. If an expression errors or produces an empty or invalid value, the request is rejected before the STS call is even made. Nothing unattributed reaches Bedrock — that’s the goal.\n\nActivate the tag keys as cost allocation tags, wait around 24 hours, and the loop closes. Cost Explorer groups Bedrock spend by Team, App, User, CostCenter, or any other attribution key you’ve chosen. CUR rows carry the calling principal and the tags next to the dollar amounts AWS charges. Chargeback per team, per app, and where you need it per user 1, read straight off AWS’s own billing data, with the gateway’s own dashboard demoted to what it should be: a fast preview of numbers the bill later confirms.\n\nThe key benefit is not merely better dashboards. It is that finance can finally reconcile AI usage to the numbers AWS actually bills.\n\nAnd that property is rarer than it sounds. I went looking for it before contributing this, across every gateway I could get my hands on, and as of this writing agentgateway is the only shipping gateway where an operator-resolved tag reaches the AWS bill. Some stop at a static role or a configurable session name; most keep attribution in their own store. Attribution that survives into the provider’s billing records, rather than stopping at a dashboard, is what makes agentgateway unique in this corner of the stack today.\n\n*A month of Bedrock traffic, reconciled: the gateway’s per-request meter on the left, the CUR lines AWS billed on the right, joined on the cost_center session tag. Figures are illustrative of a representative deployment; the columns, the join key, and the reconciliation are real CUR 2.0 and real gateway output.*\n\nThis post is AWS-shaped on purpose. Each cloud carries attribution differently. In Google Vertex AI, the equivalent information rides as labels on the native `generateContent`\n\nrequest, and the native path is already supported. Today, those labels are pass-through: whatever your clients send is what reaches Google Cloud billing. The remaining step is to give the gateway the same control it now has on AWS, so attribution is determined by an identity the gateway has validated rather than a value the caller simply asserts.\n\nOnce that piece is in place, the model becomes the same across both clouds: establish identity at the gateway, carry it through the provider’s authentication or request metadata, and let the provider’s own billing system record it.\n\nThat is ultimately what invoice-grade attribution means. Not another usage dashboard or another number to reconcile. The identity that was established at the edge survives all the way to the invoice and into CloudTrail, so months later you can trace a request back to who made it and what happened along the way.[2](#fn:2)\n\nIf you run agentgateway in front of Bedrock, your next billing period should be attributed instead of anonymous.\n\n*My name is Anton Braverman. I run AI platform infrastructure at a regulated enterprise and contributed the session-tag support described in this post.*\n\nA note on cardinality: per-user attribution isn’t free, on either side of the API. On the gateway side, every distinct tag set is its own STS session, which turns the assume-role credential cache into a high-cardinality map; the cache is bounded with single-flight fetches, so a burst of first requests for the same identity coalesces into one AssumeRole call. On the AWS side, every distinct principal and tag set expands into its own line items in the CUR, so billing files grow with the number of identities, and AWS’s own guidance for [IAM principal cost allocation](https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/iam-principal-cost-allocation.html) is to keep tag values low-cardinality: teams and cost centers, not session IDs or per-request GUIDs. Tag per user where the chargeback/logs question genuinely needs it, per team everywhere else. STS limits (50 tags, key and value length and charset) are validated at config load for static values and per request for dynamic ones, so a bad tag fails with a clear error rather than an STS 400 mid-flight. [↩︎](#fnref:1)\n\nA note on the security benefits of working this way: the same session identity that reaches the bill also reaches AWS CloudTrail. That means cybersecurity teams can investigate incidents by tracing a request back to its verified identity behind the gateway, with a granularity you cannot have when every caller shares one anonymous session. [↩︎](#fnref:2)", "url": "https://wpnews.pro/news/invoice-grade-attribution-on-amazon-bedrock-with-agentgateway", "canonical_source": "/blog/2026-08-19-invoice-grade-attribution-bedrock/", "published_at": "2026-08-19 00:00:00+00:00", "updated_at": "2026-08-20 14:15:00.846983+00:00", "lang": "en", "topics": ["ai-infrastructure", "ai-tools", "developer-tools"], "entities": ["Agentgateway", "Amazon Bedrock", "AWS", "AWS Security Token Service (STS)", "AWS Cost and Usage Report (CUR) 2.0", "AWS Cost Explorer", "IAM", "JWT"], "alternates": {"html": "https://wpnews.pro/news/invoice-grade-attribution-on-amazon-bedrock-with-agentgateway", "markdown": "https://wpnews.pro/news/invoice-grade-attribution-on-amazon-bedrock-with-agentgateway.md", "text": "https://wpnews.pro/news/invoice-grade-attribution-on-amazon-bedrock-with-agentgateway.txt", "jsonld": "https://wpnews.pro/news/invoice-grade-attribution-on-amazon-bedrock-with-agentgateway.jsonld"}}