SIGV4 EGRESS

Token Observe for Amazon Bedrock

Your agents keep speaking a dialect they already know; the gateway signs for AWS.

You put a control in front of agents that call Bedrock by having them speak a dialect the gateway accepts — Anthropic Messages at POST /v1/messages, or OpenAI Chat Completions — while Token Observe holds the AWS credentials and signs the outbound request with SigV4. That split is the whole shape of this integration and it is different from every other provider on this site: Bedrock is not bearer-authenticated, so there is no credential your agent can hold that the gateway would forward, and the agent presents a gateway-minted agent key instead while the AWS secret never leaves the gateway process or reaches the wire. Bedrock matters out of proportion to its traffic share for a reason the product’s own source states plainly: regulated buyers already hold an AWS data-processing agreement and committed spend, and for many of them it is the only route to a frontier model that procurement will sign. Three constraints come with it. Only the Anthropic-on-Bedrock body shape is spoken, so another model family is refused with a named reason rather than posted as a body the endpoint cannot parse. The signing region is bound to the endpoint host, and a contradictory environment value skips the provider rather than overriding it. And streaming is AWS event-stream framing rather than server-sent events, whose two CRC32 checksums are deliberately parsed past rather than verified.
Ingress
POST /v1/messages, or the OpenAI dialect, translated on egress
Egress
POST {baseUrl}/model/{modelId}/invoke, SigV4-signed
Cache accounting
Anthropic-on-Bedrock, so cache tokens sit beside the input total
Model families
Anthropic only; any other family is refused with a named reason
What is not verifiedThe two CRC32 checksums in each event-stream message are parsed past rather than checked — transport integrity is left to TLS, and a corrupted frame still fails the length and JSON checks that are enforced
On this page
the change

What moves in a Amazon Bedrock client

The agent speaks a dialect; the gateway holds the AWS credentials
# On the agent — it never signs SigV4 against the gateway
ANTHROPIC_BASE_URL="https://gateway.example.com"
ANTHROPIC_API_KEY="acp_agent_…"

# On the gateway. The provider row names ONLY the secret; the rest derive from it.
AWS_BEDROCK_SECRET_ACCESS_KEY="…"       # the audited name in the provider row
AWS_BEDROCK_ACCESS_KEY_ID="…"           # falls back to AWS_ACCESS_KEY_ID
AWS_BEDROCK_REGION="eu-west-2"          # falls back to AWS_REGION
AWS_BEDROCK_SESSION_TOKEN="…"           # optional; falls back to AWS_SESSION_TOKEN

# AWS_SECRET_ACCESS_KEY is reserved and is never read from a provider row.

Where that traffic lands

POST /v1/messages
The natural ingress for Bedrock traffic, because the body shape spoken upstream is the Anthropic one. max_tokens is required, system is a top-level field, and tool use travels as tool_use and tool_result blocks. Streaming is named-event server-sent events to the client, whatever framing the upstream used.
POST /v1/chat/completions
The OpenAI dialect also reaches Bedrock, through the same canonical request every ingress produces. What decides the destination is the route rule that matched the model, not the path the client used.
POST {baseUrl}/model/{modelId}/invoke
The egress, SigV4-signed. Streaming uses /invoke-with-response-stream, and the transport rather than a body field decides which: model and stream in the body are a validation exception rather than something Bedrock ignores, so both are removed and anthropic_version is set to the Bedrock body tag instead.
POST /v1/embeddings
Not available on Bedrock rows. The embeddings adapter speaks the OpenAI-compatible contract for openai and openrouter rows only, and Bedrock fails closed before credential resolution rather than being sent a request in the wrong dialect.
provider-specific

What is true of Amazon Bedrock and not of the others

Every provider in this list behaves differently somewhere that matters, and those differences are the reason a single general integration page is not enough.

SigV4, not a bearer token
The secret never reaches the wire — only a signature over the canonical request does. That is also why a wrong signature comes back as an opaque 403 with nothing to correlate, which is why the signer is exported and pinned to published AWS test vectors rather than trusted to be right.
The endpoint decides the region, not the environment
Where the base URL is an AWS-owned Bedrock runtime hostname, the region in it is authoritative. A configured signing region that contradicts it skips the provider with an error naming both, because using the environment value would sign for one region while sending prompts to another. Merely putting a region label in an operator-controlled hostname is not a verifiable mapping and cannot support a residency claim.
One API, several incompatible body shapes
Only the Anthropic-on-Bedrock family is built and read here. A model id in another family is refused with a message naming the family rather than posted as a body the endpoint cannot parse. Cross-region inference profiles such as eu.anthropic.… are the same family — the vendor is the second dotted segment when the first is a geography — and an inference-profile ARN carries the id after its last slash.
Event-stream framing, not server-sent events
Length-prefixed binary messages whose payloads carry base64-encoded Anthropic stream events, capped at the AWS maximum of 16 MiB per message. The two CRC32 checksums in each message are parsed past rather than verified: TLS carries transport integrity, a corrupted frame still fails the length and JSON checks that are enforced, and hand-rolling a CRC32 adds more code to get wrong than it removes.
The error type overrides the status
Bedrock names its failure in x-amzn-errortype rather than in the body, and two of them contradict their status code — a throttle and an exhausted service quota both arrive as 400 in some regions, which the shared classifier would file as a malformed request and the router would then refuse to retry. ThrottlingException, ServiceQuotaExceededException and TooManyRequestsException are forced to rate_limited; ModelTimeoutException to timeout; ExpiredTokenException, InvalidSignatureException and UnrecognizedClientException to auth.
Four values where every other provider needs one
The provider row names the secret access key — the only one of the four that is a credential — and the access key id, region and session token derive from that name with a fall back to the conventional AWS variables. The bare AWS_SECRET_ACCESS_KEY is reserved and never read, so an operator still has to state in an audited row which variable this upstream may sign with.
The base URL may be omitted
When it is, it is derived from the region as the standard regional runtime host. An explicit value still wins, so a VPC endpoint or a FIPS endpoint stays expressible by stating it — and the region pattern admits PrivateLink names and the -fips and .vpce variants without accepting an arbitrary DNS name as a residency assertion.

Why this integration has a different shape from the others

On every other provider the change is symmetrical: the agent speaks a dialect, the gateway speaks the same dialect upstream, and the only difference between the two hops is which credential is attached. Bedrock breaks that symmetry because it is not bearer-authenticated. Requests are signed with SigV4 over the canonical request, which means there is no token an agent could hold that the gateway would forward, and it means the AWS secret has to live in the gateway process rather than in the agent.

So the integration is two halves. Your agent keeps speaking a dialect the gateway accepts — Anthropic Messages is the natural one, because it is the body shape spoken upstream — and presents a gateway-minted agent key. Token Observe resolves the route, applies the whole governance path, and then signs the outbound request itself. What decides that a call goes to Bedrock rather than to Anthropic directly is the route rule that matched the model, not the path the client used, so moving a model onto Bedrock is a route-rule change rather than a fleet-wide reconfiguration.

That arrangement has one consequence worth stating plainly. The gateway now holds AWS credentials, which is exactly the concentration the base-URL and key-name allowlists exist to bound: a provider row may name a credential only under an allowed prefix, and AWS_BEDROCK_ is on that list precisely because bare AWS_ is not — bare AWS_ would make a host’s ambient cloud credentials nameable from a provider row. The reserved names go further, and AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN are refused outright so that widening the prefix list later cannot readmit them.

A base-URL change is the normal way in for the supported dialects, and it applies to the agent half of this integration as it does anywhere else. If the agent half does not take effect, the first thing to check is your own SDK and its version rather than the gateway — the Anthropic dialect takes no trailing /v1 where the OpenAI dialect does, and libraries disagree about whether a constructor argument beats an environment variable.

Residency, and why a contradictory region skips the provider

Three things have to agree before a Bedrock provider row will build: the region the endpoint is in, the region the request is signed for, and the region the row declares as its data-policy residency. Where they do not agree, the provider is skipped with a named reason in the log rather than being loaded and used, because each disagreement is a way of quietly sending prompts somewhere other than where the policy says they go.

The endpoint is authoritative when its hostname proves a region, and only an AWS-owned Bedrock runtime name counts as proof. That is a deliberately narrow test: putting a region label into an operator-controlled hostname is not a verifiable mapping between that proxy and the region where prompts actually leave the process, so a custom endpoint may not claim residency at all. The pattern does admit the real variants — PrivateLink names, the FIPS endpoints and the VPC-endpoint form — so a legitimate private path stays expressible without accepting an arbitrary DNS name as a residency assertion.

The refusals follow from that. A row declaring residency on an endpoint with no verifiable regional mapping is refused. A row declaring one region on an endpoint in another is refused. A configured signing region contradicting the endpoint’s region is refused, and so is a declared residency contradicting the signing region. In each case the alternative is worse than an outage: a provider that signs for one region while sending prompts to another, or that reports a residency its traffic does not honour, is a data-policy claim that is false in the one document an auditor will read.

Residency here is an operator declaration recorded and audited against the row, not a verification of a contract. Token Observe records that you asserted zero retention, no training and a serving region, honours all three separately in routing, and does not check the agreement those assertions describe. Three independent booleans rather than one flag, because providers genuinely differ on each — a provider may retain but not train, or train but not retain, and region pinning is orthogonal to both.

Derived credentials, one audited name
The provider row names the secret access key; the access key id, region and session token derive from that prefix and fall back to the conventional AWS names. The fallbacks are bounded on purpose: an access key id travels in the clear in every SigV4 authorization header, a session token is inert without the secret, and a region is not a secret at all.
A missing credential is a skip, not a crash
An incompletely credentialed Bedrock row is logged by name and left out of the registry, so routing sees it as unavailable and fails over rather than the process refusing to boot. The same is true of a missing region or a contradictory one.
The host allowlist
Bedrock is per-region, so it has no single hostname to list. The default entry is a single-label wildcard over the regional runtime hosts, and a wildcard never spans a dot — which keeps it as tight as an exact entry rather than admitting a lookalike domain.

Token accounting and streaming, where Bedrock inherits Anthropic’s conventions

Because the body shape is the Anthropic one, the usage shape is too: cache reads and cache writes are reported alongside input tokens rather than inside them, and the adapter reads them straight into the four mutually exclusive buckets — uncached input, cache reads, cache writes, output. This is the opposite of OpenAI’s and Gemini’s inclusive convention, and the direction of the error if you read one with the other’s assumption is opposite as well: inclusive read as exclusive double-counts every cached token, and exclusive read as inclusive undercounts the input side.

Prices are matched on provider kind as well as model, which is what keeps a Bedrock-served model priced against your Bedrock row rather than against a direct-vendor row for the same model name. That distinction is not cosmetic — the same model costs different amounts through different channels — and it is also why a failed-over request is metered against the provider that actually served it rather than against the one the route originally resolved. Metering against the original was a real defect: it priced against the wrong provider kind and attributed spend to a provider that never ran the call.

Streaming is where Bedrock differs most from the rest. The upstream sends AWS event-stream framing — length-prefixed binary messages, each capped at the AWS maximum of 16 MiB, whose payloads carry base64-encoded Anthropic stream events — rather than server-sent events. The gateway parses that framing and re-emits the client’s dialect, so a caller on POST /v1/messages sees ordinary named-event frames. Exception types inside the stream are classified separately from HTTP status: a throttle is rate-limited, a model timeout is a timeout, a validation exception is an invalid request, an access denial is auth, and anything else — including a stream error or an internal server exception — is a server error, which is a class that may fail over.

One limitation is published rather than implied. The two CRC32 checksums in each event-stream message are parsed past rather than verified. The reasoning is that transport integrity is TLS’s job on this hop, a corrupted frame still fails the length and JSON checks that are enforced, and hand-rolling a CRC32 implementation adds more code to get wrong than it removes.

What fails over from Bedrock, and the status codes that lie

The three classes that fail over are timeout, rate_limited and server_error; the four that do not are context_too_long, content_policy, auth and invalid_request. On Bedrock the mapping needs provider-specific correction, because AWS names its failure in the x-amzn-errortype header rather than in the body and two of those names contradict their status code.

A throttle and an exhausted service quota both arrive as 400 in some regions. Under the shared status classifier a 400 is an invalid request, which is one of the four classes that never fail over — so a throttled Bedrock call would sit in the class reserved for requests that are wrong rather than requests that are temporarily refused, and your fallback chain would never be tried. ThrottlingException, ServiceQuotaExceededException and TooManyRequestsException are therefore forced to rate_limited. ModelTimeoutException becomes a timeout. And ExpiredTokenException, InvalidSignatureException and UnrecognizedClientException become auth failures, which keeps them out of the failover set — a clock skew or a mis-derived signing key fails identically wherever that credential is used, and the exception name is the only diagnostic AWS gives, so it has to survive into the message.

The unsupported-family refusal is the other Bedrock-specific stop, and it happens before egress rather than at the endpoint. One API fronts several model families with incompatible bodies, so a model id whose family is not the Anthropic one is refused with the family named, rather than being posted as a body Bedrock would reject with a validation exception that says nothing about the cause. Cross-region inference profiles are resolved correctly for this test — a geography prefix moves the vendor to the second dotted segment, and an inference-profile ARN carries the id after its last slash.

Everything above the classification is provider-agnostic: five consecutive failures opens that provider’s circuit breaker for thirty seconds, retries within a provider use capped backoff with jitter on idempotent calls only, and failure surfaces as ACP_UPSTREAM_TIMEOUT at 504, ACP_PROVIDER_UNAVAILABLE at 502 or ACP_INVALID_REQUEST at 400.

Do my agents need AWS credentials?

No, and that is the point of the split. Bedrock is SigV4-signed rather than bearer-authenticated, so there is no token an agent could hold that the gateway would forward. Your agent presents a gateway-minted agent key and speaks a dialect the gateway accepts — Anthropic Messages at POST /v1/messages is the natural one, because it is the body shape spoken upstream — and Token Observe holds the AWS credentials and signs the outbound request. The secret never reaches the wire in either direction: only a signature over the canonical request does. What decides that a call reaches Bedrock rather than Anthropic directly is the route rule that matched the model, so moving a model between the two is a route-rule change rather than a fleet reconfiguration.

Which Bedrock models can I call?

The Anthropic family, including cross-region inference profiles such as the eu-prefixed and us-prefixed ids, and inference-profile ARNs. Any other family is refused before egress with a message naming the family it belongs to. The reason is that one Bedrock API fronts several model families whose request and response bodies are mutually incompatible, and only the Anthropic-on-Bedrock shape is built and read here — posting another family’s body would produce a validation exception from AWS that tells the operator nothing about the cause. Refusing with the family named is the more useful failure, and it is a real limit rather than a temporary one: adding a family means building and testing another body shape.

Why did my Bedrock provider row not load?

Most often because the region does not agree with itself. The endpoint host is authoritative whenever it is an AWS-owned Bedrock runtime name, and a signing region from the environment that contradicts it skips the row with an error naming both — using the environment value would sign for one region while sending prompts to another. A declared data-policy residency on an endpoint with no verifiable regional mapping is refused for the same reason, because a region label inside an operator-controlled hostname proves nothing. The other common causes are an incomplete credential set, since Bedrock needs four values where every other provider needs one, and a credential variable outside the allowed prefixes — bare AWS_ is deliberately absent from that list, and AWS_SECRET_ACCESS_KEY is reserved and never read. Every one of those is a skip with a named reason in the log rather than a boot failure, so the rest of the gateway keeps running.

Is Bedrock streaming handled differently?

Yes, upstream. Bedrock streams AWS event-stream framing rather than server-sent events: length-prefixed binary messages, each capped at the AWS maximum of 16 MiB, whose payloads carry base64-encoded Anthropic stream events. The gateway parses that framing and re-emits the caller’s own dialect, so a client on POST /v1/messages sees ordinary named-event frames and does not know the difference. One deliberate limitation is published: the two CRC32 checksums in each message are parsed past rather than verified, on the reasoning that TLS carries transport integrity on this hop, a corrupted frame still fails the length and JSON checks that are enforced, and hand-rolling a CRC32 adds more code to get wrong than it removes.

Does routing through Bedrock change how spend is attributed?

Yes, and correctly. Prices are matched on provider kind as well as on model, so a model served through Bedrock is priced against your Bedrock row rather than against a direct-vendor row carrying the same model name — the same model costs different amounts through different channels. A failed-over request is metered against the provider that actually served it on both the buffered and streamed paths, which was once a defect worth naming: metering against the originally resolved route priced against the wrong provider kind and attributed the spend to a provider that never ran the call. If a USD budget is configured and any candidate on the resolved route has no active price row, the request is refused before egress with ACP_BUDGET_UNPRICED rather than being priced at zero.

get in touch

Name the SDK and the version.

A base-URL change is the normal case for the Amazon Bedrock dialect, and whether your own client library and version behave that way is the first thing worth checking. Say which you use and you will get a straight answer.

no form · no qualification step · no sales desk · the other three ways in