AWS Builder Center
Keep the Anthropic SDK. Move to Bedrock. Build Usage Analytics with Logs and Athena.

Keep the Anthropic SDK. Move to Bedrock. Build Usage Analytics with Logs and Athena.

Move an existing Claude application to Amazon Bedrock with minimal code changes, while capturing invocation-level data for attribution and cost analysis using IAM roles, invocation logs, S3, and Athena.

You already have a working Claude application using the Anthropic Python SDK.
Now you want to move to Claude on Amazon Bedrock — but you still want minimal code changes, and you want detailed, near-real-time usage analytics for attribution, cost tracking, and compliance.
This post shows a practical migration path: change two lines, keep the same messages.create() interface, and start capturing the usage data you need for analytics and attribution.

The Migration: Before and After

Before (Anthropic API)

1
2
3
4
5
6
7
8
9
import anthropic

client = anthropic.Anthropic(api_key="sk-ant-...")

response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}]
)

After (Bedrock with IAM Role)

This pattern is especially useful when each application, tenant, customer, or environment can run under a separate IAM role.
1
2
3
4
5
6
7
8
9
import anthropic

client = anthropic.AnthropicBedrock(aws_region="us-east-1")

response = client.messages.create(
model="global.anthropic.claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}]
)
That's it. Same SDK. Same messages.create() interface. Two changes:
  1. Anthropic() → AnthropicBedrock()
  2. Model ID format: claude-sonnet-4-6 → global.anthropic.claude-sonnet-4-6
No SigV4 signing code. No boto3. The Anthropic SDK handles AWS auth transparently using your environment credentials (instance role, env vars, or profile).
The Attribution Pattern: Use IAM Roles
IAM Roles are preferable for production workloads because:
  • You control the name — role names like customer-acme-prod-role appear directly in invocation logs, making attribution instant and human-readable
  • No secrets to rotate — instance roles and IRSA provide automatic, short-lived credentials
  • Fine-grained permissions — restrict to specific models, regions, or actions via IAM policies
  • Native billing integration — tag roles for Cost Explorer visibility (billing)

Alternative: Bedrock API Key (for external access)

1
2
3
4
5
6
7
8
9
10
11
12
import anthropic

client = anthropic.AnthropicBedrock(
aws_region="us-east-1",
api_key="ABSK..." # Bedrock API key
)

response = client.messages.create(
model="global.anthropic.claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}]
)
Bedrock API keys are useful for external partners or teams without AWS accounts. However, key names are auto-generated (e.g., BedrockAPIKey-8ych) — you can't set a human-readable name. If you use them, maintain a side mapping that translates key IDs to your applications/customers.

The Payoff: The Raw Data for Usage Analytics

Once you're on Bedrock, you can enable Model Invocation Logging  and have every model call recorded to S3 - without changing your application code. From there, Athena gives you a simple way to query usage by caller, model, day, and token type.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# Create the logging bucket
BUCKET_NAME="bedrock-invocation-logs-$(aws sts get-caller-identity --query Account --output text)"
aws s3 mb s3://${BUCKET_NAME}

# Set bucket policy for Bedrock
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
cat > /tmp/bucket-policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "BedrockLogging",
"Effect": "Allow",
"Principal": {"Service": "bedrock.amazonaws.com"},
"Action": ["s3:PutObject"],
"Resource": "arn:aws:s3:::${BUCKET_NAME}/*",
"Condition": {
"StringEquals": {"aws:SourceAccount": "${ACCOUNT_ID}"},
"ArnLike": {"aws:SourceArn": "arn:aws:bedrock:us-east-1:${ACCOUNT_ID}:*"}
}
}]
}
EOF

aws s3api put-bucket-policy --bucket ${BUCKET_NAME} --policy file:///tmp/bucket-policy.json

# Enable invocation logging
aws bedrock put-model-invocation-logging-configuration \
--logging-config '{
"s3Config": {
"bucketName": "'
"${BUCKET_NAME}"'",
"keyPrefix": "invocation-logs/"
},
"textDataDeliveryEnabled": false,
"imageDataDeliveryEnabled": false,
"embeddingDataDeliveryEnabled": false
}'


# Verify
aws bedrock get-model-invocation-logging-configuration
Here we've set "textDataDeliveryEnabled": false , as we don't need the messages text to know token usage, you might prefer to also log text and other types of data.


Log File Location

Logs are delivered as gzipped JSON to a predictable path:
1
2
3
4
5
6
7
8
9
10
s3://bedrock-invocation-logs-12345678905/
invocation-logs/
AWSLogs/
12345678905/
BedrockModelInvocationLogs/
us-east-1/
2026/06/25/13/
20260625T133235873Z_fd9206e9e739d030.json.gz ← metadata (batched)
data/
10ecc3cf-3538-470b-97fc-37e9c8f4d0f9_input.json.gz ← full body (>100KB)
Each metadata file contains one or more newline-delimited JSON records (multiple invocations batched together). Input/output bodies larger than 100KB are stored separately under data/ as individual objects keyed by request ID.

What You Get in Each Log Entry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"schemaType": "ModelInvocationLog",
"schemaVersion": "1.0",
"timestamp": "2026-06-25T13:32:19Z",
"accountId": "12345678905",
"region": "us-east-1",
"requestId": "10ecc3cf-3538-470b-97fc-37e9c8f4d0f9",
"operation": "InvokeModel",
"modelId": "arn:aws:bedrock:us-east-1:12345678905:inference-profile/global.anthropic.claude-sonnet-4-6",
"input": {
"inputContentType": "application/json",
"inputTokenCount": 8,
"cacheReadInputTokenCount": 3376,
"cacheWriteInputTokenCount": 0
},
"output": {
"outputContentType": "application/json",
"outputTokenCount": 4
},
"identity": {
"arn": "arn:aws:sts::12345678905:assumed-role/my-app-role/i-0abc123"
},
"inferenceRegion": "eu-west-2"
}
Every log entry includes:
  • identity.arn — who made the call (role, user, or API key)
  • inputTokenCount — standard input tokens
  • cacheWriteInputTokenCount — tokens written to cache (25% surcharge)
  • cacheReadInputTokenCount — tokens read from cache (90% discount)
  • outputTokenCount — output tokens generated
  • modelId — which model was invoked
  • inferenceRegion — where the request was actually served (may differ from source region for cross-region profiles)

Identity Attribution

The identity.arn field tells you exactly who made each call:
Auth MethodIdentity in LogsNaming
IAM Role (EC2/ECS)arn:aws:sts::123456789012:assumed-role/my-app-role/i-0abc123You control ✅
IAM Userarn:aws:iam::123456789012:user/deploy-botYou control ✅
Bedrock API Keyarn:aws:iam::123456789012:user/BedrockAPIKey-8ychAuto-generated ⚠️

Building a Cost Report

Point Athena at your S3 bucket and query:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SELECT
identity.arn AS caller,
modelId,
DATE(timestamp) AS day,
SUM(input.inputTokenCount) AS input_tokens,
SUM(input.cacheWriteInputTokenCount) AS cache_write_tokens,
SUM(input.cacheReadInputTokenCount) AS cache_read_tokens,
SUM(output.outputTokenCount) AS output_tokens,
-- Claude Sonnet 4.6 pricing: $3/M input, $3.75/M cache write, $0.30/M cache read, $15/M output
ROUND(SUM(input.inputTokenCount) * 3.0 / 1000000
+ SUM(input.cacheWriteInputTokenCount) * 3.75 / 1000000
+ SUM(input.cacheReadInputTokenCount) * 0.30 / 1000000
+ SUM(output.outputTokenCount) * 15.0 / 1000000, 4) AS estimated_cost_usd
FROM bedrock_invocation_logs
GROUP BY identity.arn, modelId, DATE(timestamp)
ORDER BY estimated_cost_usd DESC
Example output:
callerdayinput_tokenscache_writecache_readoutput_tokensest_cost_usd
...assumed-role/app-prod-role/...2026-06-251,2403,37667,5208,450$0.1598
...user/BedrockAPIKey-8ych2026-06-25140010$0.0002
Note: This is an estimate based on published per-token rates. It does not reflect commitment discounts, batch pricing, provisioned throughput, or free tier. For invoice-accurate totals, join invocation logs with your AWS Cost and Usage Report (CUR) on the model and usage-type grain.
This query is intentionally simple: it gives you a starting point for cost and attribution analysis directly from the invocation logs. If you want to take this further and build a dashboard on top of the same log data, AWS has a detailed walkthrough for using Amazon Athena and Amazon QuickSight  to monitor and optimize Amazon Bedrock usage.

Billing Integration with AWS Cost Explorer

Invocation logs give you the per-request data needed for analytics. To also have costs appear natively in AWS Billing:
  1. Tag your IAM Roles — add tags like team=analytics, customer=acme, environment=prod. Then activate these as Cost Allocation Tags  in the Billing console so they appear in Cost Explorer.
  2. Use Application Inference Profiles — create named inference profiles per application or customer. These appear as distinct resources in Cost Explorer and CUR, giving you native billing breakdowns without log processing.
1
2
3
4
5
6
7
8
# Tag the role for billing visibility
aws iam tag-role --role-name customer-acme-prod-role \
--tags Key=customer,Value=acme Key=team,Value=platform

# Create an application inference profile for billing grouping
aws bedrock create-inference-profile \
--inference-profile-name "acme-production" \
--model-source '{"copyFrom": "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-sonnet-4-6"}'
Use application inference profiles in conjunction with invocation logs for the complete picture: billing shows the dollar amount, invocation logs show the token-level detail.

Key Details

  • Logs land in the source region only. Even if cross-region inference routes your request to eu-west-2 or ap-northeast-1, the log appears in us-east-1 (where you configured logging). One bucket, one region, all your data.
  • Log delivery takes 1–5 minutes. For faster visibility, you could use CloudWatch Logs as the destination instead of (or in addition to) S3.
  • Full request/response bodies are optional. Set textDataDeliveryEnabled: false if you only need token counts and metadata (smaller logs, no sensitive text data stored). Bodies larger than 100KB are always stored as separate S3 objects under data/.
  • All models via Messages API are logged. We confirmed that Opus 4.8, Sonnet 4.6, and cross-region inference profiles all appear in invocation logs when called through the Anthropic SDK (which uses InvokeModel). The only exception is calls through the Responses API (bedrock-mantle endpoint), which are not currently captured.

Why Not Request Metadata?

Bedrock's per-request metadata  feature lets you attach arbitrary key-value tags to each call (team, user, environment, experiment). These tags appear in invocation logs and can be used for fine-grained attribution.
The problem: request metadata is only available through:
  • The Converse API — via the requestMetadata field in the request body
  • Raw InvokeModel calls — via the X-Amzn-Bedrock-Request-Metadata HTTP header (must be included in SigV4 signed headers)
If you're using the Anthropic SDK (AnthropicBedrock), you can't access either mechanism — the SDK abstracts away the HTTP layer and doesn't expose request metadata parameters. You'd have to rewrite your application to use boto3's converse() or invoke_model() directly.
That's why identity-based attribution (one IAM Role per caller) is the practical approach for Anthropic SDK users: it requires zero code changes beyond the initial migration, and the identity appears automatically in every log entry.
If you do need per-request granularity (e.g., tagging individual requests with a user email or experiment ID), you'll need to use the Converse API instead:
1
2
3
4
5
6
7
8
9
10
11
12
13
import boto3

client = boto3.client("bedrock-runtime")

response = client.converse(
modelId="global.anthropic.claude-sonnet-4-6",
messages=[{"role": "user", "content": [{"text": "Hello"}]}],
requestMetadata={
"user": "alice@example.com",
"team": "growth",
"feature": "summarizer",
},
)

Summary

Bedrock (IAM Role)Bedrock (API Key)
Code changes2 lines2 lines
SDKAnthropic SDK ✅Anthropic SDK ✅
Usage loggingAutomaticAutomatic
Per-request metadataOnly via Converse API ⚠️Only via Converse API ⚠️
Identity trackingIAM ARN in logsKey ID in logs
Identity namingYou control role names ✅Auto-generated ⚠️
Cost attributionPer-role + tagsPer-key (needs mapping)
Cache trackingIn logs (read/write tokens)In logs (read/write tokens)
AWS Billing integrationTag roles + App Inference ProfilesLimited
Compliance auditS3 + AthenaS3 + Athena
The migration is small. The real payoff is that Bedrock gives you the right primitives — identity, invocation logs, token counts, cache metrics, S3 delivery, Athena queries, and billing integration — to build the usage analytics your application probably needs anyway.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article