Cross-posted with permission from iqraa.tech — the full guide has the complete IAM policy, SCP examples, and a 300-engineer rollout case study.
Claude Code has a Bedrock mode. Most people find out about it from a single environment variable — CLAUDE_CODE_USE_BEDROCK=1 — and assume that's the whole story. It isn't. The env var is the on-switch; the IAM policy, region strategy, and governance layer around it are where almost every enterprise rollout actually gets stuck.
I run through the deployment end to end below — the parts that matter for a platform team, not just "here's a flag."
Why route through Bedrock at all
The models are identical either way. What changes is procurement, governance, and audit:
- Consolidated billing. An AWS spend commitment absorbs Bedrock usage instead of opening a separate Anthropic contract.
- Data residency. Bedrock lets you pin inference to a region — direct Anthropic API gives you far less control over where a request lands.
- SCP-level guardrails. You can restrict which IAM principals, accounts, or regions may invoke Bedrock at all. That control doesn't exist on the direct API.
- CloudTrail auditability. Every invocation lands in CloudTrail with caller identity, source IP, region, and model ID — append-only and SIEM-ready.
The environment variables
export CLAUDE_CODE_USE_BEDROCK=1
export AWS_REGION=us-east-1
export ANTHROPIC_MODEL=us.anthropic.claude-sonnet-4-6-20250610-v1:0
export AWS_PROFILE=claude-code-prod
Note the model ID format difference — direct Anthropic uses claude-sonnet-4-6; Bedrock uses inference-profile IDs like us.anthropic.claude-sonnet-4-6-20250610-v1:0. The us. prefix means AWS load-balances the request across US regions for resilience. Drop the prefix and you're pinned to one region only. Mixing the two ID formats is the single most common "model not found" error teams hit on day one.
The IAM policy (minimum viable, not bedrock:*)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "InvokeClaudeModels",
"Effect": "Allow",
"Action": ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"],
"Resource": [
"arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-sonnet-*",
"arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-opus-*",
"arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-haiku-*"
]
},
{
"Sid": "UseCrossRegionInferenceProfiles",
"Effect": "Allow",
"Action": ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"],
"Resource": ["arn:aws:bedrock:us-east-1:*:inference-profile/us.anthropic.claude-*"]
},
{
"Sid": "ListFoundationModels",
"Effect": "Allow",
"Action": "bedrock:ListFoundationModels",
"Resource": "*"
}
]
}
Two details that trip people up:
-
InvokeModelWithResponseStreamis a separate permission fromInvokeModel. Miss it and Claude Code falls back to one-shot invocation — it still works, but feels sluggish and streaming sessions fail. - Never grant
Resource: "*"on Bedrock invoke actions. Bedrock also hosts Llama, Mistral, Nova, and marketplace models with their own cost/security profile. Scope toanthropic.claude-*ARNs only, so a leaked credential's blast radius stays limited to Anthropic models.
The gotcha nobody expects: model access provisioning
New AWS accounts don't have Claude models enabled by default. You have to explicitly request access per model per region in the Bedrock console, and approval can take anywhere from minutes to several hours — not instant, despite what the console implies. Request access to Haiku, Sonnet, and Opus up front, even if you're only using Sonnet today, so a future model switch doesn't get blocked by a multi-hour provisioning delay mid-rollout.
SCPs: the actual governance layer
IAM controls what a role can do; SCPs control what an entire AWS account can do, org-wide:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyBedrockOutsideApprovedRegions",
"Effect": "Deny",
"Action": "bedrock:*",
"Resource": "*",
"Condition": {
"StringNotEquals": { "aws:RequestedRegion": ["us-east-1", "eu-west-1"] }
}
},
{
"Sid": "DenyNonAnthropicBedrockModels",
"Effect": "Deny",
"Action": ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"],
"Resource": "arn:aws:bedrock:*:*:foundation-model/*",
"Condition": {
"StringNotLike": { "bedrock:FoundationModelArn": "arn:aws:bedrock:*:*:foundation-model/anthropic.claude-*" }
}
}
]
}
Layer this with an IAM permission boundary and the inline invoke policy above, and you get the same three-layer model mature shops already use for IAM and KMS: SCP governs the org, permission boundary governs what roles developers can create, inline policy governs what Claude Code itself can invoke.
Cost levers that actually move the number
The headline per-token rate is the ceiling, not what you'll actually pay:
-
Prompt caching — cached tokens bill at ~10% of normal input rate. A stable
CLAUDE.md(no dynamic timestamps/build IDs) keeps cache hits high; that alone routinely cuts effective input cost 80-90% on long sessions. -
Model routing — Haiku input is $0.80/M tokens vs. Opus at $15/M. Review your actual model mix after 30 days; Opus dominating usually means either a
CLAUDE.mdthat isn't giving Sonnet enough structure, or developers manually overriding the default. -
--max-budget-usdin CI — without a hard cap, a runaway agent loop in a failed pipeline is the most common cost-overrun story in early adoption. $5/pipeline is a reasonable default for routine tasks.
Auth: use the credential chain you already have
Claude Code on Bedrock rides the standard AWS SDK credential chain — no custom auth to build. AWS SSO for developer workstations (aws sso login, short-lived creds, single point of revocation), OIDC federation for CI/CD (GitHub Actions/GitLab/Jenkins all support it — no static secrets), instance/task roles for EC2/ECS. If you're stuck on long-lived access keys, rotate quarterly via Secrets Manager + a rotator Lambda, not manually.
The five failure modes that generate the most support tickets
- Skipping the model-access request (blocks launch for hours)
- Wrong model ID format — direct vs. cross-region vs. single-region Bedrock IDs
- Missing
InvokeModelWithResponseStream - Wildcard IAM resources on Bedrock invoke actions
- No SCP region pinning — developers will invoke from unintended regions if nothing stops them
Full write-up (including a worked 300-engineer financial-services rollout, VPC endpoint config, and provisioned-throughput break-even math) is here: Claude Code AWS Bedrock: Enterprise Setup Guide
Happy to answer questions on IAM/SCP specifics in the comments — I set up a few of these rollouts and the model-access delay catches everyone the first time.
Top comments (0)