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 key | What the gateway does |
|---|---|
eager_input_streaming | Kept 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. |
strict | Dropped on every model. Opus 4.7, Sonnet 5 and later reject it, and Claude Code never sends it to Bedrock directly. |
defer_loading | Kept. 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:
| Request | anthropicInvokeNativeHeaderBetas | Betas sent to Bedrock |
|---|---|---|
| No routing config | either | The anthropic-beta header, minus a denylist. Without a header, the body's anthropic_beta. |
| Under a routing config | unset 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-20250219interleaved-thinking-2025-05-14mid-conversation-system-2026-04-07thinking-token-count-2026-05-13effort-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:
| Setting | Console field | When set to false |
|---|---|---|
anthropicInvokeNative | Claude over InvokeModel | Claude /v1/messages uses Converse. |
anthropicInvokeNativeHeaderBetas | Forward anthropic-beta header under routing | Routed 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:
- A built-in table of released Claude models, keyed by their dated ids. No extra permission, no network call.
- Bedrock itself, via
ListInferenceProfilesthenListFoundationModels, for anything the table does not hold. This is the path that needs the twoListactions 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. - 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 region | Prefix |
|---|---|
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 family | What 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. |
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.
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.