# Amazon Bedrock

A region, two or three AWS variables, and the pricing rule that turns one model into three different rates depending where you called it.

Source: https://tokentriage.com/docs/build/providers-bedrock

---
import Callout from '../../../components/Callout.astro';
import Details from '../../../components/Details.astro';
import NextSteps from '../../../components/NextSteps.astro';

Bedrock is the family where the region is not a detail. It selects the endpoint host, it is the
scope every request is signed against, and it changes the price: in the committed snapshot the same
Claude model bills $0.80 per million input tokens in the United States and $0.25 in Europe. A
gateway that treats region as cosmetic will hand you a confident wrong figure.

You need a region, an access key pair, and the Bedrock model id in the form AWS publishes it
(`anthropic.claude-3-5-haiku-20241022-v1:0`, not `claude-3-5-haiku`).

## The config

```yaml
upstreams:
  bedrock:
    provider: bedrock
    base_url: https://bedrock-runtime.us-east-1.amazonaws.com
    region: us-east-1                       # signing scope and price selector
    auth:
      access_key_id_env: AWS_ACCESS_KEY_ID
      secret_access_key_env: AWS_SECRET_ACCESS_KEY
      session_token_env: AWS_SESSION_TOKEN  # optional; temporary credentials

tiers:
  - name: cheap
    upstream: bedrock
    model: anthropic.claude-3-5-haiku-20241022-v1:0
```

Every one of those `*_env` fields holds a variable *name*. Three variables, none of them in the
file.

`region:` has its own field rather than living under `params:`. Writing it as a param is turned away
with `params.region: use the dedicated region: field, not params`. Leave it out entirely and
validation stops you:

```console
$ tokentriage validate --config tokentriage.yaml
Error: configuration is invalid: upstreams["bedrock"].region: provider "bedrock" requires a region (aws_sigv4 signing scope / region-qualified pricing / a {region} URL template)
```

The upstream name inside the quotes is yours; the rest of the sentence is the binary's, and it is
worth grepping for. One thing is tidied above: the command prints that message twice, because it
returns the error and then prints it again on the way out.

<Details summary="Using a Bedrock API key instead of signing">

Set `api_key_env` alone, with no `auth:` block, and the key is presented as a bearer token rather
than signing the request. `region:` is still required, because it still selects the price.

```yaml
upstreams:
  bedrock:
    provider: bedrock
    base_url: https://bedrock-runtime.us-east-1.amazonaws.com
    region: us-east-1
    api_key_env: AWS_BEARER_TOKEN_BEDROCK
```

</Details>

## How the region reaches the price

Two different values come out of the one you wrote. The raw region signs the request, because
SigV4 needs `us-east-1`. A shorter *region class* looks up the price, because the catalog keys its
region-qualified rates by continent.

| Your `region:` | Class used for pricing |
|---|---|
| `us-*` | `us` |
| `eu-*` | `eu` |
| `ap-*` | `apac` |
| anything else | none, and the row is `region_unknown` |

That last row is the one to plan around. A Bedrock upstream in `ca-central-1`, `sa-east-1` or
`me-south-1` signs correctly and gets its answer, and any model whose rates are region-qualified
comes back with no dollar rather than a neighbouring region's.

## The rule that produces a missing number instead of a wrong one

Each region class is a separate key in the price file, and you can read one back without sending a
request:

```console
$ tokentriage prices show bedrock/eu.anthropic.claude-3-5-haiku-20241022-v1:0
model:      bedrock/eu.anthropic.claude-3-5-haiku-20241022-v1:0
resolved:   bedrock/eu.anthropic.claude-3-5-haiku-20241022-v1:0
provider:   bedrock
snapshot:   litellm@2026-07-14#179affb
source:     litellm  fetched 2026-07-14  generator pricegen v2
upstream_sha256: 179affb29103fed8b02a83e22e93209c2c65a393cbf9dd785dbaec0f8748d6f7

RATE         USD / TOKEN          USD / 1M TOKENS
input        2.5e-07              $0.2500
output       1.25e-06             $1.2500
cache_read   2.5e-08              $0.0250
cache_write  3.125e-07            $0.3125
reasoning    (fallback = output)  $1.2500
```

Swap `eu.` for `us.` and the input rate is $0.80 with output at $4.00. The snapshot id and digest
in that header are the build's, not your account's, and the same command on your machine prints the
same figures while you run the same build.

The third class is where it gets interesting. There is no `apac.` key for this model:

```console
$ tokentriage prices show bedrock/apac.anthropic.claude-3-5-haiku-20241022-v1:0
Error: prices: model "bedrock/apac.anthropic.claude-3-5-haiku-20241022-v1:0" not found in snapshot litellm@2026-07-14#179affb
```

A region-neutral key for that model *does* exist, at `bedrock/anthropic.claude-3-5-haiku-20241022-v1:0`.
It is not used.

<Callout type="key">
Once a model has any region-qualified rate, a region without one resolves `region_unknown`. The
region-neutral entry is not substituted, and a neighbouring region's rate is not borrowed. The
cost is a row with no dollar; the alternative is a row with the wrong dollar, off by a factor of
three in the example above.
</Callout>

Models whose catalog entry is region-neutral throughout are unaffected:
`amazon.titan-text-lite-v1` prices at $0.30 per million input in every class, because the file
holds no region-qualified key for it to defeat the neutral one.

## Two Bedrock descriptors

`bedrock` and `bedrock_converse` speak the identical Converse wire and share the driver. They
differ only in which price bucket they bind, and the buckets do not overlap: `bedrock/` carries 267
priced keys and `bedrock_converse/` carries 138, and no model key appears in both. Those are counts
of rows in the vendored `litellm@2026-07-14#179affb` price file rather than claims about what either
descriptor can reach.

Amazon's own Nova models are the case that catches people. They are keyed under
`bedrock_converse/` and not under `bedrock/`, in all four forms:

```console
$ tokentriage prices show bedrock_converse/us.amazon.nova-lite-v1:0
model:      bedrock_converse/us.amazon.nova-lite-v1:0
resolved:   bedrock_converse/us.amazon.nova-lite-v1:0
provider:   bedrock_converse
snapshot:   litellm@2026-07-14#179affb
source:     litellm  fetched 2026-07-14  generator pricegen v2
upstream_sha256: 179affb29103fed8b02a83e22e93209c2c65a393cbf9dd785dbaec0f8748d6f7

RATE         USD / TOKEN          USD / 1M TOKENS
input        6e-08                $0.0600
output       2.4e-07              $0.2400
cache_read   (fallback = input)   $0.0600
cache_write  (fallback = input)   $0.0600
reasoning    (fallback = output)  $0.2400
```

So: use `bedrock` unless your models resolve under the separate `bedrock_converse/` bucket. If a
model comes back `model_unknown` on one, try the other before concluding it is unsupported. Nova
carries `us.`, `eu.` and `apac.` keys as well as a neutral one, so the rule above applies to it in
full.

## Reaching Bedrock from an SDK you already have

On the translation axis, two client formats come in and four upstream dialects go out. An
OpenAI-format or an Anthropic-format client reaches a `bedrock` upstream, streaming and not, and the
same two formats also reach `openai`, `anthropic` and `gemini` upstreams. A Bedrock-format client
reaches only a Bedrock upstream, because `bedrock` is a translation target rather than an inbound
client format.

That asymmetry is a decision with a reason, and the reason is evidence rather than difficulty: no
population of Bedrock-SDK clients pointed at a third-party gateway has been observed, and switching
on a direction nobody has verified would leave an unowned surface behind rather than a feature. It
behaves like a decision too. A Bedrock-format request is not rejected: the translator returns a
named error, and the request is then served byte-faithfully on its own dialect, a Bedrock client
reaching a Bedrock upstream, which loses nothing at all.

The full grid is drawn once, on the [dialect matrix](/docs/build/providers-dialect-matrix), rather
than redrawn here. What a translated row records about fields that could not cross is on
[translation loss](/docs/build/providers-translation-loss), and the streaming half of it is on
[streaming translation](/docs/build/providers-streaming-translation).

## Prefixes that look missing and are not

Two catalog prefixes in this family sit outside the covered list, and neither one is a driver gap.
`bedrock_mantle` holds 10 synthetic or future model ids on Bedrock. `amazon_nova` holds 4 shorthand
ids such as `nova-lite-v1`, which is not the Bedrock model id you send: that is
`amazon.nova-lite-v1:0`, and it prices under `bedrock_converse/` as shown above.

Both belong to the published gap partition's `alias-or-sub-namespace` class, whose own column
heading in the generated table reads "Why it's not a driver gap". Binding either would be a
pricing-bucket descriptor, pure data on the same pattern `bedrock_converse` follows, with no new
wire code. The condition for doing it is an operator who uses that sub-namespace, which is a
condition and not a date or a queue position. The
[coverage page](/docs/build/providers-coverage) carries the whole partition.

## What is verified, and what is not

`aws_sigv4` is the scheme these two descriptors declare, and they are the only two of the 95
built-in provider descriptors TokenTriage ships that declare it. Seven of the 95 carry no gated
cell at all and neither of these two is among them: each declares one endpoint, chat, and the
generated matrix gates both of those cells at its strongest evidence level, a
[row-stamp](/docs/build/providers-reference). The matrix names the gate per cell, so you can run
one. Here they are `TestBedrockCostGoldenRegionPriced` and `TestBedrockConverseDescriptor`.

Three sibling tests in the same file carry the rest of this page. `TestBedrockSigV4Wire` asserts the
AWS4-HMAC-SHA256 header and that the signature covers the forwarded body bytes.
`TestBedrockStreamFidelity` asserts the binary event stream reaches the client byte-for-byte as the
upstream sent it. `TestBedrockCostUnpricedRegionUnknown` asserts that a row from an unmapped region
carries no dollar and no borrowed key. Every one of them runs against a mock, not against AWS, and
no claim here is a claim about your account's negotiated rates.

`bedrock` declares chat and nothing else. Bedrock's embedding and image surfaces are not wired, so
those calls are forwarded and ledgered without a cost figure.

## What can go wrong

| Symptom | Cause | Fix |
|---|---|---|
| `requires a region` | no `region:` field | add it; `params.region` is turned away with its own message |
| `credentials_file_env: not valid for the "aws_sigv4" scheme` | Google-style credentials block | use the AWS pair, or `api_key_env` alone |
| every request gets a signing error | wrong region for the host, or a stale session token | region must match the host; refresh temporary credentials |
| `region_unknown` on every row | your region is outside `us-`, `eu-` and `ap-` | expected; the rate does not exist for that class |
| `model_unknown` with a correct-looking model | the short model name, or the wrong Bedrock bucket | use the full AWS id; try `bedrock_converse` |

<NextSteps items={[
  {title: "Which pricing key your model lands under", href: "/docs/build/providers-pricing-keys", description: "The full resolution order, including where the region step sits and why it stops early instead of falling back."},
  {title: "Authentication schemes", href: "/docs/build/providers-auth", description: "SigV4 in detail, the bearer short-circuit you saw above, and what auth health will and will not tell you."},
  {title: "Gemini, Vertex AI and Model Garden", href: "/docs/build/providers-google", description: "The other cloud family, where a project and a location replace the region and a key file replaces the AWS pair."}
]} />
