
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:Anthropic()→AnthropicBedrock()- 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-roleappear 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-configurationHere 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 tokenscacheWriteInputTokenCount— tokens written to cache (25% surcharge)cacheReadInputTokenCount— tokens read from cache (90% discount)outputTokenCount— output tokens generatedmodelId— which model was invokedinferenceRegion— 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 Method | Identity in Logs | Naming |
|---|---|---|
| IAM Role (EC2/ECS) | arn:aws:sts::123456789012:assumed-role/my-app-role/i-0abc123 | You control ✅ |
| IAM User | arn:aws:iam::123456789012:user/deploy-bot | You control ✅ |
| Bedrock API Key | arn:aws:iam::123456789012:user/BedrockAPIKey-8ych | Auto-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 DESCExample output:
| caller | day | input_tokens | cache_write | cache_read | output_tokens | est_cost_usd |
|---|---|---|---|---|---|---|
...assumed-role/app-prod-role/... | 2026-06-25 | 1,240 | 3,376 | 67,520 | 8,450 | $0.1598 |
...user/BedrockAPIKey-8ych | 2026-06-25 | 14 | 0 | 0 | 10 | $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:
- 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. - 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: falseif 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 underdata/. - 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-mantleendpoint), 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
requestMetadatafield in the request body - Raw InvokeModel calls — via the
X-Amzn-Bedrock-Request-MetadataHTTP 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 changes | 2 lines | 2 lines |
| SDK | Anthropic SDK ✅ | Anthropic SDK ✅ |
| Usage logging | Automatic | Automatic |
| Per-request metadata | Only via Converse API ⚠️ | Only via Converse API ⚠️ |
| Identity tracking | IAM ARN in logs | Key ID in logs |
| Identity naming | You control role names ✅ | Auto-generated ⚠️ |
| Cost attribution | Per-role + tags | Per-key (needs mapping) |
| Cache tracking | In logs (read/write tokens) | In logs (read/write tokens) |
| AWS Billing integration | Tag roles + App Inference Profiles | Limited |
| Compliance audit | S3 + Athena | S3 + 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.
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article