Skip to main content

Bedrock reference

Depth behind the Bedrock guide.

Which Bedrock API a request uses​

Claude on /v1/messages uses InvokeModel by default. The OpenAI-shaped /v1/chat/completions lane and non-Claude models use Converse. Setting anthropicInvokeNative: "false" also selects Converse for Claude on /v1/messages.

Converse has no shape for a mid-conversation system turn and folds it into the neighbouring user turn. The gateway never sends Converse the client's anthropic-beta header, and it drops the betas Converse rejects (such as tool-search-tool-2025-10-19 and advisor-tool-2026-03-01) from an anthropic_beta body field. Bedrock accepts thinking.display: "updates" only with the thinking-display-updates-2026-08-18 beta. On both lanes the gateway removes that value from a request it sends without the beta; put the beta in the anthropic_beta body field to keep it. Converse accepts defer_loading on a tool but does not defer it: the schema is still sent and billed. Claude Code's own tool search runs in the client, so it is unaffected.

A cache_control marker becomes a Converse cachePoint. On /v1/chat/completions that covers a marker on a content part and one on the message itself, which closes that message. OpenAI SDK clients (LangChain, LangGraph) send no markers, so the optimizer adds them to Claude requests once a conversation shows a follow-up inside the five-minute cache window, however Bedrock was chosen (request header, the agent key's own route, or org routing). A ttl: "1h" marker keeps the hour on Claude 4.5 and later and Claude 5: the gateway sends cachePoint: { type: "default", ttl: "1h" }, so 1-hour writes bill at the 1-hour rate. Every other model gets the default 5-minute cache point, because Bedrock rejects the field there. The same models take the hour-long prompt cache setting.

Claude on the native Anthropic lane (/v1/messages, what Claude Code and the Anthropic SDKs send) goes over InvokeModel with the client's Messages body forwarded as it arrived: mid-conversation system turns, cache_control markers, thinking, tools (except the keys below) and metadata all reach the model untouched, and the stream comes back as the same Anthropic events. The gateway drops model and stream (both are in the URL), sets anthropic_version to bedrock-2023-05-31, and sends the request's betas as the anthropic_beta field. It also drops context_management with the context-management-2025-06-27 beta, safeguards with the dangerous-tool-use-2026-09-03 beta, and a message-level output_config (the one Claude Code puts on a system turn after a resume), because InvokeModel rejects all three. The request-level output_config is kept.

A tool key the model does not take fails the whole request (tools.0.custom.eager_input_streaming: Extra inputs are not permitted):

Tool keyWhat the gateway does
eager_input_streamingKept on Haiku 4.5, Sonnet 4.6 to 5.5 and Opus 4.6 to 5.5, where Bedrock accepted it. Dropped on every other model, Sonnet 4.5 and Opus 4.5 included.
strictDropped on every model. Opus 4.7, Sonnet 5 and later reject it, and Claude Code never sends it to Bedrock directly.
defer_loadingKept. Sonnet 4.5 accepts it only with tool-search-tool-2025-10-19, so on Sonnet 4.5 the gateway adds that beta when the request uses tool search.

On the native InvokeModel lane, which betas reach Bedrock depends on routing:

RequestanthropicInvokeNativeHeaderBetasBetas sent to Bedrock
No routing configeitherThe anthropic-beta header, minus a denylist. Without a header, the body's anthropic_beta.
Under a routing configunset or "true" (default)The betas in the anthropic-beta header that are on the allowlist below. Without a header, the body's anthropic_beta.
Under a routing config"false"The body's anthropic_beta only. The header is not forwarded.

A routing config means the org default routing, or an x-anyray-config with targets or a provider. A header replaces the body list; it does not merge. Claude Code sends its betas in the header, so routed sessions forward the supported values by default.

Bedrock rejects a beta it does not know (400 ValidationException: Unexpected value(s) ... for the anthropic-beta header). A routed request forwards only this allowlist:

  • claude-code-20250219
  • interleaved-thinking-2025-05-14
  • mid-conversation-system-2026-04-07
  • thinking-token-count-2026-05-13
  • effort-2025-11-24

These are the betas InvokeModel accepted when probed (us.anthropic.claude-sonnet-5, us-east-1, 2026-10-01) and that Claude Code sends to Bedrock directly. Others Bedrock accepts, like context-1m-2025-08-07 or thinking-display-updates-2026-08-18, are not forwarded.

Tool search is the one mapping. When the header carries advanced-tool-use-2025-11-20 and the body uses tool search (a tool with defer_loading: true, or a tool_reference block), the gateway sends tool-search-tool-2025-10-19 instead. It never sends advanced-tool-use-2025-11-20 on this path.

thinking.display: "updates" is forwarded only when thinking-display-updates-2026-08-18 is among the betas sent. That beta is not on the allowlist, so a routed request drops the value with the switch on or off. A target's own anthropic_beta is kept: the allowlist applies to the client's header, not to a value an admin wrote.

With no routing config, the gateway removes prompt-caching-scope-2026-01-05, cache-diagnosis-2026-04-07, prompt-caching-evict-2026-05-12 and context-management-2025-06-27 from the header and forwards the rest. Any other beta Bedrock does not know, such as advanced-tool-use-2025-11-20 or extended-cache-ttl-2025-04-11, fails with the 400 above.

Both provider settings default to true. Change them in Console → Providers → bedrock, or in the credential bag through PUT /admin/provider-keys:

SettingConsole fieldWhen set to false
anthropicInvokeNativeClaude over InvokeModelClaude /v1/messages uses Converse.
anthropicInvokeNativeHeaderBetasForward anthropic-beta header under routingRouted requests use the body's betas only.

The header setting has no effect on Converse, which never forwards the header.

count_tokens counts the body the active setting would send, with Bedrock's CountTokens. For Claude 5 and Opus 4.7+, which that API refuses, it uses the Anthropic count_tokens on bedrock-mantle. If neither can count the model, the role lacks the CountTokens actions, or a count takes over 5 seconds, you get the 501 with x-should-retry: false, so the client uses its own estimate.

Model-id resolution​

The gateway resolves a Claude id in this order:

  1. A built-in table of released Claude models, keyed by their dated ids. No extra permission, no network call.
  2. Bedrock itself, via ListInferenceProfiles then ListFoundationModels, for anything the table does not hold. This is the path that needs the two List actions in the guide's policy; the answer is cached per region, so it costs one lookup rather than one per request, and it can only ever resolve to a model your account can reach.
  3. Unchanged, when neither resolves it. A Bedrock id sent directly is always forwarded exactly as written.

Bedrock names its newer models without a date, so claude-opus-5 matches anthropic.claude-opus-5 directly. For an older family that Bedrock publishes only as dated snapshots, an undated id names the model rather than a snapshot, and from v1.10.269 it resolves to the newest published snapshot: claude-sonnet-4-5 reaches claude-sonnet-4-5-20250929-v1:0. Newest wins rather than first because AWS returns these listings in no guaranteed order, and first-match would leave the same request served by a different snapshot from one region or account to the next. On earlier releases that form does not resolve, so send the dated id or set a model alias.

The cross-region inference-profile prefix comes from the provider's configured region (Claude 4 and newer are profile-only in most regions, which is what makes the prefix necessary):

Configured regionPrefix
us-*us.
eu-*eu.
ap-*apac.
ca-*ca.

Discovery picks the regional profile over the cross-region global. one, whatever order AWS lists them in, and returns global. only for a model with no regional profile in your region. A regional profile keeps prompts in that geography, and for Claude Sonnet 4.5 and later Bedrock bills it 10% above global.. To use global., map the short name to the global. id with a model alias (PUT /admin/model-aliases, see Routing). Models from other families keep their Bedrock ids, since only Claude has a canonical id to convert from.

Claude 5 availability by region​

No undated id is in the built-in table, so claude-opus-5, claude-sonnet-5, and claude-fable-5 resolve only through discovery, and availability differs by region in a way a fixed mapping could not handle:

Region familyWhat discovery finds
us-*All three under the us. prefix
eu-*Opus and sonnet under eu.; fable only as global.anthropic.claude-fable-5
ap-*None carry apac.; all three come from global.
Fable needs a retention opt-in

Bedrock serves Claude Fable only when the account's data retention mode is aws_review (or the legacy provider_data_share). Below that, every Fable request fails with a 400, so a Bedrock agent's model menu leaves Fable out. If your account has opted in, publish it with a model alias that targets bedrock/claude-fable-5-1.

To pin an exact id instead, map the short name with a model alias, which is applied before the request reaches the provider and so takes precedence over all resolution above. To list what your account can use as an alias target:

aws bedrock list-inference-profiles --region <region> \
--query 'inferenceProfileSummaries[].inferenceProfileId' --output text

Static keys and cross-account access​

Use a static access-key pair when the gateway has no AWS workload identity (a Cloudflare Worker has no access to AWS workload metadata), or when Bedrock lives in a different AWS account than the gateway, which a workload role cannot reach. Create an IAM user in the account that holds the model access, attach the guide's policy, and mint a key:

aws iam create-user --user-name anyray-bedrock
aws iam attach-user-policy --user-name anyray-bedrock --policy-arn "$POLICY_ARN"
aws iam create-access-key --user-name anyray-bedrock

Paste the pair into the provider config (guide, step 2). The key is long-lived, so it goes on your normal rotation schedule.

A new IAM user needs its own use-case approval

Bedrock evaluates the Anthropic use-case attestation per principal, not per account: a freshly created IAM user gets ResourceNotFoundException asking for the use-case form, even in an account that already invokes Claude successfully. Read the account's form as an admin with bedrock get-use-case-for-model-access, then submit it as the new user with bedrock put-use-case-for-model-access. The BedrockUseCaseAttestation statement in the guide's policy is what permits that; without it the failure arrives later and elsewhere, as AccessDenied on Converse reading "not authorized to perform the required AWS Marketplace actions".

The CloudFormation role​

The stack creates the role with the logical id TaskRole. Find its generated name with aws cloudformation describe-stack-resources --stack-name <stack> --logical-resource-id TaskRole, then attach the policy with aws iam attach-role-policy.

Attach a managed policy rather than adding an inline one. The template declares the role's inline policies, so CloudFormation reconciles that list on every stack update and can remove an inline policy you added by hand. A managed-policy attachment is not declared in the template, so it survives; it appears in drift detection, which is expected. One TaskRole backs all three services in the stack, and only the gateway calls Bedrock; scope the policy's Resource if you want to narrow that.

VPC and private subnets​

The CloudFormation stack needs no networking change: its tasks run with their own outbound access to AWS, and that holds when the load balancer is internal as well, because the tasks reach Bedrock directly rather than through the load balancer. A gateway in private subnets needs either a NAT gateway or VPC interface endpoints for com.amazonaws.<region>.bedrock-runtime and com.amazonaws.<region>.bedrock. VPC endpoints are regional, so a Bedrock region different from the gateway's own region cannot use one and needs ordinary internet egress. Token counts for Claude 5 go to bedrock-mantle.<region>.api.aws, which neither endpoint covers. Without a route to it, those counts fall back to the client's estimate.