Amazon Bedrock Cost Optimization: Tokens, Tiers, Caching
Amazon Bedrock cost optimization with us-east-1 prices: model choice, output length, batch and Flex, prompt caching, vector stores, guardrails and tags.
On most AWS services, whoever picks the infrastructure sets the price of a request. On Bedrock, whoever writes the prompt sets it too. The model ID, the length of the system prompt, how much the model may say back and whether the call could have waited an hour are all decided in application code, one request at a time. That’s where most Bedrock waste sits, and it’s why the bill rarely shrinks when someone changes a setting in the console.
Around the model calls are charges that ignore traffic completely: a vector store billing by the hour, capacity left over from a fine-tuning trial, a guardrail checking text that never needed it. Prices below are us-east-1 list prices at the time of writing, from the AWS Price List. The worked numbers use Amazon Nova models because AWS publishes their rates directly; third-party models follow the same mechanics at their own prices.
How Bedrock bills
Text models charge per token, and output is the expensive side. Nova Pro lists $0.80 per million input tokens and $3.20 per million output tokens, so every token the model writes costs four times one you send. How you call the model then moves that rate:
| Option | Price against Standard on-demand | Fits |
|---|---|---|
| Standard (default) | List price | Interactive traffic |
| Batch inference | 50% lower, on supported models | Large jobs that can wait for a result file |
| Flex tier | 50% lower | Requests that can queue behind Standard traffic |
| Priority tier | 75% higher | Requests where latency is the product |
| Latency-optimized (preview) | 25% higher on Nova Pro | A few models, in a few Regions |
| Global cross-Region inference | About 10% below geographic cross-Region | Traffic with no data-residency limit |
| Prompt cache reads | 75% below input on Nova; varies by model | Long prefixes reused within minutes |
| Provisioned Throughput | Per model unit per hour | Guaranteed throughput, older custom models |
| Reserved tier | Per 1,000 tokens-per-minute, 1 or 3 month terms | Guaranteed capacity |
One billing detail trips people up. Bedrock appears in Cost Explorer as both Amazon Bedrock and Amazon Bedrock Service, and some third-party models bill through AWS Marketplace as their own service, with “(Amazon Bedrock Edition)” in the name. Filter on one name and you’ll undercount. The usage types, at least, spell out model and mode, as in USE1-NovaPro-output-tokens-batch.
Find which model and mode costs the most
Start with last month by usage type:
START=$(date -u -d "$(date -u +%Y-%m-01) -1 month" +%Y-%m-%d 2>/dev/null || date -u -v1d -v-1m +%Y-%m-%d)
END=$(date -u +%Y-%m-01)
aws ce get-cost-and-usage \
--time-period Start="$START",End="$END" \
--granularity MONTHLY \
--metrics UnblendedCost \
--filter '{"Dimensions":{"Key":"SERVICE","Values":["Amazon Bedrock","Amazon Bedrock Service"]}}' \
--group-by Type=DIMENSION,Key=USAGE_TYPE \
--query 'ResultsByTime[0].Groups[].[Keys[0],Metrics.UnblendedCost.Amount]' \
--output text | sort -t$'\t' -k2 -nr | head -20
Then pull a week of calls and tokens for the top model. Output tokens divided by invocations is the average answer length, the first number we look at:
START=$(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -v-7d +%Y-%m-%dT%H:%M:%SZ)
END=$(date -u +%Y-%m-%dT%H:%M:%SZ)
MODEL=amazon.nova-pro-v1:0 # the model or inference profile ID your code sends
for m in Invocations InputTokenCount OutputTokenCount CacheReadInputTokenCount; do
printf '%s\t' "$m"
aws cloudwatch get-metric-statistics --namespace AWS/Bedrock --metric-name "$m" \
--dimensions Name=ModelId,Value="$MODEL" \
--start-time "$START" --end-time "$END" --period 86400 --statistics Sum \
--query 'sum(Datapoints[].Sum)' --output text
done
Pick the smallest model that passes your evals
The gap between sizes is bigger than most teams assume. Nova Micro lists $0.035 input and $0.14 output per million tokens, Nova Lite $0.06 and $0.24, Nova Pro $0.80 and $3.20. Lite to Pro is more than 13 times the price on both sides, and classification, extraction and short summaries often pass on the small one.
Prove it with an eval set of a few hundred real, anonymised requests and what a good answer looks like for each. Run every candidate, score the results (Bedrock’s model evaluation can use a judge model, billed at on-demand rates), record tokens per request beside each score, and pick the cheapest model that passes. A small model that needs twice the examples in its prompt, or a retry when its JSON won’t parse, gives back much of its discount, and the token column shows it.
Intelligent prompt routing picks between two models of one family per request, such as Nova Lite and Nova Pro, for $1 per 1,000 requests (English prompts only). A request with 2,000 input and 300 output tokens costs $0.000192 on Lite and $0.00256 on Pro, so the router’s $0.001 only pays once more than about 42% of traffic lands on Lite. A plain rule in your own code, by feature or input length, is free.
Ask for shorter answers
With output at four times the input rate on Nova Pro, and five times on several third-party models, the cheapest token is the one never written. Ask for JSON with only the fields you’ll read, set a sentence limit, tell the model not to restate the question, and use stop sequences where the format allows.
maxTokens is widely misunderstood. You’re billed for tokens produced, not the limit, so lowering it doesn’t cut the bill by itself. It does stop runaway answers, and Bedrock reserves the full value against your tokens-per-minute quota as each request starts, so an oversized limit gets you throttled sooner. Set it a little above the longest good answer in your eval set and watch how often stopReason comes back as max_tokens. A truncated answer that gets retried costs more than it saved.
Send work that can wait to batch or Flex
Batch inference reads a JSONL file from S3, one recordId and modelInput per line, and writes results back at 50% below on-demand on supported models. Backlog classification, catalogue descriptions and evaluation runs fit well. Jobs have a minimum record count (see the Bedrock service quotas), and prompt caching doesn’t apply, but batch halves output as well as input, which caching never does.
The Flex tier gives the same 50% on Nova Pro and the Nova 2 models without building a job. You send normal requests and accept that, when traffic is high, they’re processed after Standard ones. In Converse it’s one field:
{
"modelId": "amazon.nova-pro-v1:0",
"messages": [{"role": "user", "content": [{"text": "Summarise this support ticket in two sentences: ..."}]}],
"inferenceConfig": {"maxTokens": 200},
"serviceTier": {"type": "flex"}
}
InvokeModel takes the X-Amzn-Bedrock-Service-Tier header instead, and CloudWatch’s ResolvedServiceTier dimension shows which tier actually served each request.
Premium options need the opposite discipline. Priority costs 75% more (Nova Pro at $1.40 and $5.60) and latency-optimized Nova Pro 25% more ($1.00 and $4.00); use them only where you’ve measured that latency changes what users do, never as a library default. Global cross-Region inference, called with a global. profile ID, runs about 10% below geographic cross-Region prices where compliance allows processing outside your geography.
Cache the prefix you send on every call
If every request opens with the same long system prompt, tool definitions or reference document, put a cache checkpoint after that static part. Most models keep the cache for five minutes, reset on every hit, and some Claude models offer one hour. Minimums run from 512 to 4,096 tokens per checkpoint depending on the model. Nova caches up to 20,000 tokens of system and message content, but not tools.
Pricing differs by family. Nova cache reads cost a quarter of the input rate (Nova Pro: $0.20 per million instead of $0.80) and the price list shows Nova cache writes at $0.00. Claude Sonnet 4 through global inference charges $3.75 per million to write against $3.00 for plain input, and $0.30 to read. With a write premium, a cached prefix pays off from its second use inside the TTL; used once, it costs 25% more.
The cache matches from the start of the prompt (tools, then system, then messages), so a timestamp at the top of the system prompt means nothing ever hits. Check cacheReadInputTokens in the response, or the CacheReadInputTokenCount metric above. Features whose requests arrive more than five minutes apart won’t benefit.
Capacity you pay for by the hour
Provisioned Throughput is sold per model unit per hour. Nova Micro, Lite and Pro all list $60.50 an hour with no commitment, $55.00 on a one-month term and $30.25 on six months. One unit on six months is $22,082.50 a month over 730 hours, enough to buy roughly 6.9 billion Nova Pro output tokens on demand, so treat it as guaranteed throughput, not a discount. A no-commitment unit bills every hour until someone deletes it, which makes it the classic leftover from a fine-tuning trial.
It may not be needed now. Fine-tuned Nova Micro, Lite, Pro and Nova 2 Lite models customised on or after 16 July 2025 can use on-demand deployment in us-east-1, billed per token at the base model’s rate plus $1.95 per model per month for storage. The Reserved tier (fixed tokens-per-minute on 1 or 3 month terms, through your account manager) also bills until deleted.
aws bedrock list-provisioned-model-throughputs \
--query 'provisionedModelSummaries[].[provisionedModelName,modelUnits,status,commitmentDuration,commitmentExpirationTime]' \
--output table
aws bedrock list-custom-model-deployments \
--query 'modelDeploymentSummaries[].[customModelDeploymentName,status,createdAt]' --output table
An empty commitment column means no term, so the unit can go at any time. Check the invocation logs or CloudTrail for recent calls first, then:
# DESTRUCTIVE: anything still calling this provisioned model will fail.
PT_ID=finetune-trial-pt
aws bedrock get-provisioned-model-throughput --provisioned-model-id "$PT_ID" \
--query '[provisionedModelName,modelUnits,commitmentDuration,status]' --output text
read -r -p "Delete provisioned throughput $PT_ID? [y/N] " ok
[ "$ok" = "y" ] && aws bedrock delete-provisioned-model-throughput --provisioned-model-id "$PT_ID"
Knowledge Bases: price the vector store first
A vector knowledge base pays for embedding calls plus whatever holds the vectors. The most common store has been OpenSearch Serverless, where a Classic collection bills OCUs at $0.24 an hour with a floor of 2 OCUs for a highly available setup: $350.40 a month before anyone searches. A 1 OCU dev and test option without high availability halves that. Next-generation collections in a collection group with a minimum of 0 OCUs scale to zero after 10 idle minutes, at the cost of a 10 to 30 second cold start.
Two other options change the maths. S3 Vectors charges $0.06 per GB-month and $2.50 per million queries, plus a per-GB processing fee, and suits latency-tolerant retrieval. Managed Knowledge Base, generally available since June 2026, runs the store for you and charges $5.00 per GB of raw data per month plus $1.00 per 1,000 retrieve calls. Ten gigabytes of documents is $50 a month there, well under a Classic floor; at 100 GB it’s $500 and the comparison flips.
for id in $(aws bedrock-agent list-knowledge-bases --query 'knowledgeBaseSummaries[].knowledgeBaseId' --output text); do
aws bedrock-agent get-knowledge-base --knowledge-base-id "$id" \
--query 'knowledgeBase.[name,knowledgeBaseConfiguration.type,storageConfiguration.type]' --output text
done
aws opensearchserverless list-collections \
--query 'collectionSummaries[].[name,status,collectionGroupName]' --output table
aws opensearchserverless list-collection-groups \
--query 'collectionGroupSummaries[].[name,generation,numberOfCollections,capacityLimits.minSearchCapacityInOCU]' --output table
A collection with no group is Classic and pays its floor every hour. One behind a demo nobody opens is the same kind of leftover we cover in idle and orphaned AWS resources.
Guardrails bill per policy, per 1,000 characters
Guardrails charge per 1,000 text units, a text unit being up to 1,000 characters. Content filters and denied topics cost $0.15 each, sensitive information filters $0.10 (regex and word filters are free), contextual grounding checks $0.10. Each policy bills on each text it checks, so guarding both prompt and response pays twice.
That can outgrow the model. A retrieval request with 8,000 characters of question and passages, a 1,200-character answer, and content filters plus denied topics on both is 10 text units at $0.30 per 1,000: $0.003. The Nova Lite call that wrote the answer cost about $0.0002.
You don’t have to drop safety to fix it. In Converse, wrap only the user’s own text in a guardContent block so your own documents aren’t checked again, and remove policies that don’t apply to the feature. On public chat, keep the full set.
Tag the calls, not just the resources
Cost Explorer can only split Bedrock by application if something carries a tag. There are three ways in:
- Application inference profiles. One per model per application, tagged, called by ARN in place of the model ID in InvokeModel and Converse. Activate the tag as a cost allocation tag; it isn’t retroactive and takes up to 24 hours to show.
- IAM principal attribution. Bedrock records the caller’s identity on every runtime call, so a role per service splits the dollars.
- Request metadata. Up to 16 key-value pairs per call in
requestMetadata. They never reach Cost Explorer, but they sit beside each call’s token counts in the invocation logs.
Creating a profile is a write, but it changes nothing for existing callers:
aws bedrock create-inference-profile \
--inference-profile-name support-summariser-prod \
--model-source copyFrom=arn:aws:bedrock:us-east-1::foundation-model/amazon.nova-lite-v1:0 \
--tags key=app,value=support-summariser key=env,value=prod
Invocation logging is off by default. Once it’s on, this read-only query totals a week of tokens by model and feature:
LOG_GROUP=$(aws bedrock get-model-invocation-logging-configuration \
--query 'loggingConfig.cloudWatchConfig.logGroupName' --output text)
START=$(date -u -d '7 days ago' +%s 2>/dev/null || date -u -v-7d +%s)
QID=$(aws logs start-query --log-group-name "$LOG_GROUP" \
--start-time "$START" --end-time "$(date -u +%s)" \
--query-string 'stats count(*) as calls, sum(input.inputTokenCount) as in_tok, sum(output.outputTokenCount) as out_tok by modelId, requestMetadata.feature | sort out_tok desc | limit 20' \
--query queryId --output text)
sleep 15
aws logs get-query-results --query-id "$QID" --output text
The logs hold full prompts and answers, so set retention and limit who can read them. They also cost CloudWatch ingestion; moving logs to S3 covers when that’s worth it.
FAQ
Is Amazon Bedrock batch inference cheaper than on-demand?
Yes. Supported models list batch at 50% below Standard on-demand, input and output. Results arrive as a file in S3, not in real time, and prompt caching doesn’t apply.
Does max_tokens affect what I pay on Bedrock?
Not directly; you pay for tokens generated. A high limit reserves more quota per request, though, which can cause throttling.
Why is Bedrock charging me when nobody calls a model?
Look for Provisioned Throughput or Reserved tier capacity, custom model storage, Managed Knowledge Base storage, or OpenSearch Serverless OCUs behind a knowledge base. They bill by time or size, not requests.
How do I track Amazon Bedrock costs per application?
Tag an application inference profile per application and activate the tag in Billing. For cost per request, use request metadata and invocation logs.
Put a price on one request
Run the Logs Insights query and take the feature with the most output tokens. Work out its cost per request at list price, then try the cheapest change that still passes your evals: a smaller model, a shorter answer format, a cache checkpoint or Flex. Compare with Cost Explorer a week later. After that, an automated scan can watch the charges that run without traffic, such as idle provisioned throughput and vector collections nobody queries.
