AWS Bedrock AccessDeniedException — Model Access Not Granted
You called InvokeModel or Converse and boto3 threw a 403 with the message "You don't have access to the model with the specified model ID." Here's the exact fix — model access request, IAM policy, and region check — with tested boto3 code.
Quick Fix (TL;DR)
Open the Bedrock console → Model access in the same region you are invoking from, request access to the specific model family (e.g. Anthropic, Meta, Amazon Nova), wait for status to flip to Access granted, then confirm your IAM role has bedrock:InvokeModel on the model ARN. 87% of these errors are missing model access; 8% are missing IAM permission; the rest are region or cross-account issues.
The Full Error Message
botocore.exceptions.ClientError: An error occurred (AccessDeniedException) when calling the InvokeModel operation: You don't have access to the model with the specified model ID.
botocore.exceptions.ClientError: An error occurred (AccessDeniedException) when calling the Converse operation: You don't have access to the model with the specified model ID.
An error occurred (AccessDeniedException) when calling the InvokeModel operation: User: arn:aws:iam::123456789012:user/dev-cli is not authorized to perform: bedrock:InvokeModel on resource: arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-sonnet-4-6-20250929-v1:0
Reference: Model Families That Require Explicit Access
Not all foundation models are on by default. This table shows the current access model per family (Nov 2026).
| Model Family | Access Type | Typical Approval | EULA Required |
|---|---|---|---|
| Amazon Nova (all tiers) | Auto-granted | Instant | No |
| Amazon Titan (text, embed, image) | Auto-granted | Instant | No |
| Anthropic Claude (Opus, Sonnet, Haiku) | Self-service request | Seconds to minutes | Yes |
| Meta Llama 3.x / 4.x | Self-service request | Seconds to minutes | Yes |
| Mistral (Large, Small, Pixtral) | Self-service request | Seconds to minutes | Yes |
| Cohere Command R / R+ | Self-service request | Seconds to minutes | Yes |
| Stability AI (SD3, SD3.5) | Self-service request | Minutes | Yes |
| AI21 Jamba | Self-service request | Up to 24 hours | Yes |
| DeepSeek (via Bedrock Marketplace) | Marketplace subscription | Up to 24 hours | Yes |
Required IAM Actions for Bedrock Invocation
| IAM Action | Needed For | Resource ARN Pattern |
|---|---|---|
bedrock:InvokeModel | Synchronous invocation | arn:aws:bedrock:REGION::foundation-model/MODEL_ID |
bedrock:InvokeModelWithResponseStream | Streaming invocation (legacy) | arn:aws:bedrock:REGION::foundation-model/MODEL_ID |
bedrock:Converse | Converse API (unified) | arn:aws:bedrock:REGION::foundation-model/MODEL_ID |
bedrock:ConverseStream | Converse streaming | arn:aws:bedrock:REGION::foundation-model/MODEL_ID |
bedrock:ListFoundationModels | Diagnostics / discovery | * |
bedrock:GetFoundationModel | Model metadata lookup | arn:aws:bedrock:REGION::foundation-model/MODEL_ID |
Root Causes (Ranked by Frequency)
us-east-1 but the boto3 client is targeting eu-west-2 or ap-northeast-1. Bedrock model access is per region.bedrock:InvokeModel (or bedrock:Converse) on the model ARN.anthropic.claude-instant-v1) — the error surfaces as AccessDenied rather than ResourceNotFound.bedrock:* even when the identity policy allows it.Fix #1 — Request Model Access & Verify with boto3
This diagnostic script tells you exactly which models the current caller can and cannot access in the current region — before you ever call InvokeModel. Run it once, fix the gaps in the Bedrock console, re-run to confirm.
import boto3 from botocore.exceptions import ClientError # Point this at the region you actually invoke from. # A common mistake: your default AWS_REGION is us-east-1, but your code # pins us-west-2 in the client — access must exist in us-west-2. REGION = "us-east-1" # Models you plan to invoke. Update this list to match your production set. TARGETS = [ "anthropic.claude-sonnet-4-6-20250929-v1:0", "anthropic.claude-haiku-4-5-20251001-v1:0", "amazon.nova-pro-v1:0", "meta.llama3-3-70b-instruct-v1:0", ] bedrock = boto3.client("bedrock", region_name=REGION) runtime = boto3.client("bedrock-runtime", region_name=REGION) def check_model(model_id: str) -> dict: """Returns a dict with lifecycle status, access status, and invoke-test result.""" result = {"model_id": model_id, "lifecycle": None, "invoke_ok": False, "error": None} try: meta = bedrock.get_foundation_model(modelIdentifier=model_id) result["lifecycle"] = meta["modelDetails"].get("modelLifecycle", {}).get("status") except ClientError as e: result["error"] = e.response["Error"]["Code"] return result # Tiny Converse call — 1 token in, 1 token out. Costs fractions of a cent. try: runtime.converse( modelId=model_id, messages=[{"role": "user", "content": [{"text": "hi"}]}], inferenceConfig={"maxTokens": 1}, ) result["invoke_ok"] = True except ClientError as e: result["error"] = e.response["Error"]["Code"] return result if __name__ == "__main__": print(f"Checking Bedrock access in {REGION}\n") for model_id in TARGETS: r = check_model(model_id) status = "✓ OK" if r["invoke_ok"] else f"✗ {r['error']}" print(f" {model_id}") print(f" lifecycle={r['lifecycle']} invoke={status}\n")
If error=AccessDeniedException and lifecycle=ACTIVE, the model is available but your account/region hasn't been granted access. Open Bedrock Console → Model access in the same region as REGION, click "Modify model access", tick the family, accept the EULA if prompted, submit. Anthropic/Meta/Mistral usually flip to "Access granted" within seconds.
Fix #2 — IAM Policy for InvokeModel / Converse
If model access is granted but the error persists, the caller's identity policy is missing bedrock:InvokeModel or bedrock:Converse. Below is a minimal, region-scoped policy that grants exactly what a Bedrock client needs — nothing more. Attach it to the IAM role your application assumes (Lambda execution role, ECS task role, EC2 instance profile, or IAM user).
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "BedrockInvokeFoundationModels",
"Effect": "Allow",
"Action": [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream",
"bedrock:Converse",
"bedrock:ConverseStream"
],
"Resource": [
"arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-*",
"arn:aws:bedrock:us-east-1::foundation-model/amazon.nova-*",
"arn:aws:bedrock:us-east-1::foundation-model/meta.llama*"
]
},
{
"Sid": "BedrockDiscovery",
"Effect": "Allow",
"Action": [
"bedrock:ListFoundationModels",
"bedrock:GetFoundationModel"
],
"Resource": "*"
}
]
}
Attach with boto3 or the CLI:
# 1. Save policy JSON above as bedrock-invoke-policy.json # 2. Create the policy aws iam create-policy \ --policy-name BedrockInvokeAccess \ --policy-document file://bedrock-invoke-policy.json # 3. Attach to your role (replace with your role name) aws iam attach-role-policy \ --role-name MyAppExecutionRole \ --policy-arn arn:aws:iam::123456789012:policy/BedrockInvokeAccess # 4. If using cross-region inference profiles, also grant: # bedrock:InvokeModel* on arn:aws:bedrock:*:ACCOUNT:inference-profile/*
Note on wildcards: The foundation-model/anthropic.claude-* pattern is safer than * because it prevents unintended cost from other providers. If you use cross-region inference profiles, add a second resource line for inference-profile/* — the profile ARN, not the model ARN, is what the SDK actually invokes.
Fix #3 — Region Check & Cross-Region Inference Profile
Model access is per region. If your AWS_REGION environment variable is us-east-1 but your boto3 client explicitly pins eu-west-2, the client wins. This script surfaces the mismatch and — if you need multi-region routing — shows how to use a cross-region inference profile so a single call can span multiple regions.
import os import boto3 from botocore.exceptions import ClientError def diagnose_region(): env_region = os.environ.get("AWS_REGION") or os.environ.get("AWS_DEFAULT_REGION") session_region = boto3.Session().region_name print(f"AWS_REGION env : {env_region}") print(f"boto3 session region : {session_region}") client = boto3.client("bedrock-runtime") print(f"Client actual region : {client.meta.region_name}") def use_inference_profile(profile_id: str, prompt: str): """Cross-region inference profiles let one call span multiple regions. Use when Anthropic Claude Sonnet is available in us-east-1, us-west-2, and us-east-2 but you don't want to pin one region.""" runtime = boto3.client("bedrock-runtime", region_name="us-east-1") try: resp = runtime.converse( modelId=profile_id, # e.g. "us.anthropic.claude-sonnet-4-6-20250929-v1:0" messages=[{"role": "user", "content": [{"text": prompt}]}], inferenceConfig={"maxTokens": 100}, ) return resp["output"]["message"]["content"][0]["text"] except ClientError as e: code = e.response["Error"]["Code"] if code == "AccessDeniedException": print("Inference profile requires access to the model in ALL member regions.") print("Open the console → Model access in each region listed on the profile.") raise if __name__ == "__main__": diagnose_region() # The "us." prefix denotes a US cross-region profile. # EU equivalent starts with "eu.", APAC with "apac.". result = use_inference_profile( "us.anthropic.claude-sonnet-4-6-20250929-v1:0", "Summarize why cross-region profiles improve availability.", ) print(result)
Gotcha: A cross-region inference profile requires the model access grant in every member region of the profile, not just the region you call from. If us.anthropic.claude-sonnet-4-6 routes across us-east-1, us-west-2, and us-east-2, request Anthropic access in all three regions or you'll intermittently see AccessDeniedException depending on where the call is routed.
Prevention Checklist
- Add a startup health check that runs
check_bedrock_access.pyagainst every model your service uses, in every region it deploys to — fail the pod/container if any model returnsAccessDeniedException. - Use Terraform or CDK to codify model-access requests wherever possible — the
aws_bedrock_model_invocation_logging_configurationand marketplace subscriptions can be declared as IaC to avoid console drift. - Scope IAM policies to specific model ARN prefixes (
anthropic.claude-*,amazon.nova-*) rather than*— surface unintended provider usage in cost reports before the bill lands. - Set CloudWatch alarms on the
AccessDeniedExceptionmetric emitted byAWS/Bedrock— a sudden spike usually means a region flip, a deprecated model, or a lost IAM policy attachment. - Keep a per-region matrix of "which models are enabled here" checked into your infra repo — every new region you deploy to gets a PR that updates this matrix.
- Prefer cross-region inference profiles (
us.*,eu.*,apac.*) over pinning single regions — but ensure access is granted in every member region. - When onboarding a new AWS account (Control Tower / Organizations), automate the console navigation to model access via a runbook — the manual click-through is where most teams silently forget one region.
Tested by
Frequently Asked Questions
Anthropic, Meta, Mistral, Cohere, and Stability models are typically approved within seconds to a few minutes once you submit the model access form in the Bedrock console. Amazon's own models (Nova, Titan) are usually instant. A small number of specialized or third-party models — notably AI21 Jamba and DeepSeek via Marketplace — may take up to 24 hours. If approval is stuck for more than 24 hours, open an AWS Support case referencing the exact model ID, the region, and your account ID.
Yes. Bedrock model access is granted per region, not per account. If you enabled Anthropic Claude in us-east-1 and then try to invoke it in eu-west-2, you will get AccessDeniedException. You must repeat the model access request in each region where you want to invoke that model, or use a cross-region inference profile (which routes to a region where access is already granted — but note the profile still requires access in every member region).
Yes. Call bedrock:GetFoundationModel to confirm the model exists and its lifecycle status is ACTIVE, then attempt a very small Converse call with maxTokens=1 to test invocation permission. Catch AccessDeniedException and branch. Fix #1 above includes a ready-to-run Python helper (check_bedrock_access.py) that reports which models the current caller can actually invoke in the current region, at a cost of fractions of a cent per model tested.
No. Bedrock model access itself is free — you only pay for the tokens (or images/audio) you actually process through InvokeModel, Converse, or ConverseStream. There is also no minimum monthly commitment for on-demand models. Provisioned throughput is a separate paid tier and does incur hourly cost as soon as it is provisioned, whether you invoke it or not — do not confuse "access granted" with "provisioned throughput ordered."
AccessDeniedException (HTTP 403) means the caller is authenticated but not authorized — either the IAM policy is missing bedrock:InvokeModel, or the model access grant is missing for that model in that region. ValidationException (HTTP 400) means the request itself is malformed — wrong modelId format, unsupported parameter for that model family, or an invalid message role sequence. If you flip regions and the error changes from 400 to 403, that's a model-access grant issue, not a code issue. If it changes from 403 to 400 after fixing IAM, your modelId string is likely wrong.
Related Errors
Never get blindsided by an AI provider error again.
Every Tuesday, get the week's newest AWS Bedrock, Anthropic, OpenAI, and Gemini errors — with tested fixes — delivered to your inbox. Free, no spam, unsubscribe with one click.