See also (wiki): llm-cost-attribution · agentic-token-governance · ai-budget-cfo-decisions
Pillar: 19 — Agent Frameworks & Observability
Scope: Fortune 500 enterprise running LiteLLM as AI gateway routing to Amazon Bedrock
Research date: 2026-06-16
Contract strength: Official docs verified (primary); community-pattern (secondary)
CUR 2.0 (AWS Data Exports) is the correct instrument for invoice-accurate Bedrock spend by model and IAM identity. It does not produce per-request rows — the finest grain is per-usage-type per day. For per-prompt attribution (team, project, user) behind a gateway like LiteLLM, the correct architecture is:
- IAM principal attribution (CUR 2.0
line_item_iam_principal+iamPrincipal/tags) for dollar-level attribution by role/team → flows directly into billing - Request metadata (
X-Amzn-Bedrock-Request-Metadataheader orrequestMetadatabody field) for per-prompt token counts → flows only into invocation logs, not CUR - Invocation logs → Athena for per-call detail; join to CUR at model+usage-type grain for invoice reconciliation
IAM principal attribution became available April 8, 2026. It requires CUR 2.0 with INCLUDE_IAM_PRINCIPAL_DATA = TRUE enabled in the Data Exports configuration.
1. CUR 2.0 Column Structure for Bedrock
Core line-item columns (official docs verified)
| Column | Type | Bedrock-specific value / pattern |
|---|---|---|
line_item_product_code |
string | AmazonBedrock |
line_item_usage_type |
string | {REGION}-{MODEL}-{token-type}[-{tier}][-cross-region-global] — see table below |
line_item_operation |
string | InvokeModelInference or InvokeModelStreamingInference (as of Jan 2026; was generic Usage before) |
line_item_usage_amount |
decimal | Token count for the aggregation period (not request count) |
line_item_unblended_cost |
decimal | Dollar cost for the line item |
line_item_net_unblended_cost |
decimal | Cost after credits/discounts |
line_item_line_item_type |
string | Usage (on-demand); Tax; Credit; SavingsPlanCoveredUsage (if covered by Bedrock Savings Plan) |
line_item_usage_start_date |
timestamp | Start of the aggregation window (hourly or daily per export config) |
line_item_usage_end_date |
timestamp | End of the aggregation window |
line_item_resource_id |
string | Present only when INCLUDE_RESOURCES = TRUE; contains inference profile ARN if request used one |
line_item_iam_principal |
string | CUR 2.0 only, requires INCLUDE_IAM_PRINCIPAL_DATA = TRUE. IAM ARN of the Bedrock caller. See formats below. |
product_servicecode |
string | AmazonBedrock |
product_region |
string | AWS region code (e.g., us-east-1) |
product_from_location |
string | Human-readable region name (e.g., US East (N. Virginia)) |
tags |
map<string,string> | Nested map of all cost-allocation tag key-values with prefixes |
line_item_usage_type format patterns
{region}-{model}-{token-type} # standard tier, in-region
{region}-{model}-{token-type}-{tier} # priority or flex tier
{region}-{model}-mantle-{token-type}-standard # bedrock-mantle endpoint
{region}-{model}-{token-type}-cross-region-global # cross-region inference
Region prefix uses AWS billing region codes: USE1 = us-east-1, USW2 = us-west-2, EUW1 = eu-west-1, etc.
Concrete line_item_usage_type examples (official docs verified, 2026)
| Usage type | Model | Tier | Token type | Routing |
|---|---|---|---|---|
USE1-Claude4.6Sonnet-input-tokens |
Claude Sonnet 4.6 | Standard | Input | In-region |
USE1-Claude4.6Sonnet-output-tokens |
Claude Sonnet 4.6 | Standard | Output | In-region |
USE1-Claude4.6Sonnet-cache-read-input-token-count |
Claude Sonnet 4.6 | Standard | Cache read | In-region |
USE1-Claude4.6Sonnet-cache-write-input-token-count |
Claude Sonnet 4.6 | Standard | Cache write | In-region |
USE1-Claude4.6Sonnet-output-tokens-cross-region-global |
Claude Sonnet 4.6 | Standard | Output | Cross-region |
USE1-Claude4.6Opus-input-tokens |
Claude Opus 4.6 | Standard | Input | In-region |
USE1-Nova2.0Lite-input-tokens-flex |
Amazon Nova 2 Lite | Flex | Input | In-region |
USE1-gpt-oss-120b-output-tokens-priority |
OpenAI gpt-oss-120b | Priority | Output | In-region |
USE1-openai.gpt-oss-120b-mantle-input-tokens-standard |
OpenAI gpt-oss-120b | Standard | Input | In-region (mantle) |
Four token types — all must be summed for accurate reconciliation
| Token type | line_item_usage_type pattern |
Relative price vs input |
|---|---|---|
| Input tokens | *-input-tokens or *-mantle-input-tokens-* |
1x (baseline) |
| Output tokens | *-output-tokens or *-mantle-output-tokens-* |
~3–5x input price |
| Cache read tokens | *-cache-read-input-token-count |
~0.1x input price (significantly cheaper) |
| Cache write tokens | *-cache-write-input-token-count |
~1.25x input price (more expensive than input) |
Critical: If you only SUM input + output tokens, your totals will not match your bill for workloads using prompt caching. Cache read and write are separate line items with different unit prices.
line_item_iam_principal column formats
Available only in CUR 2.0 with INCLUDE_IAM_PRINCIPAL_DATA = TRUE (data starts April 8, 2026):
| Identity type | ARN format |
|---|---|
| IAM user | arn:aws:iam::123456789012:user/alice |
| IAM role (assumed) | arn:aws:sts::123456789012:assumed-role/AppRole/session-name |
| Federated user | arn:aws:sts::123456789012:assumed-role/Role/user@acme.org |
| Bedrock API Key | arn:aws:iam::123456789012:user/BedrockAPIKey-234s |
For LiteLLM gateway: Bedrock records the gateway’s IAM role as the caller. To get per-team or per-project attribution, the gateway must call STS AssumeRole per tenant with a per-tenant RoleSessionName or session tags. The resulting ARN (assumed-role/gateway-role/tenant-name) flows into line_item_iam_principal.
Tags column — iamPrincipal/ prefix
IAM principal tags appear in the tags map column with the prefix iamPrincipal/:
{
"iamPrincipal/department": "data-engineering",
"iamPrincipal/cost-center": "CC-1042",
"iamPrincipal/team": "finops-product",
"iamPrincipal/project": "customer-360",
"resourceTags/Environment": "prod",
"accountTag/CostCenter": "CC-1042"
}
Full tag prefix taxonomy in CUR 2.0 tags column:
| Prefix | Source |
|---|---|
resourceTags/ |
Tags on AWS resources (e.g., application inference profile ARN) |
iamPrincipal/ |
Tags on IAM users or roles making Bedrock calls |
userAttribute/ |
User attributes imported from IAM Identity Center |
accountTag/ |
Tags at the AWS account level |
costCategory/ |
Tags derived from AWS Cost Categories |
2. CUR 2.0 vs CUR 1.0 for Bedrock
| Capability | Legacy CUR 1.0 | CUR 2.0 (Data Exports) |
|---|---|---|
| Per-token type line items | Yes | Yes |
line_item_usage_type with model+token-type |
Yes | Yes |
line_item_operation with InvokeModelInference |
Yes (since Jan 2026) | Yes (since Jan 2026) |
line_item_iam_principal column |
No | Yes (requires INCLUDE_IAM_PRINCIPAL_DATA = TRUE, data from Apr 8, 2026) |
iamPrincipal/ tag prefix in tags |
No | Yes |
| Fixed schema (no monthly schema drift) | No | Yes — 125 fixed columns |
Nested tags map column |
No (flat columns) | Yes (also queryable as flat via dot operator) |
bill_payer_account_name |
No | Yes |
line_item_usage_account_name |
No | Yes |
| SQL table name in Athena | Varies by export name | COST_AND_USAGE_REPORT |
| Configurable time granularity | Hourly or daily | HOURLY, DAILY, or MONTHLY |
Key migration note: product['product_name'] in CUR 2.0 replaces product_product_name in legacy CUR. All product_* flat columns become product['key'] map-style references in CUR 2.0 unless you use the dot operator: product.servicecode.
IAM principal attribution is CUR 2.0 only. There is no equivalent in legacy CUR.
3. Athena Queries
Prerequisites
-- CUR 2.0 table is named COST_AND_USAGE_REPORT by default in Data Exports
-- Replace 'your_cur_database' with the Glue database name from your Data Export config
-- Filter on line_item_usage_start_date — table is partitioned by date; this dramatically reduces scan cost
Query 1: Bedrock spend by model and token type (daily)
SELECT
DATE_TRUNC('day', line_item_usage_start_date) AS usage_date,
line_item_usage_type,
-- Extract model name from usage type (everything between first and second '-' after region)
REGEXP_EXTRACT(line_item_usage_type, '^[^-]+-(.+?)-(input|output|cache)', 1) AS model,
CASE
WHEN line_item_usage_type LIKE '%-input-tokens%' THEN 'input'
WHEN line_item_usage_type LIKE '%-output-tokens%' THEN 'output'
WHEN line_item_usage_type LIKE '%-cache-read-input-token%' THEN 'cache_read'
WHEN line_item_usage_type LIKE '%-cache-write-input-token%' THEN 'cache_write'
ELSE 'other'
END AS token_type,
SUM(line_item_usage_amount) AS total_tokens,
SUM(line_item_unblended_cost) AS total_cost_usd
FROM your_cur_database.COST_AND_USAGE_REPORT
WHERE line_item_product_code = 'AmazonBedrock'
AND line_item_line_item_type = 'Usage'
AND line_item_usage_start_date >= DATE_ADD('day', -30, CURRENT_DATE)
GROUP BY 1, 2, 3, 4
ORDER BY usage_date DESC, total_cost_usd DESC;
Query 2: Bedrock spend by IAM principal (requires CUR 2.0 with IAM principal enabled)
SELECT
DATE_TRUNC('day', line_item_usage_start_date) AS usage_date,
line_item_iam_principal,
-- Extract session name from assumed-role ARN for human-readable identity
REGEXP_EXTRACT(line_item_iam_principal, 'assumed-role/[^/]+/(.+)$', 1) AS session_or_user,
line_item_usage_type,
SUM(line_item_usage_amount) AS total_tokens,
SUM(line_item_unblended_cost) AS total_cost_usd
FROM your_cur_database.COST_AND_USAGE_REPORT
WHERE line_item_product_code = 'AmazonBedrock'
AND line_item_line_item_type = 'Usage'
AND line_item_iam_principal IS NOT NULL
AND line_item_usage_start_date >= DATE_ADD('day', -30, CURRENT_DATE)
GROUP BY 1, 2, 3, 4
ORDER BY usage_date DESC, total_cost_usd DESC;
Query 3: Bedrock spend by team tag (iamPrincipal/team)
SELECT
DATE_TRUNC('day', line_item_usage_start_date) AS usage_date,
tags['iamPrincipal/team'] AS team,
tags['iamPrincipal/project'] AS project,
CASE
WHEN line_item_usage_type LIKE '%-Claude%' THEN 'Anthropic Claude'
WHEN line_item_usage_type LIKE '%-Nova%' THEN 'Amazon Nova'
WHEN line_item_usage_type LIKE '%-Titan%' THEN 'Amazon Titan'
WHEN line_item_usage_type LIKE '%-llama%' THEN 'Meta Llama'
ELSE 'Other'
END AS model_family,
SUM(line_item_usage_amount) AS total_tokens,
SUM(line_item_unblended_cost) AS total_cost_usd
FROM your_cur_database.COST_AND_USAGE_REPORT
WHERE line_item_product_code = 'AmazonBedrock'
AND line_item_line_item_type = 'Usage'
AND tags['iamPrincipal/team'] IS NOT NULL
AND line_item_usage_start_date >= DATE_ADD('day', -30, CURRENT_DATE)
GROUP BY 1, 2, 3, 4
ORDER BY usage_date DESC, total_cost_usd DESC;
Query 4: Invocation logs — per-prompt token detail with request metadata (S3 → Athena)
-- Prerequisite: CREATE EXTERNAL TABLE for Bedrock invocation logs in S3
-- S3 path pattern: s3://bucket/prefix/AWSLogs/{accountId}/BedrockModelInvocationLogs/{region}/
SELECT
DATE_TRUNC('hour', from_iso8601_timestamp(timestamp)) AS hour,
requestmetadata['team'] AS team,
requestmetadata['project'] AS project,
requestmetadata['environment'] AS env,
modelid,
identity.arn AS caller_arn,
COUNT(*) AS request_count,
SUM(input.inputtokencount) AS input_tokens,
SUM(output.outputtokencount) AS output_tokens,
-- Estimate cost; replace rates with current Bedrock pricing
SUM(input.inputtokencount) * 0.000003 AS est_input_cost_usd, -- Claude Haiku example rate
SUM(output.outputtokencount) * 0.000015 AS est_output_cost_usd
FROM bedrocklogsdb.bedrocklogs
WHERE dt >= '2026/06/01' -- partition filter: YYYY/MM/DD
GROUP BY 1, 2, 3, 4, 5, 6
ORDER BY hour DESC, input_tokens DESC;
Query 5: CUR + invocation logs join (model+usage-type grain reconciliation)
-- Join at model+day grain — CUR has no per-requestId identifier
-- This confirms that your estimated costs from logs track to the invoice
WITH cur_daily AS (
SELECT
DATE_TRUNC('day', line_item_usage_start_date) AS day,
line_item_usage_type,
SUM(line_item_usage_amount) AS cur_tokens,
SUM(line_item_unblended_cost) AS cur_cost_usd
FROM your_cur_database.COST_AND_USAGE_REPORT
WHERE line_item_product_code = 'AmazonBedrock'
AND line_item_line_item_type = 'Usage'
GROUP BY 1, 2
),
logs_daily AS (
SELECT
DATE_TRUNC('day', from_iso8601_timestamp(timestamp)) AS day,
modelid,
SUM(input.inputtokencount) AS log_input_tokens,
SUM(output.outputtokencount) AS log_output_tokens
FROM bedrocklogsdb.bedrocklogs
GROUP BY 1, 2
)
SELECT
c.day,
c.line_item_usage_type,
c.cur_tokens,
l.log_input_tokens + l.log_output_tokens AS log_tokens,
c.cur_cost_usd,
(c.cur_tokens - (l.log_input_tokens + l.log_output_tokens)) AS token_gap
FROM cur_daily c
LEFT JOIN logs_daily l
ON c.day = l.day
AND c.line_item_usage_type LIKE '%' || SPLIT_PART(l.modelid, '.', 2) || '%'
ORDER BY c.day DESC, ABS(token_gap) DESC;
Query 6: Cost per million tokens by model (CUDOS-style)
SELECT
REGEXP_EXTRACT(line_item_usage_type, '^[^-]+-(.+?)-(input|output|cache)', 1) AS model,
CASE
WHEN line_item_usage_type LIKE '%-input-tokens%' THEN 'input'
WHEN line_item_usage_type LIKE '%-output-tokens%' THEN 'output'
WHEN line_item_usage_type LIKE '%-cache-read-input-token%' THEN 'cache_read'
WHEN line_item_usage_type LIKE '%-cache-write-input-token%' THEN 'cache_write'
END AS token_type,
SUM(line_item_usage_amount) AS total_tokens,
SUM(line_item_unblended_cost) AS total_cost_usd,
SUM(line_item_unblended_cost) / NULLIF(SUM(line_item_usage_amount), 0) * 1000000 AS cost_per_million_tokens
FROM your_cur_database.COST_AND_USAGE_REPORT
WHERE line_item_product_code = 'AmazonBedrock'
AND line_item_line_item_type = 'Usage'
AND line_item_usage_start_date >= DATE_ADD('day', -30, CURRENT_DATE)
GROUP BY 1, 2
ORDER BY total_cost_usd DESC;
4. FinOps Dashboard Patterns
CUDOS — Cloud Intelligence Dashboards (official AWS open-source, QuickSight)
Repo: aws-solutions-library-samples/cloud-intelligence-dashboards-framework
Deployment: CLI tool or CloudFormation; deploys in under 30 minutes
Bedrock coverage added by version:
| CUDOS version | Bedrock capability added |
|---|---|
| 5.0.0 | Bedrock section: daily cost per resource (model), spend by model family |
| 5.5 | Guardrails added to Bedrock UsageType Group calculated field |
| 5.6 | Spend per UsageType Group: Image Generation, Input Tokens, Output Tokens, Model Units, Model Customization |
| 5.8.0 | “Cost per 1000 Tokens by Usage Type Group” visual; Group By control (Account, Region, Pricing Model, Usage Type Group, Model, IAM Principal Tags) |
| 5.8.1 | Switched to cost per 1M tokens; “Amazon Bedrock Cost per Million Tokens by Model (Top 5 Models)”; “Bedrock Spend per Resource” (last 7 months) |
Coverage gap: CUDOS does not yet have a pre-built view filtered to a single gateway role’s IAM principal spend. That requires a custom QuickSight calculated field or filtered analysis on iamPrincipal/team.
AWS-native: QuickSight + CUR + Bedrock invocation logs
Official blog: Monitor and optimize your Amazon Bedrock usage with Amazon Athena and Amazon QuickSight
Architecture:
Bedrock invocation logs → S3 → Glue Catalog → Athena → QuickSight
CUR 2.0 → S3 → Glue Catalog → Athena → QuickSight
Typical dashboard panels:
- Average token usage and total cost (pie chart by model)
- Average task token usage over time (line chart)
- Latency vs token count scatter
- Cost per team/project (if request metadata or IAM tags present)
Grafana (community pattern, not officially supported)
Pattern: Athena data source plugin → Grafana dashboard. No official AWS-maintained Bedrock dashboard for Grafana exists as of mid-2026. Teams build custom dashboards using Athena data source with the queries above.
aws-usage-queries (deprecated)
aws-samples/aws-usage-queries — bootstraps Athena for CUR queries with preconfigured SQL. Archived November 2025. AWS recommends migrating to CUDOS.
5. LiteLLM Gateway — Attribution Architecture
When LiteLLM routes all traffic through one Bedrock IAM role, every CUR line item shows the same line_item_iam_principal. To recover per-team / per-project attribution:
Option A: STS AssumeRole per tenant (IAM principal attribution → CUR)
# LiteLLM custom auth / pre-call hook pattern
import boto3
def get_bedrock_client_for_tenant(tenant_id: str, team: str, project: str):
sts = boto3.client('sts')
response = sts.assume_role(
RoleArn='arn:aws:iam::123456789012:role/bedrock-gateway-role',
RoleSessionName=f'{tenant_id}', # appears in line_item_iam_principal ARN
Tags=[
{'Key': 'team', 'Value': team},
{'Key': 'project', 'Value': project},
{'Key': 'env', 'Value': 'prod'},
]
)
creds = response['Credentials']
return boto3.client(
'bedrock-runtime',
aws_access_key_id=creds['AccessKeyId'],
aws_secret_access_key=creds['SecretAccessKey'],
aws_session_token=creds['SessionToken'],
)
Result in CUR 2.0:
line_item_iam_principal:arn:aws:sts::123456789012:assumed-role/bedrock-gateway-role/tenant-idtags['iamPrincipal/team']:data-engineeringtags['iamPrincipal/project']:customer-360
Prerequisite: IAM role trust policy must include "sts:TagSession" action, or assume_role fails with AccessDenied.
Cache credentials: Credential TTL is 1 hour by default. Cache per tenant to avoid one STS call per request (STS call latency is ~50–200ms).
Option B: Request metadata per call (invocation logs only, not CUR)
# LiteLLM custom_llm_provider or pre-call hook
headers = {
'X-Amzn-Bedrock-Request-Metadata': json.dumps({
'team': team,
'project': project,
'user': user_id,
'feature': feature_name,
})
}
This appears in invocation logs as requestMetadata but does not appear in CUR. Use for per-prompt detail; use Option A for invoice-level attribution.
Option C: Application inference profiles per project (resource tag attribution → CUR)
Create one application inference profile ARN per project, tag it with project, team, use the profile ARN instead of the model ID. Tags flow into resourceTags/ in CUR 2.0.
Limit: ~1,000 profiles per account per region. Not suitable for high-cardinality attribution (per-user, per-session).
6. Known Gaps and Limitations
CUR data latency
- Standard CUR 2.0 delivery: 24–48 hours after the usage day closes. Bedrock charges for a given day first appear in CUR the following day, sometimes 48 hours later.
- IAM principal tag activation: Up to 24 hours after tag first appears; up to another 24 hours after activation before data populates.
- Total new-tenant visibility lag: Up to 72–96 hours from first API call to first CUR row with
iamPrincipal/teampopulated. - Cost Explorer: Same data as CUR; 24-hour lag typical.
Granularity
- CUR aggregates by
(usage_type, operation, identity/tag, hour or day). There is norequestIdin CUR. - Finest grain available: per usage-type per day (or per hour with
TIME_GRANULARITY = HOURLY). - Per-request detail requires invocation logs. Invocation logs are near real-time (seconds to S3).
What CUR does NOT contain
| Missing from CUR | Where to find it |
|---|---|
Per-request requestId |
Invocation logs (requestId field) |
| Per-request input/output token counts | Invocation logs (input.inputTokenCount, output.outputTokenCount) |
| Cache read/write token split at request level | Invocation logs (if logged) |
| Latency per request | Invocation logs or x-amzn-bedrock-invocation-latency response header |
| Prompt content / model input body | Invocation logs (input.inputBodyJson, up to 100KB inline) |
requestMetadata key-value tags |
Invocation logs (requestMetadata field) |
| Per-Converse-turn breakdown | Not available; per-call only |
| Cross-region inference routing detail | Usage type suffix (-cross-region-global) shows routing, not destination region |
What invocation logs do NOT contain
| Missing from invocation logs | Where to find it |
|---|---|
| Dollar cost (invoice-accurate) | CUR only |
| Discount/credit application | CUR only (line_item_net_unblended_cost) |
| Savings Plan coverage | CUR only (line_item_line_item_type = SavingsPlanCoveredUsage) |
| Bedrock Mantle endpoint calls | Not supported by invocation logging (as of mid-2026) |
IAM principal attribution scope
- Only available for
bedrock-runtimeendpoint (InvokeModel, Converse, Chat Completions). - Not available for
bedrock-mantleendpoint (Responses API, Anthropic Messages). For mantle, use Projects/Workspaces. INCLUDE_IAM_PRINCIPAL_DATAonly adds data for Bedrock costs; other services are unaffected.
File size impact
Enabling INCLUDE_IAM_PRINCIPAL_DATA = TRUE expands each aggregated Bedrock line item into N rows, one per calling IAM identity. For an account with 50 IAM roles calling Bedrock, this multiplies Bedrock-related CUR row count by ~50. Plan S3 storage and Athena scan cost accordingly.
7. Attribution Method Decision Matrix
| Goal | Mechanism | CUR column | Granularity | Latency |
|---|---|---|---|---|
| Per-team dollar chargeback | IAM principal tags via STS + INCLUDE_IAM_PRINCIPAL_DATA |
tags['iamPrincipal/team'] |
Day (aggregated) | 24–48h |
| Per-project dollar chargeback | Application inference profiles + resource tags | tags['resourceTags/project'] |
Day (aggregated) | 24–48h |
| Per-user dollar chargeback at scale | STS RoleSessionName per user | line_item_iam_principal (session in ARN) |
Day (aggregated) | 24–48h |
| Per-prompt token count (any dimension) | Request metadata (X-Amzn-Bedrock-Request-Metadata) + invocation logs |
Not in CUR — invocation logs only | Per-request | Seconds |
| Invoice reconciliation | Join invocation logs to CUR at model+day grain | Both | Day | 24–48h |
| Cross-region inference cost | CUR line_item_usage_type LIKE '%-cross-region-global' |
line_item_usage_type |
Day | 24–48h |
8. Invocation Log Schema (Athena CREATE TABLE)
CREATE EXTERNAL TABLE bedrocklogsdb.bedrocklogs (
schematype STRING,
schemaversion STRING,
timestamp STRING,
accountid STRING,
region STRING,
requestid STRING,
operation STRING,
modelid STRING,
identity STRUCT<
arn: STRING
>,
requestmetadata MAP<STRING, STRING>,
input STRUCT<
inputcontenttype: STRING,
inputtokencount: INT,
inputbodyjson: STRING
>,
output STRUCT<
outputcontenttype: STRING,
outputtokencount: INT,
outputbodyjson: STRING
>
)
PARTITIONED BY (dt STRING, h STRING)
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
WITH SERDEPROPERTIES ('ignore.malformed.json' = 'TRUE')
STORED AS INPUTFORMAT 'org.apache.hadoop.mapred.TextInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION 's3://YOUR-BUCKET/YOUR-PREFIX/AWSLogs/123456789012/BedrockModelInvocationLogs/us-east-1/'
TBLPROPERTIES ('projection.enabled' = 'true',
'projection.dt.type' = 'date',
'projection.dt.format' = 'yyyy/MM/dd',
'projection.dt.range' = '2026/01/01,NOW',
'projection.h.type' = 'integer',
'projection.h.range' = '0,23',
'storage.location.template' =
's3://YOUR-BUCKET/YOUR-PREFIX/AWSLogs/123456789012/BedrockModelInvocationLogs/us-east-1/${dt}/${h}/');
9. Source Ledger
| Claim | Source | Strength |
|---|---|---|
CUR 2.0 Bedrock column names, line_item_usage_type patterns |
AWS docs: Understanding your Amazon Bedrock CUR data | Official docs verified |
INCLUDE_IAM_PRINCIPAL_DATA config key, data starts Apr 8 2026 |
AWS docs: CUR 2.0 table dictionary | Official docs verified |
line_item_iam_principal column, IAM ARN formats |
AWS docs: Using IAM principal for cost allocation | Official docs verified |
iamPrincipal/ tag prefix, tag prefix taxonomy |
AWS docs: CUR 2.0 Tags Column | Official docs verified |
Per-request metadata, invocation log schema, requestMetadata field |
AWS docs: Per-request metadata tagging | Official docs verified |
Invocation log schema, inputTokenCount, outputTokenCount field names |
AWS docs: Model invocation logging | Official docs verified |
| Attribution method comparison table (CUR vs invocation logs) | AWS docs: Track usage and costs in Bedrock | Official docs verified |
InvokeModelInference / InvokeModelStreamingInference operation values (Jan 2026) |
AWS What’s New: Bedrock granular operation visibility | Official docs verified |
| CUR 2.0 fixed 125-column schema, new columns vs legacy CUR | AWS docs: CUR 2.0 table dictionary | Official docs verified |
| CUDOS Bedrock visuals by version, cost per 1M tokens | CUDOS CHANGELOG | Source/code verified |
| aws-usage-queries archived Nov 2025 | GitHub: aws-samples/aws-usage-queries | Source/code verified |
| STS TagSession requirement for session tags | AWS guidance: guidance-for-claude-code-with-amazon-bedrock COST_ATTRIBUTION.md | Official docs verified |
| Application inference profile ~1,000/account limit | FinOut blog: Hidden Superpower of Bedrock Cost Allocation | Community pattern |
10. Next Research Lanes
-
Bedrock Savings Plans and CUR — how
SavingsPlanCoveredUsageline items interact with IAM principal attribution; whether RI/SP discounts are visible per-principal or only at the aggregate level. -
bedrock-mantle endpoint FinOps gap — Projects and Workspaces handle attribution for Responses API / Anthropic Messages, but there is no invocation logging for mantle. This is a blind spot for Anthropic-SDK-native workloads.
-
Multi-account aggregation — how management-account CUR consolidates Bedrock spend across member accounts; whether
line_item_iam_principalworks cross-account via resource-based policies. -
LiteLLM session credential caching — validate whether LiteLLM’s built-in AWS credential provider supports per-virtual-key STS AssumeRole + tag injection, or whether a custom pre-call hook is required.
-
Athena partition projection for CUR 2.0 — CUR 2.0 uses
billing_periodas the partition key (notyear/monthas in legacy CUR); confirm the correct partition projection config for the Data Exports S3 layout.