Part 2: Amazon Bedrock cost attribution with Amazon Athena and CUDOS
A single model call can cost anywhere from fractions of a cent to several dollars depending on the model and token counts. That makes knowing who made each call essential for cost control. AWS now includes per-request caller identity in the Cost and Usage Report (CUR 2.0), so platform, engineering, and FinOps teams can trace Amazon Bedrock inference spend back to users, roles, and projects when attribution is available.
What you get and why it matters
Turn on one CUR option and you get per-request principal attribution where available. The CUR 2.0 feature titled “Include caller identity (IAM principal) allocation data” fills the line_item_iam_principal column and exposes IAM-principal tags under keys prefixed with iamPrincipal/. That answers the practical questions AWS framed: “Who is calling which models, and how much are they spending?” and “How much did the engineering team spend on Bedrock this month?”
“Include caller identity (IAM principal) allocation data.”, AWS blog
Key effect: rows that were a single aggregate can expand into multiple rows, one per IAM principal, increasing CUR row counts and S3 size. AWS warns: “Important: Enabling IAM principal data increases CUR file sizes because usage that was previously a single row is now expanded into multiple rows, one for each IAM principal that contributed to the usage.”
Quick checklist to get started (what to configure)
- Enable CUR 2.0 and turn on Include caller identity (IAM principal) allocation data in the Billing console.
- Set time granularity to Hourly and CUR file versioning to Overwrite existing report.
- Expect up to 24 hours for the first CUR 2.0 file and for iamPrincipal/* keys to appear in S3 after activity begins.
- Use the AWS sample repo for automation and examples: https://github.com/aws-samples/sample-cur-iam-principal-bedrock-tracking
Important tagging caveat
If you rely on IAM principal tags to roll costs up to teams or projects, note this: session tags must be passed at AssumeRole time (and allowed via IAM permissions) to appear as iamPrincipal/<key>. Tag visibility depends on whether tags are attached to the principal itself or propagated as session tags when a role is assumed.
From CUR parquet to answers: three Athena query patterns you’ll use
CUR 2.0 writes parquet files that work well with Athena. Below are three practical patterns, discovery, exploration, and production rollups, and notes to keep query costs tiny.
1) Discovery: find what iamPrincipal tags exist (UNNEST)
If you don’t know all iamPrincipal/* keys, explode the tag map to discover keys and values. CUR parquet stores IAM-principal tags as a map, so verify the exact column name and map type in your Glue table before running the query.
Example (adjust column name and types to match your Glue schema):
SELECT principal_key, principal_value, SUM(line_item_unblended_cost) AS total_cost
FROM your_cur_table_name
CROSS JOIN UNNEST(cast(line_item_iam_principal_tag_map AS map(varchar, varchar))) AS t (principal_key, principal_value)
WHERE line_item_product_code IN (‘AmazonBedrock’, ‘AmazonBedrockService’)
GROUP BY principal_key, principal_value;
Use this to find stray high-cardinality keys and to build a curated list of tag keys you’ll actually use for rollups.
2) Exploration: who is calling which models
Quick test SQL to surface non-null principals for Bedrock rows (replace your_cur_table_name):
SELECT line_item_iam_principal, line_item_usage_type, line_item_unblended_cost FROM your_cur_table_name WHERE line_item_product_code in (‘AmazonBedrock’, ‘AmazonBedrockService’) AND line_item_iam_principal IS NOT NULL LIMIT 10;
Sample output (illustrative):
- arn:aws:sts::123456789012:assumed-role/ChatApp/session-1 | USW2-anthropic.claude-opus-4-8-mantle-cache-write-tokens-standard | 1629.5 | $11.2029
- arn:aws:sts::123456789012:assumed-role/DocProcessor/batch-7 | USW2-Claude4.6Sonnet-output-tokens | 68.579 | $1.131
- arn:aws:sts::123456789012:assumed-role/ClaudeCode/chat | USW2-Claude4.6Sonnet-cache-write-input-token-count | 831.74 | $3.4309
- arn:aws:iam::123456789012:user/alice | USW2-Claude4.6Sonnet-input-tokens | 17.33 | $0.0572
For assumed-role ARNs (arn:aws:sts::account:assumed-role/RoleName/session-name), the session-name is often meaningful. Use regex extraction to capture RoleName and session-name for rollups instead of keeping raw session strings.
3) Production rollups: tag-driven chargebacks and summaries
If you tag principals with chargeback dimensions like project, team, or cost-center, CUR will include those values under iamPrincipal/<key>. Aggregate by those tags for showback or chargeback reports.
Sample aggregation output (illustrative):
- project: data-science | USW2-Claude4.5Sonnet-cache-write-input-token-count | total_tokens: 433.893 | total_cost: 1.789808625
- project: data-science | USW2-Claude4.6Sonnet-cache-read-input-token-count | total_tokens: 5372.659 | total_cost: 1.77297747
- project: engineering | USW2-Claude4.5Sonnet-input-tokens | total_tokens: 29.481 | total_cost: 0.0972873
- project: engineering | USW2-Claude4.5Sonnet-output-tokens | total_tokens: 31.102 | total_cost: 0.513183
Tip: prefer stable, low-cardinality tag keys like team, project, and cost-center. Avoid session IDs as a primary chargeback key.
Real-world compare: ChatApp vs DocProcessor (illustrative)
Comparing services side-by-side turns visibility into action: move bulk batch work to cheaper models, reserve higher-cost models for latency-sensitive or quality-critical flows, or rework prompts to reduce output tokens.
Example multi-service aggregation (sample data, illustrative):
- arn:aws:sts::123456789012:assumed-role/ChatApp/session-1 | USE1-Claude4.6Sonnet-output-tokens | total_usage: 4, 800, 000 | total_cost: $72.00
- arn:aws:sts::123456789012:assumed-role/ChatApp/session-1 | USE1-Claude4.6Sonnet-input-tokens | total_usage: 2, 900, 000 | total_cost: $8.70
- arn:aws:sts::123456789012:assumed-role/DocProcessor/batch-7 | USE1-NovaLite-output-tokens | total_usage: 6, 100, 000 | total_cost: $1.46
- arn:aws:sts::123456789012:assumed-role/DocProcessor/batch-7 | USE1-NovaLite-input-tokens | total_usage: 3, 200, 000 | total_cost: $0.19
These figures are illustrative, but they show a common reality: model choice and usage patterns produce very different cost-per-million-token profiles. Use cost-per-million-tokens as a KPI when selecting models and redesigning flows.
Athena costs, optimizations, and engine nuance
Athena Serverless standard pricing charges $5 per TB scanned with a 10 MB minimum per query. That 10 MB minimum means many targeted queries end up costing roughly $0.00005 each (10 MB billed at $5/TB ≈ $0.00005). AWS notes: “You pay only for the queries that you run.”
“You pay only for the queries that you run.”, AWS blog
Pricing and available engines can vary by region and Athena deployment (Serverless vs provisioned). Also account for Glue ETL and CTAS costs if you run transformation jobs or create aggregated tables.
Practical query hygiene:
- Partition by billing_period (and optionally region or line_item_product_code) and use hive partition projection. Always include WHERE billing_period = ‘YYYYMM’ to limit scanned files.
- Filter early on line_item_product_code IN (‘AmazonBedrock’, ‘AmazonBedrockService’) to avoid scanning the full CUR dataset, verify product_code strings in your CUR schema.
- Create pre-aggregated daily/monthly Glue tables (or CTAS results) for queries you run frequently to avoid repeated scans.
- Use discovery queries sparingly and capture their output into smaller summary tables for repeated access.
CUDOS dashboards: a ready-made visualization layer
The Cloud Intelligence Dashboards (CUDOS) framework provides prebuilt visuals that map neatly to Athena outputs. As shown in the AWS samples, CUDOS version 5.8 includes a Bedrock-focused section in the AI/ML tab with IAM-principal cost attribution support: grouping by IAM Principal, IAM Principal Tags, Model/Resource Group, and Region; cost-per-million-tokens tracking; and interactive drill-downs.
Cost-per-million-tokens is the most actionable trend line. It shows where prompt or model changes raise or lower token cost, and the dashboard filters let you find the teams and sessions responsible for spikes. Confirm the exact CUDOS release and deployment steps for your environment before assuming feature parity.
Operational playbook, what to do first (and next)
- Enable IAM principal allocation in CUR and wait up to 24 hours for the first report and tag activation.
- Run the discovery UNNEST query to enumerate iamPrincipal/* keys, then decide the canonical tag keys you’ll use for rollups.
- Validate visibility with a scoped Athena query that includes billing_period and product_code filters.
- Build daily aggregated tables (Glue/CTAS) for common rollups: by principal, by project tag, by model.
- Hook CUDOS (or your BI tool) to the aggregated tables for interactive dashboards and cost-per-million-tokens tracking.
- Apply S3 lifecycle rules to CUR files and restrict access to CUR buckets, Glue/Athena catalogs, and dashboards with least-privilege policies (Lake Formation or bucket policies as appropriate).
Caveats, governance, and real-world limits
Visibility is powerful and partial. Plan for these limits and guardrails:
- Proxies and shared roles mask users. If your application calls Bedrock under a single service role (API gateway, backend proxy, or third-party), CUR will show that role rather than the end user. For true per-user attribution you must surface user identity to AWS (for example, via session tags at AssumeRole time) or perform mapping outside CUR.
- High-cardinality explosion. Session-level identifiers and thousands of unique principals multiply CUR rows and storage. Mitigations: restrict which tag keys you record, aggregate to daily rollups, and drop session-level granularity once you no longer need it for chargeback.
- Data governance and privacy. Per-request ARNs are sensitive operational metadata. Restrict access to S3/Glue/Athena and dashboards, define retention and masking rules in your FinOps policy, and consider Lake Formation or S3 bucket policies for fine-grained control.
- Not real-time. CUR is batch-exported (hourly granularity recommended). For near-real-time cost guardrails, supplement CUR with CloudWatch metrics, application telemetry, or streaming counters from your service layer.
Cleanup commands (from the tutorial)
If you follow the sample walkthrough and want to remove the Glue/Athena artifacts and results, the tutorial shows these commands:
aws glue delete-table –region us-east-1 –database-name your_cur_table_name –name curexport
aws glue delete-database –region us-east-1 –name your_cur_table_name
aws s3 rm s3://<your-cur-bucket>/athena-results/ –recursive
“There are no crawlers, AWS Lambda functions, or schedules to delete. Partition projection means the only ongoing cost is S3 storage for the CUR files themselves, which is typically pennies per month.”, AWS blog
Key takeaways, quick Q&A
- How do I capture per-request IAM principal attribution for Bedrock usage?
Enable the CUR 2.0 option labeled “Include caller identity (IAM principal) allocation data” and wait for the first report (up to 24 hours). Remember that tags appear only after the principal has made calls and that session tags must be passed at AssumeRole time to show up under iamPrincipal/<key>.
- How do I analyze Bedrock costs by user, role, or project?
Query CUR parquet with Athena. Use line_item_iam_principal for per-principal rows, reference iamPrincipal/<key> for known principal tags, or UNNEST the principal-tag map to discover keys dynamically. Aggregate by usage type or model and store frequent rollups as daily/monthly summary tables.
- How much will Athena queries cost for these analyses?
Athena Serverless standard pricing bills at $5 per TB scanned with a 10 MB minimum, so many tightly-scoped queries run at about $0.00005 due to the 10 MB floor. Verify your Athena engine/region pricing and account for Glue ETL and CTAS costs for any transformation jobs.
- What operational risks should I plan for?
High-cardinality CUR growth, masked attribution when using shared roles/proxies, and sensitive ARNs in billing data. Mitigate with tag governance, aggregation pipelines, retention policies, least-privilege access, and complementary telemetry for near-real-time guardrails.
- What’s a concrete metric I should track?
Cost per million tokens by model and by principal, track it daily and alert when any model/principal’s cost-per-million deviates significantly (for example, >20% vs a 7-day baseline). This couples cost visibility with actionable model-selection and prompt-engineering decisions.
Credits
How-to details, configuration labels, and the sample materials referenced here are sourced from the AWS treatment of CUR + Bedrock attribution and the accompanying samples authored by Abhi Shivaditya (Principal Solutions Architect), Brenno Passanha (Senior Technical Account Manager, Cloud Financial Management), and Yash Yamsanwar (Machine Learning Architect). For automation and SQL examples see: https://github.com/aws-samples/sample-cur-iam-principal-bedrock-tracking.