Artificial Intelligence

Tokenomics at scale: Track and cap Amazon Bedrock spend per IAM principal with CUR 2.0 and AWS Budgets

Generative AI spend is hard to attribute. Before an organization expands AI access, CFOs and CTOs usually want three answers: what do we spend per person or per team, can we cap it, and how much work does it take to get there.

In our previous post, Tokenomics at Scale: How Jamf Built Real-Time Spend Enforcement for Amazon Bedrock, we explained a custom enforcement loop built on AWS IAM Identity Center, Amazon Athena over Amazon Bedrock model invocation logs, and AWS Lambda. This approach gives near-real-time control, but it requires code you then own and operate.

This post shows how to get per-identity Amazon Bedrock cost visibility and enforceable spend limits using only AWS-native features, with no application code to write for the common case. The approach relies on three things: IAM principal attribution, which Amazon Bedrock records automatically; AWS Cost and Usage Reports (CUR) 2.0 or AWS Cost Explorer for reporting; and AWS Budgets with budget actions for enforcement. By the end, you will have per-identity cost reporting and an automatic guardrail that restricts Amazon Bedrock access when a threshold is crossed.

Solution overview

The solution has three parts: attribute, report, and enforce.

Attribute. Amazon Bedrock captures the AWS Identity and Access Management (IAM) principal identity, the IAM user or role, for every inference request. This happens automatically, with no change to your Amazon Bedrock model inference calls. If several people share one role, you add per-user detail with session tags passed at role assumption time. If each person or application has its own role, principal tags on the role are enough.

Report. Activate those tags as cost allocation tags, then read the data either in Cost Explorer for a console-only view, or in a CUR 2.0 data export for detailed line-item analysis and SQL queries.

Enforce. Create an AWS Budget scoped to Amazon Bedrock and filtered to one identity or tag value, then attach a budget action that applies a Deny IAM policy when the threshold is crossed.

The following sections show each part in more detail, alongside the implementation steps.

Prerequisites

To deploy this solution, you need:

AWS Billing Console access is managed separately from most IAM permissions. Confirm you have it before starting.

Walkthrough

Before configuring anything, check what you already have. Amazon Bedrock records caller identity for every inference request automatically. Open Cost Explorer, filter Service to Amazon Bedrock, and group by the available identity or tag dimension. For accounts where each application or person already uses a distinct IAM role, this may be all the visibility you need.

To configure additional visibility:

  • Step 1 gives you identity-level visibility.
  • Steps 2 through 4 add tag dimensions and detailed reporting.
  • Step 5 adds enforcement.

Step 1: Decide how identity maps to your users

How you tag depends on how your IAM principals are organized.

One role per person or per application. Attach principal tags directly to each IAM role or user. These are static and apply to every request from that principal. Use the IAM console, or the AWS CLI:

aws iam tag-role \
  --role-name BedrockDeveloperRole-alice \
  --tags Key=user,Value=alice@example.com Key=team,Value=platform Key=CostCenter,Value=CC-1001

A shared role used by several people, or a gateway. Principal tags cannot distinguish users on a shared role, because every session carries the same tags. Use session tags instead, passed when the role is assumed. Session tags are dynamic and can vary per session. If a session tag and a principal tag share a key, the session tag wins for that session.

The role’s trust policy must allow sts:TagSession. Session tags can be passed three ways:

  • sts:AssumeRole with --tags, for a gateway assuming a role per user or tenant.
  • AssumeRoleWithWebIdentity, with tags in the https://aws.amazon.com/tags claim of the OpenID Connect (OIDC) ID token (this is a claim name, not a URL you browse to).
  • AssumeRoleWithSAML, by mapping PrincipalTag:* attributes in the identity provider’s Security Assertion Markup Language (SAML) assertion.

If your organization already captures a claim like saml:sub for other purposes, that alone does not make it available here. Your identity provider must separately map that attribute to a PrincipalTag:* SAML attribute statement, and the role’s trust policy must allow sts:TagSession, before it flows through as a session tag. The two integrations draw from the same SAML assertion but are configured independently. Common tag keys are user, team, Department, CostCenter, and Environment. You can apply up to 50 principal or session tags per IAM user or role.

Principal tags attached to an IAM role compared with session tags passed at role assumption

Figure 1: Attribution with principal tags compared to session tags

Step 2: Activate cost allocation tags

Tags do not appear in billing data until you activate them.

  1. Open the AWS Billing and Cost Management Console.
  2. Choose Cost allocation tags.
  3. Filter by type IAM principal.
  4. Select your tag keys and choose Activate.

In an AWS Organization, activate IAM principal cost allocation tags in the management account (payer). Member accounts cannot activate them independently.

Two things to expect:

  • A tag key only appears in this list after the principal has made at least one Amazon Bedrock call, so make a test call first if the list looks empty.
  • It might take up to 24 hours for a newly applied tag key to appear for activation, then up to another 24 hours after you activate it before it begins populating billing data.

Activation does not apply retroactively by default, so activate tags before you need the data.

Step 3: Create a CUR 2.0 data export that includes caller identity

Skip this step if Cost Explorer is enough for your reporting.

  1. In the Billing and Cost Management console, choose Data Exports.
  2. Choose Create and create a CUR 2.0 (Standard) export.
  3. Under Additional export content, select Include caller identity (IAM principal) allocation data.
  4. Under Time granularity, select Hourly.
  5. Under Report data integration, select Amazon Athena. This sets the file format to Parquet and, importantly, delivers a setup script to your S3 bucket alongside the export data.
  6. Choose the destination Amazon S3 bucket.
  7. Run the delivered setup script (or the Glue crawler/DDL it defines) once. This is what actually creates the Glue Data Catalog table you’ll query in Step 4. The preceding console flow does not create it for you automatically.

If you already have a CUR 2.0 export created before enabling identity attribution, it will not include identity data retroactively. Create a new export with the caller identity option selected. In the export, principal and session tags appear as columns prefixed iamPrincipal/, and resource tags as resourceTags/. For example, a tag key team appears as iamPrincipal/team. AWS publishes a ready-made query set for this exact pattern in aws-samples/sample-cur-iam-principal-bedrock-tracking.

Step 4: Read the data

The following diagram compares the two ways to read the data: a console-only path through Cost Explorer, and a SQL path over the CUR 2.0 export with Amazon Athena.

Two reporting paths, the Cost Explorer console view and a SQL query over the CUR 2.0 export in Amazon Athena

Figure 2: Reporting with Cost Explorer compared to CUR 2.0 and Amazon Athena

Console path, no custom code. Cost Explorer, filtered to Amazon Bedrock and grouped by your tag key, gives per-team or per-user spend as a chart and table. This covers most recurring reporting needs and requires no query authoring.

SQL path, low code. Point Amazon Athena at the CUR 2.0 export to group spend by iamPrincipal/team and usage type:

SELECT
    tags['iamPrincipal/team'] AS team,
    line_item_usage_type,
    SUM(line_item_unblended_cost) AS cost
FROM cur2_view
WHERE product_servicecode IN ('AmazonBedrock', 'AmazonBedrockService')
AND billing_period = '2026-09'
GROUP BY 1, 2
ORDER BY 3 DESC

Things to note:

  • Service code: Amazon Bedrock spend can be recorded under more than one product_servicecode value, for example, AmazonBedrock and AmazonBedrockService.
  • Table name: The table name (cur2_view) is whatever the setup script from Step 3 named it when it created the Glue table. Check the script or your Glue catalog if it differs. Column names match the CUR 2.0 Parquet schema regardless of table name.
  • Billing-period column. Confirm how your export exposes the period. Some exports use billing_period (YYYY-MM). Others partition on a start-date or year/month column.

Two notes worth carrying into any query you write.

  • Count all token types: Amazon Bedrock bills four token types, each at a different rate: input, output, cache read, and cache write. Each is a separate line item with its own usage type and unit price, so summing only input and output undercounts. The gap is largest for workloads that use prompt caching heavily. Refer to the AWS document Understanding Amazon Bedrock pricing in CUR for the latest information.
  • Match the rate to the line item, not the model: Amazon Bedrock pricing in CUR is determined by three things: the token type, the service tier, and whether the request was routed through cross-Region inference. In-Region and cross-Region requests are priced differently, and service tier is reflected in the usage type as well. The usage type carries a suffix indicating cross-Region routing and service tier, so if your workloads mix these, match the rate to each line item rather than applying one rate per model. Refer to the CUR usage type examples for examples.

Step 5: Add enforcement with AWS Budgets and budget actions

AWS Budgets performs the attach, so there is no custom code for this workflow. Budget actions attach to a specific IAM user, group, or role. This isolates one person’s access only if that person already has their own role. If your Amazon Bedrock traffic runs through a role that many people share, a Deny action attached to that role blocks everyone on it, not only the person who crossed the threshold. The following diagram explains the enforcement workflow.

AWS Budgets threshold triggering a budget action that attaches a Deny IAM policy

Figure 3: Enforcement with AWS Budgets and budget actions

AWS Budgets can run an action when a threshold is crossed. Available actions include applying a custom IAM policy, applying a service control policy (SCP), and targeting Amazon Elastic Compute Cloud (Amazon EC2) or Amazon Relational Database Service (Amazon RDS) instances. For this use case, the IAM policy action is the relevant one.

First, create the Deny policy the action will attach. This example denies the higher-cost models while leaving a lower-cost model available, so the user can keep working:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Action": [
        "bedrock:InvokeModel",
        "bedrock:InvokeModelWithResponseStream"
      ],
      "Resource": [
        "arn:aws:bedrock:*::foundation-model/[HIGHER-COST-MODEL-ID]"
      ]
    }
  ]
}

Replace [HIGHER-COST-MODEL-ID] with the model ID you want to deny in your environment.

Then set up permissions and the budget itself.

There are two distinct roles here, and the AWS managed policy only covers one of them:

Your own user or role (the one configuring the budget) needs the AWS managed policy AWSBudgetsActionsWithAWSResourceControlAccess. It grants budgets permissions plus the conditioned iam:PassRole needed to hand your budget action role to AWS Budgets.

The IAM role AWS Budgets assumes to perform the action is not covered by a single named managed policy for this use case. Create it with a trust policy that allows the principal budgets.amazonaws.com to assume it (scoped with aws:SourceArn and aws:SourceAccount conditions), and a permissions policy granting iam:AttachRolePolicy and iam:DetachRolePolicy (and organizations:AttachPolicy and organizations:DetachPolicy if you are also using SCPs). Refer to Setting up a role for AWS Budgets to run budget actions for the grantable permissions.

  1. Create the budget action role described earlier.
  2. Create a cost budget scoped to Amazon Bedrock, filtered to the identity or tag value you want to cap.
  3. In Configure alerts, add an alert threshold. Choose Actual for realized spend or Forecasted to act before the spend lands.
  4. Under Attach actions, choose Add Action, select the budget actions role, and choose the IAM policy action. Select the Deny policy and the IAM user, group, or role to attach it to.
  5. For automatic enforcement, choose Yes to run the action automatically. Choose No to require a human to approve each action from the Alert details page.
  6. Configure at least one notification so the affected user is told why access changed.

You can attach multiple actions to a single threshold, and multiple thresholds to one budget. That is how you build tiers: for example, deny the premium model at 80 percent and the mid-tier model at 100 percent, while never denying the low-cost model.

Only the management account can apply SCPs, and from the management account you can apply an SCP to another account.

Considerations

AWS Budgets is not near-real-time: Budget data refreshes up to three times a day, so spend can overshoot a threshold before the action runs.

Cost allocation tags are not retroactive: Expect up to 24 hours for a newly applied tag key to appear, then up to another 24 hours after you activate it before it begins populating billing data.

CUR does not contain per-request line items: Both classic CUR and CUR 2.0 aggregate Amazon Bedrock cost by usage type, operation, and pricing/resource attributes over an hour or a day.

IAM principal fields require CUR 2.0: The legacy CUR format does not include them. Per-token cost breakdowns are available in both formats.

Session tags are bound at AssumeRole time, not per request. Their values are constant for every call made with that session’s credentials, and they surface only as aggregated billing data. Session tags are not written to model invocation logs. Logs capture the caller’s identity.arn. To distinguish users on a shared session at the request level, use a per-user RoleSessionName, or set the user in request metadata on each call.

Budget actions attach to IAM users, groups, and roles. If your users are federated and have no standing IAM identity, the action needs a role to target. This is why the pattern works best when each person or application has its own role. If your users share a role or are federated without their own role, see our previous post for a per-user enforcement approach.

A shared gateway role collapses attribution by default. With a large language model (LLM) gateway, the identity captured is the gateway’s execution role.

New models need attention. Usage types are model-specific. When you enable a new model, confirm it appears in your queries and budget filters as expected rather than assuming it is covered.

Decision framework

Requirement Recommended path
Quick reporting Cost Explorer grouped by identity
Per-team reporting Cost Explorer with activated tags (Steps 1, 2, and 4)
Detailed line-item analysis CUR 2.0 export (Steps 1 through 4)
Enforcement AWS Budgets with budget actions (Steps 1 and 5)

Cleanup

To avoid ongoing charges, delete all the resources you created.

Conclusion

In this post, you learned how to get per-identity Amazon Bedrock cost visibility and enforceable spend limits using only AWS-native features, with no application code to write for the common case. As generative AI adoption scales, the organizations that win will be the ones that can answer the tokenomics question with confidence: what do we spend per person or per team, can we cap it, and how much work does it take to get there.

Now, it’s your turn to start exploring. We are here to help, and if you need further assistance, reach out to AWS Support and your AWS account team.


About the author

Arun Chandapillai

Arun Chandapillai

Arun is a Senior Cloud Architect passionate about helping customers accelerate IT modernization through business-first cloud adoption strategies. He specializes in building and deploying AI and Generative AI solutions, from agentic workflows to production-ready applications, on AWS. Arun is an automotive enthusiast and avid speaker who is passionate about giving back and believes ‘you get back what you give.’

Cami Persson

Cami Persson

Cami is a Principal Account Manager at AWS, partnering with Independent Software Vendors to drive value creation through cloud adoption and AI integration. She focuses on accelerating partners’ revenue growth and platform modernization at scale. Based in Minneapolis, Cami resides with her husband.

Aditya Mettu

Aditya Mettu

Aditya is a Technical Account Manager at AWS, supporting Independent Software Vendor (ISV) customers. He helps engineering teams run generative AI on AWS reliably and cost-effectively, with a focus on AI FinOps: cost visibility, governance, and optimization for services such as Amazon Bedrock. He enjoys turning cloud spend data into practical guardrails that let teams scale AI adoption without runaway costs.

Andre Bernardo

Andre Bernardo

Andre is a builder at the intersection of Cloud Infrastructure, FinOps, and AI design, specializing in systems that scale cleanly and stay out of the engineer’s way. He builds the cloud automation and generative AI platform that lets Jamf adopt AI safely, without slowing teams down.

Andrew Dunham

Andrew Dunham

Andrew is a FinOps Engineer focused on cloud cost optimization, automation, and financial governance. He combines a background in finance, economics, and data science with cloud engineering expertise to help teams understand, control, and optimize the cost of AWS and Generative AI workloads.

Levi McCormick

Levi McCormick

Levi is Director of Engineering with 25+ years in tech, spanning phone support, training and mentoring, architecture, application design, and customer engagement. He’s passionate about developer experience and leveraging cloud platforms so every engineer can be a 10x engineer.