AWS Bedrock Invalid Model ID
Bedrock rejected the call before it even hit the model — ValidationException at the SDK boundary. Common causes: missing version suffix (:0), wrong provider prefix, using a foundation-model ARN where a plain ID is expected, or a copy-paste artifact from the console.
Quick Fix (TL;DR)
Bedrock model IDs follow a strict format: {provider}.{model-name}-{version-date}-v{X}:{Y} — for example anthropic.claude-sonnet-4-6-20250929-v1:0. Missing the :0 suffix or the date component causes 90% of these failures. Use list_foundation_models() to fetch the exact canonical string, then match it byte-for-byte.
The Full Error Message
botocore.exceptions.ClientError: An error occurred (ValidationException) when calling the Converse operation: The provided model identifier is invalid. Valid model identifiers include 'anthropic.claude-sonnet-4-6-20250929-v1:0'.
botocore.exceptions.ClientError: An error occurred (ValidationException) when calling the InvokeModel operation: Malformed input request: #: expected type: String, found: Object. If you intended to use an ARN, pass the ARN string not an object.
botocore.exceptions.ClientError: An error occurred (ValidationException) when calling the Converse operation: The provided model identifier is invalid. Did you mean 'anthropic.claude-haiku-4-5-20251001-v1:0'?
Bedrock Model ID Format Anatomy
Every foundation-model ID has five parts. Missing any of them yields ValidationException.
| Part | Example | Notes |
|---|---|---|
| Provider prefix | anthropic. | Lowercase, includes trailing dot |
| Model family | claude-sonnet | Hyphen-separated tokens |
| Generation | 4-6 | Two-digit major.minor |
| Version date | 20250929 | YYYYMMDD, model release date |
| Version suffix | v1:0 | Interface version and revision |
Common ID Format Mistakes
The eight typos we see most often.
| You wrote | You meant | What Bedrock says |
|---|---|---|
anthropic/claude-... | anthropic.claude-... | Slash not accepted, use dot |
claude-sonnet-4-6 | anthropic.claude-sonnet-4-6-20250929-v1:0 | Missing provider prefix + date + version |
anthropic.claude-sonnet-4-6-v1 | ...20250929-v1:0 | Missing version date + revision |
...-v1 | ...-v1:0 | Missing revision after colon |
Anthropic.Claude-Sonnet-4-6-... | anthropic.claude-sonnet-4-6-... | Case-sensitive, lowercase |
arn:aws:bedrock:... | anthropic.claude-... | Pass ID string, not ARN (unless spec says ARN) |
us.anthropic.claude-... as modelId in InvokeModel | anthropic.claude-... | Profile prefix only for Converse |
anthropic.claude-instant-v1 | anthropic.claude-haiku-4-5-20251001-v1:0 | Deprecated — use replacement |
Root Causes (Ranked by Frequency)
anthropic.claude-sonnet-4-6-20250929 without the -v1:0. The colon-revision is mandatory.anthropic.claude-sonnet-4-6-v1:0. The YYYYMMDD component is required for all Anthropic models.anthropic/claude-sonnet-4-6-.... Bedrock uses dots, not slashes, as provider separators.amazon.claude-... or openai.gpt-... — provider must match; Amazon owns Nova/Titan, Anthropic owns Claude.InvokeModel takes a plain ID; some newer APIs accept both, but mixing them causes validation.anthropic.claude-instant-v1 are validated but return a deprecation error suggesting the replacement.Anthropic.Claude-... instead of lowercase — model IDs are case-sensitive.Fix #1 — Fetch the Canonical Model ID Programmatically
The safest way to get a model ID right is to fetch it from list_foundation_models() at deploy time. This script prints every model available to your account in the target region, formatted for copy-paste into config. Filter by provider or family to narrow down.
import boto3 import argparse def print_ids(region: str, provider_filter: str = None, family_filter: str = None): br = boto3.client("bedrock", region_name=region) resp = br.list_foundation_models() print(f"# Bedrock foundation models available in {region}") print(f"# Fetched via list_foundation_models()\n") for m in resp["modelSummaries"]: model_id = m["modelId"] if provider_filter and not model_id.startswith(f"{provider_filter}."): continue if family_filter and family_filter not in model_id: continue provider = m.get("providerName", "") name = m.get("modelName", "") streaming = "streaming" if m.get("responseStreamingSupported") else "sync-only" lifecycle = m.get("modelLifecycle", {}).get("status", "") print(f"{model_id}") print(f" provider = {provider}") print(f" name = {name}") print(f" streaming = {streaming}") print(f" lifecycle = {lifecycle}\n") if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--region", default="us-east-1") parser.add_argument("--provider", help="Filter: anthropic, amazon, meta, mistral, cohere") parser.add_argument("--family", help="Substring filter: sonnet, opus, haiku, nova, etc.") args = parser.parse_args() print_ids(args.region, args.provider, args.family)
Usage: python print_canonical_model_ids.py --region us-east-1 --provider anthropic --family sonnet prints every Sonnet variant in us-east-1. Pipe to grep -oE '[a-z]+\.[a-z0-9\-]+:[0-9]+' to extract just the IDs.
Fix #2 — Validate Model IDs at Config-Load Time
A tiny validator that checks the ID structure against Bedrock's known format. Wire it into your config loader (Pydantic, dataclasses, whatever) so an invalid ID fails at startup rather than at first user request.
import re from typing import Optional # Provider.family-generation-YYYYMMDD-vX:Y # Allow optional us./eu./apac. profile prefix. _MODEL_ID_PATTERN = re.compile( r"^" r"(?:(?P<prefix>us|eu|apac)\.)?" # optional profile prefix r"(?P<provider>[a-z0-9]+)\." # provider (anthropic, amazon, meta, ...) r"(?P<family>[a-z0-9\-]+?)" # family (claude-sonnet, nova-pro, ...) r"(?:-(?P<date>\d{8}))?" # optional YYYYMMDD version date r"-v(?P<major>\d+)" # interface version r"(?::(?P<minor>\d+))?" # optional revision r"$" ) _KNOWN_PROVIDERS = {"anthropic", "amazon", "meta", "mistral", "cohere", "ai21", "stability", "deepseek"} def validate(model_id: str) -> tuple[bool, Optional[str]]: """Return (valid, error_reason). Reason is None when valid.""" if not model_id: return False, "empty string" if model_id != model_id.strip(): return False, "leading or trailing whitespace" if model_id != model_id.lower(): return False, "must be lowercase" match = _MODEL_ID_PATTERN.match(model_id) if not match: return False, f"does not match pattern {_MODEL_ID_PATTERN.pattern}" provider = match.group("provider") if provider not in _KNOWN_PROVIDERS: return False, f"unknown provider '{provider}' (known: {_KNOWN_PROVIDERS})" # Anthropic requires the version date; Amazon does not for older Nova. if provider == "anthropic" and not match.group("date"): return False, "Anthropic models require YYYYMMDD version date" # Almost every model requires the :N revision suffix. if match.group("minor") is None: return False, "missing revision suffix (e.g. :0)" return True, None # Self-test if __name__ == "__main__": for candidate in [ "anthropic.claude-sonnet-4-6-20250929-v1:0", # valid "anthropic.claude-sonnet-4-6-v1:0", # invalid: no date "anthropic.claude-sonnet-4-6-20250929-v1", # invalid: no :0 "Anthropic.Claude-Sonnet-4-6-20250929-v1:0", # invalid: uppercase "us.anthropic.claude-opus-4-7-20260315-v1:0", # valid: profile prefix "amazon.nova-pro-v1:0", # valid: no date needed ]: ok, reason = validate(candidate) status = "OK" if ok else f"INVALID: {reason}" print(f"{candidate:<50} {status}")
Where to hook this: in Pydantic, add a @field_validator("model_id"). In dataclasses, add a __post_init__ check. In env-driven configs, validate at process start. The point is to fail fast on bad IDs, not at the moment a customer request hits the SDK.
Fix #3 — Handle ARN-vs-ID Confusion Explicitly
Bedrock has three flavors of "how do I refer to a model": plain ID (anthropic.claude-...), foundation-model ARN (arn:aws:bedrock:REGION::foundation-model/anthropic.claude-...), and inference-profile ID/ARN. This helper normalizes any of them to the format the specific API expects, so call sites don't have to remember the rules.
from typing import Literal def normalize(model_ref: str, target: Literal["id", "arn"], region: str = "us-east-1", account: str = None) -> str: """Convert between model ID and model ARN. Examples: normalize("anthropic.claude-sonnet-4-6-20250929-v1:0", target="arn") -> "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-sonnet-4-6-20250929-v1:0" normalize("arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-...", target="id") -> "anthropic.claude-..." """ is_arn = model_ref.startswith("arn:aws:bedrock:") if target == "id": if not is_arn: return model_ref # already an ID # Extract the last path segment after "foundation-model/" or "inference-profile/" return model_ref.rsplit("/", 1)[-1] elif target == "arn": if is_arn: return model_ref # already an ARN # Detect profile vs foundation model if model_ref.startswith(("us.", "eu.", "apac.")): # Inference profile — needs the account ID. if not account: raise ValueError("inference-profile ARN requires account ID") return f"arn:aws:bedrock:{region}:{account}:inference-profile/{model_ref}" # Foundation model — no account in ARN. return f"arn:aws:bedrock:{region}::foundation-model/{model_ref}" raise ValueError(f"target must be 'id' or 'arn', got {target}") # Quick self-check if __name__ == "__main__": tests = [ ("anthropic.claude-sonnet-4-6-20250929-v1:0", "arn", "us-east-1", None), ("arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-haiku-4-5-20251001-v1:0", "id", None, None), ("us.anthropic.claude-opus-4-7-20260315-v1:0", "arn", "us-east-1", "123456789012"), ] for ref, target, region, account in tests: result = normalize(ref, target, region=region or "us-east-1", account=account) print(f"{ref[:50]:<52} --{target}--> {result[:80]}")
Rule of thumb: InvokeModel and Converse accept both ID and ARN. Bedrock Agent and Knowledge Base configuration usually want ARNs. IAM policies always want ARNs in the Resource block. When in doubt, normalize to ARN for storage and to ID for logging.
Prevention Checklist
- Never hardcode model IDs — pull from a config file or Secrets Manager entry that itself is validated at load time.
- Version-pin the full model ID including
-YYYYMMDD-v1:0— using a family name likeclaude-sonnetis always wrong. - Run the
validate_model_id.pyregex at CI time against every model ID string in your codebase. - For copy-paste from AWS docs, verify the ID actually appears in
list_foundation_models()— docs occasionally list preview IDs that aren't yet GA. - When the AWS blog announces a new model, update your config in a PR and run validation before merging.
- Emit the model ID as a structured log field on every Bedrock invocation — makes debugging invalid-ID errors trivial.
- For customer-facing model selection UIs, list only IDs returned by
list_foundation_models()— don't let users type free-form IDs.
Tested by
Frequently Asked Questions
For Anthropic models, yes — all current Claude IDs include the release date and rejecting a call without it is guaranteed.
For Amazon Nova, older versions (Nova Pro v1) don't have a date; newer premium models do. Meta Llama 4 uses dates; Llama 3 sometimes doesn't.
When in doubt, include the date and test — if Bedrock accepts a date-less ID, both formats work for that model; if not, the date is mandatory.
Both work for InvokeModel and Converse.
The ID is shorter and more common in examples; the ARN is required in IAM policy Resource blocks and in some cross-account patterns.
If you're building an SDK wrapper, accept both and normalize internally. If you're writing straight application code, plain IDs are more readable.
Bedrock's validator does fuzzy matching on invalid IDs and suggests the closest valid one. If you write anthropic.claude-sonnet-4 and there's no such ID, the error might say "Did you mean anthropic.claude-sonnet-4-6-20250929-v1:0?"
This is a helpful hint but not authoritative — always confirm with list_foundation_models() rather than accepting the suggestion blindly, especially across account/region boundaries.
The v1:0 part has two components:
v1is the interface/API version (change indicates breaking API changes):0is the revision (increments for minor updates within the same interface)
Most current models are at v1:0. When AWS ships v2 of an interface, both v1 and v2 typically exist in parallel for at least 6 months to allow migration. Pin to a specific v{X}:{Y} to freeze behavior.
No. Bedrock does not support wildcards or aliases in model IDs. Every call must specify an exact model ID including the version date and revision.
This is intentional — AWS wants inference behavior to be reproducible, and a "latest" pointer would change without deployment. For automated model updating, use a resolver pattern like Fix #3 on the model-not-available-in-region page.
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.