Skip to main content

Server-Side Fallbacks & Fallback Credit

Advanced

Before Opus 5, a Claude refusal was your problem. The classifier declined, you saw stop_reason: "refusal" come back on a happy HTTP 200, and now you owned the retry: pick another model, resend the full history, watch your prompt cache melt because the new model has a different cache namespace, and try to explain to your finance team why the same conversation billed twice.

The Opus 5 launch (24 July 2026) shipped two related betas that collapse all of that into one API call:

  1. Server-side fallback (server-side-fallback-2026-07-01) — set fallbacks: "default" and the API retries the refused request on a model Anthropic picks for the refusal category, in the same round trip. You can also name up to three targets of your own.
  2. Fallback credit (fallback-credit-2026-07-01) — a one-time credit token attached to every refusal that, when echoed on a retry, reprices the retry as if the conversation had been on the fallback model all along. Cache writes on the new model become cache reads.

The two betas are independent — you can use fallback credit alone if you already have client-side retry logic — but the point of the release is that you should almost never need to. This page walks you through both, from the copy-paste one-liner to the corner cases that bite prod (streaming mid-tool_use, sticky routing, output_config.format locking out the continuation shape).

What you'll learn
  • What a refusal actually looks like on the wire (JSON, five stop categories, when tokens are billed)
  • The three ways to fall back (server-side / SDK middleware / manual raw HTTP) and when each is the right one
  • The one-liner: fallbacks: 'default' plus the beta header, and what the response shape adds
  • Explicit list vs default mode, allowed_fallback_models, and why order matters
  • How fallback credit stops you paying prompt-cache twice — the token, the two retry-body shapes, and what usage.iterations should show
  • The 3-rung rejection ladder every manual retry has to implement (continuation → unchanged body → forfeit token)
  • Where it does NOT work: Message Batches, Bedrock/GCP/Foundry gaps, Sonnet 5, streaming refusals mid tool_use, output_config.format + server tools

★ Insight ───────────────────────────────────── There are two Anthropic-specific fingerprints worth internalizing here. First, a classifier refusal is a 200 with stop_reason: "refusal" — not a 4xx. If your error handler treats non-2xx as "retry" you will silently ignore refusals; if it treats 200 as "success" you will silently show empty content. Neither is what you want. Second, prompt caches are per-model, so a naive retry on a different Claude model always pays the cache-write cost from scratch even when the conversation prefix is byte-identical. The credit token is the piece that closes that hole — and the reason fallback-credit exists as a separate beta from server-side fallback. ─────────────────────────────────────────────────

What a refusal actually looks like

A classifier refusal is a normal message response with an empty content array and stop_reason: "refusal":

{
"id": "msg_01XFUDYJgAACzvnptvVoYEL",
"type": "message",
"role": "assistant",
"model": "claude-fable-5",
"content": [],
"stop_reason": "refusal",
"stop_details": {
"type": "refusal",
"category": "cyber",
"explanation": "This request was declined because it could enable cyber harm."
},
"usage": {
"input_tokens": 412,
"output_tokens": 0
}
}

The stop_details.category is one of five values. Two are null when the refusal doesn't map to a named category (a permanent null, not a placeholder):

categoryWhat triggered it
"cyber"Request could enable cyber harm (malware, exploit dev). Benign cybersecurity work can also trigger it.
"bio"Request could enable biological harm. Beneficial life-sciences work can also trigger it.
"frontier_llm"Request could assist competing-AI-model development, restricted by Anthropic's commercial terms.
"reasoning_extraction"Request asks the model to reproduce its internal reasoning in the response text. Use adaptive thinking to get reasoning in a structured form.
"general_harms"Miscellaneous harm areas; benign work occasionally trips this.

A refusal that arrives before any output is not billed (its tokens show in usage but aren't charged); it still counts against your rate limits. A mid-stream refusal bills the input and the output that already streamed at normal rates. Either way, treat any partial output as incomplete and discard it — the safety classifier fired on the model's own trajectory.

The explanation string is not stable across versions. Display it, do not parse it.

Picking a fallback approach

Three flavors exist. Pick the row that matches you:

Your situationUseWhy
Claude API, want the simplest thingServer-side fallback with fallbacks: "default"One request, one response. The API picks the fallback and applies credit for you.
Any platform (Bedrock, Vertex, Foundry), using an Anthropic SDKSDK middleware (BetaRefusalFallbackMiddleware)Configure once on the client. Retries + credit are automatic. This is the only path on Bedrock / Vertex / Foundry today.
Raw HTTP, custom retry logic, or non-Anthropic SDKManual retry with the fallback-credit-2026-07-01 headerFull control. You implement the 3-rung ladder yourself.

Server-side fallback and the SDK middleware apply fallback credit for you. You only need to think about the credit-token dance if you build the retry yourself.

The one-liner: fallbacks: "default"

The whole feature, in one request:

Server-side fallback in default mode

curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: server-side-fallback-2026-07-01" \
-H "content-type: application/json" \
-d '{
  "model": "claude-fable-5",
  "max_tokens": 1024,
  "fallbacks": "default",
  "messages": [{"role": "user", "content": "Hello, Claude"}]
}'

If Fable 5 declines and the refusal category has an Anthropic-recommended fallback, the API runs the same request on that model in the same call. You get back one response and the top-level model field names whichever model actually answered. If the category has no recommended fallback, the refusal stands and you get the refusal back exactly as if fallbacks were unset.

What "default" is really doing: the API reads the requested model's server-defined routing table and picks a fallback from it based on the refusal category. As Anthropic updates that table (adding a new fallback for a category, promoting Opus 5 to be Fable 5's default target, etc.) you get the new routing for free. That is the pitch: stop maintaining a fallback-model list that will be wrong in a month.

The explicit list, for when you need to pin

If you want to control the routing yourself, pass a list instead of "default". Up to three entries, tried in order:

response = client.beta.messages.create(
model="claude-fable-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello, Claude"}],
fallbacks=[
{"model": "claude-opus-5"}, # try Opus 5 first
{"model": "claude-opus-4-8"}, # then Opus 4.8
],
betas=["server-side-fallback-2026-07-01"],
)

The rules that will trip you if you don't read them:

  • Every target must be a permitted fallback for the requested model. The list of permitted targets is published as allowed_fallback_models on each model's entry in the Models API when the server-side-fallback-2026-07-01 beta header is set. (For Claude Fable 5, at the time of writing that list is claude-opus-4-8 and claude-opus-5.)
  • Entries must be distinct from each other and from the requested model.
  • Each entry can override max_tokens, thinking, output_config, and speed for that attempt only. This is how you say "on the fallback, run at lower effort" without touching your main request.
  • The request must be valid as a direct request to every model named. If a fallback doesn't support a feature the request uses (e.g. a beta the fallback model doesn't accept), the API rejects the whole request up front, not just the fallback attempt.
  • Only classifier refusals trigger the fallback. Rate limits, overloads, and server errors on the requested model surface to you as-is.

The "default" mode only works under server-side-fallback-2026-07-01. The explicit-list form also works under the older server-side-fallback-2026-06-01 header.

What the response contains

The response is a normal message with two additions:

  • The top-level model field names the model that produced the returned message (requested or fallback).
  • A fallback content block marks each point where one model's output gives way to the next: {"type": "fallback", "from": {"model": ...}, "to": {"model": ...}}. On a refusal-before-output, this block is the first content block; on a mid-stream fallback it appears at the handoff point.
  • usage.iterations records every attempt. A model that declined shows as a message entry (its tokens reported but not charged); the model that served the turn shows as a fallback_message entry.

Example after a refusal before any output, when default routing selects Opus 4.8:

{
"id": "msg_01XFUDYJgAACzvnptvVoYEL",
"type": "message",
"role": "assistant",
"model": "claude-opus-4-8",
"content": [
{ "type": "fallback", "from": { "model": "claude-fable-5" }, "to": { "model": "claude-opus-4-8" } },
{ "type": "text", "text": "Hi! How can I help you today?" }
],
"stop_reason": "end_turn",
"stop_details": null,
"usage": {
"input_tokens": 412,
"output_tokens": 264,
"iterations": [
{ "type": "message", "model": "claude-fable-5", "input_tokens": 535, "output_tokens": 0 },
{ "type": "fallback_message", "model": "claude-opus-4-8", "input_tokens": 412, "output_tokens": 264 }
]
}
}

If every model in the chain refuses, the response is the last model's refusal, with a message entry for each earlier hop and a fallback_message entry for the last one.

Continuing the conversation

On the next turn, echo the assistant content as you received it. After a mid-output fallback, the content you got back can include blocks the declining model produced before the handoff. Which to keep and which to drop:

Block typeOn the next turn
fallbackKeep it exactly where it appeared. Its position is used to validate the thinking blocks around it. Moving or dropping it → 400.
textKeep.
Any block after the final fallback blockKeep.
thinking, redacted_thinking, connector_text before the final fallbackDrop.
Client-side tool_use before the final fallbackDrop.
server_tool_use before the final fallbackKeep when paired with its result. Drop when it has no matching result.

The mental model: everything that ran on the fallback model stays; the declining model's uncorroborated intermediate work goes away.

Sticky routing

Once a conversation has fallen back, the API remembers it. Later requests for that conversation that also include a fallbacks parameter go directly to the fallback model, skipping the requested model entirely. This stops you from paying a refusal-tax on every single follow-up in a session that was always going to refuse again.

Properties worth knowing:

  • Retained for ~1 hour, scoped to your organization.
  • Stored as a content hash of the conversation prefix + the model that served it. The message content itself isn't stored server-side.
  • Best effort — your code must still handle the requested model being tried again at any time.
  • A sticky-served turn has no fallback content block (nothing declined that turn). Identify it by the presence of a fallback_message in usage.iterations, the absence of a message entry for the requested model, and the response model field.

On streaming, the routing decision is made before the stream opens, so message_start already carries the fallback model's ID.

Streaming behavior

The retry happens on the same stream — nothing you've already received is invalidated.

Refusal before any output

  • message_start names the fallback model.
  • The fallback block is the first content block.
  • Time to first byte includes the declined attempt (because message_start waits for the fallback to start).

Refusal mid-output

  • The currently open content block closes.
  • A fallback block (content_block_start + content_block_stop, no deltas) marks the boundary.
  • The fallback model continues from the partial output. Only text blocks from the partial output are passed as context to the fallback model; other block types remain in content but are not seen by the fallback.
  • message_start already named the requested model, so read the serving model from the fallback block's to.model and from the fallback_message entry in the final message_delta's usage.iterations.

Non-streaming, mid-output refusal: the response omits the declined model's partial output and the fallback answers from scratch. The result looks like a refusal-before-output — fallback block first — with the declined attempt's tokens still recorded in usage.iterations. This is a real behavior difference from streaming; sizing tests done on the stream can under-predict cost when you flip to non-streaming.

Fallback credit: the invisible repricing

Prompt caches are per-model. If Fable 5 has cached 400k tokens of your conversation prefix and refuses, a naive retry on Opus 5 has to write all 400k into Opus 5's cache from scratch — and cache writes cost more than cache reads. Fallback credit removes that extra cost. The refusal carries a one-time credit token, you echo the token on the retry, and the retry is billed as though the conversation had been on the fallback model all along.

Server-side fallback and the SDK middleware apply credit automatically. You only need to think about the token yourself if you're building the retry over raw HTTP.

The four-step manual flow

Guided walkthrough1 of 4
  1. Send the first request with anthropic-beta: fallback-credit-2026-07-01. (server-side-fallback-2026-07-01 grants the same fields, and the older fallback-credit-2026-06-01 header is still accepted.)

The rejection ladder every manual retry needs

Most retries redeem on the first attempt. When one doesn't, the API returns a 400 that tells you what to try next. Implement all three rungs:

Guided walkthrough1 of 3
  1. The most common cause is that output_config.format or a tool_choice that forces tool use rules out the continuation shape. Drop the appended assistant message; keep the token.
Watch out
  • "redemption temporarily unavailable" is a transient error, NOT a verdict on your retry shape. Retry the SAME request with the SAME token, within the 5-minute window. Do not step down the ladder.

Fields that must match exactly (the strict-match rules)

Redemption compares your retry against the refused request. Every field that shapes the prompt must match:

RuleFields
Must match exactlysystem, messages, tools, tool_choice, thinking, cache_control, and (when used) output_config, mcp_servers, context_management, container
May change on the retrymodel, max_tokens, stop_sequences, temperature, top_p, top_k, stream, metadata, service_tier

The continuation shape is the one exception to the messages match: it adds exactly one assistant message at the end of messages.

Two subtle traps:

  1. Beta headers must also match. A beta header present on one of the two requests but not the other can fail the match even when the bodies are identical. The 400 says request body ... does not match, which reads like a body difference but is a header difference. Two families are exempt: server-side-fallback-* (drop it on retry along with the fallbacks param), and fallback-credit-* (keep it on both).
  2. Do not strip thinking or redacted_thinking blocks from earlier turns on the retry, even though a plain tokenless retry usually does. The body must match the refused request; the server handles those blocks itself.

Checking that the credit actually applied

The refund is visible in the retry's usage. Compared with what the same request would report without the token, cache_creation_input_tokens is lower, and cache_read_input_tokens is higher by the same amount. A shift of zero means the token was honored but there was nothing to reprice (e.g. the retry model's cache was already warm).

Token scope and lifetime

  • Redeems only from the organization and workspace that received the refusal (on Foundry too). On Bedrock and Vertex, which have no workspaces, the token is bound to the platform's caller identity.
  • Expires 5 minutes after the refusal. After that, retry without it.
  • Stateless — the server stores nothing about it, and there's no endpoint to inspect or revoke it.

Where it does not work (or works differently)

Guided walkthrough1 of 6
  1. The fallbacks parameter isn't supported on the Message Batches API (a batch item that includes it comes back as an errored result). Refusals in Message Batches don't mint credit tokens either, and a token passed on a batch request is accepted but ignored. Fall back to client-side retry after the batch resolves.

A pragmatic setup for a production Claude app

Guided walkthrough1 of 5
  1. Zero-effort protection against the categories Anthropic has recommended fallbacks for. It's a superset of the manual approach because the routing table updates automatically.

How this compares to what other providers do

ProviderAutomatic refusal → fallback in one API call?
Anthropic Claude Fable 5 / Opus 5Yes — fallbacks: "default" + credit token. Sticky routing carries follow-ups.
Anthropic Claude Opus 4.8Was the target model of the credit-token-only variant (June 2026 beta). Server-side default mode landed with Opus 5.
OpenAI GPT-5 / 6No first-party server-side fallback. You detect a refusal finish_reason yourself and retry on another model client-side; the Responses API doesn't publish an equivalent of allowed_fallback_models.
Google Gemini 3Refusals surface as SAFETY block reasons; retry is client-side against another model in the family.
AI gateways (LiteLLM, Portkey, OpenRouter)Provider-agnostic router-level fallback exists but is billed independently on each attempt — no per-provider cache-credit equivalent. See AI gateways.

Cross-model harnesses can still use the credit token: it's model-specific but the concept (echo an opaque token on the retry, get repriced) can be feature-detected per provider.

Common failure modes and what they mean

  • You get an empty content array back and your UI shows a blank message. You forgot to check stop_reason: "refusal" before rendering. Detect it and either show a category-specific message or wire in fallbacks.
  • Your retry keeps 400ing with request body ... does not match. Header mismatch, most likely. Diff every anthropic-beta header between the two requests, not just the body.
  • You use the SDK middleware and see the same model billed twice. You forgot to share the BetaFallbackState across requests of the same conversation. Sticky routing needs the state to pin follow-ups.
  • Your cost report shows a big jump on Opus 4.8 even though you thought you were on Fable 5. Sticky routing carried follow-ups after a refusal. Log response.model and usage.iterations to see the split.
  • You forgot the beta header on the retry and got a redemption failure. The retry needs fallback-credit-2026-07-01 to redeem the token.
  • Batch job silently drops your fallbacks. Batches ignore fallbacks and credit tokens. Do the retry after batch completion.
No cards yet — add some to start studying. 🃏

Check yourself

0/7
  1. A Claude Fable 5 request returns HTTP 200 with `stop_reason: 'refusal'` and an empty content array. How much are you billed?
  2. You send `fallbacks: 'default'` with the `server-side-fallback-2026-07-01` header on a Fable 5 request that gets refused with category `reasoning_extraction`. What happens?
  3. Which Claude API fields must match exactly between the refused request and the credit-token retry?
  4. You get `stop_details.fallback_has_prefill_claim: true` on a refusal that fired mid-output. What retry body should you build?
  5. Your streaming Fable 5 request refuses while a `tool_use` block is still open on the stream. What does the API do?
  6. Your billing shows Opus 4.8 charges on turns you thought were going to Fable 5, days after a single refused turn. What is going on?
  7. You've built a strong client-side retry, so you'd rather NOT use server-side fallback. Can you still get the cache-credit savings?

Sources & further reading