Source Intelligence
Sweep 28 Aug 2026 ยท 00:00Z Build v2.1.250 478 read Stable v2.1.236 Latest v2.1.250 Next v2.1.250 Feeds RSS JSON llms.txt

DisclaimerUnofficial, and not affiliated with Anthropic. Nearly all of this is read straight out of what ships: npm bundles, captured prompts, published docs. Anthropic's own notes go in verbatim, marked as theirs. The rest is my reading, and every entry carries the strings behind it. If one looks wrong, vote it down and say why.

Page history

fallback-credit

build-with-claude/fallback-credit

1 recorded change 679 lines First seen Last changed Upstream

History

build-with-claude/fallback-credit First recorded · 679 lines, first recorded

## The basic flow ## Example ## Where it works ## Checking that the credit applied ## When a retry is rejected ## Reference ## Next steps

The first capture of this source. The page was already there, and this is what it said.

---
title: Fallback credit
url: https://platform.claude.com/docs/en/build-with-claude/fallback-credit
description: Avoid paying the prompt-cache cost twice when you retry a refused Claude Fable 5 request on another model.
---

Prompt caches are per-model. When Claude Fable 5 declines a request and you retry on another model, the conversation prefix that was already cached for Claude Fable 5 must be written into the new model's cache from scratch. Cache writes cost more than cache reads. Fallback credit removes that extra cost. The refusal carries a credit token, you echo the token on the retry, and the retry is billed as though the conversation had been on the new model all along.

You need this page only when you build the retry yourself: over raw HTTP or with custom retry logic. [Server-side fallback](https://platform.claude.com/docs/en/build-with-claude/refusals-and-fallback#server-side-fallback) and the [SDK middleware](https://platform.claude.com/docs/en/build-with-claude/refusals-and-fallback#client-side-fallback) apply fallback credit automatically. If you use either, skip this page.

[Refusals and fallback](https://platform.claude.com/docs/en/build-with-claude/refusals-and-fallback) covers detecting refusals and choosing a fallback approach. [Prompt caching](https://platform.claude.com/docs/en/build-with-claude/prompt-caching) explains cache reads and cache writes if those terms are new.

## The basic flow

<Steps>
  <Step title="Opt in with the beta header">
    Send the request that may be refused with the `anthropic-beta: fallback-credit-2026-07-01` header. The `server-side-fallback-2026-07-01` header also grants the same fields, and the earlier `fallback-credit-2026-06-01` header remains accepted and grants the same fields.
  </Step>

  <Step title="Read two fields from the refusal">
    On a refusal, `stop_details` includes two fields:

    * **`fallback_credit_token`:** an opaque string that represents the credit.
    * **`fallback_has_prefill_claim`:** a Boolean that tells you which retry body shape to use.

    Both are `null` when no credit is available for the refusal.
  </Step>

  <Step title="Build the retry">
    Start from the refused request body. Set `model` to the fallback model and add the token as the top-level `fallback_credit_token` parameter. Pick the body shape from the table below.
  </Step>

  <Step title="Send the retry with the same header">
    Send the retry with the same `fallback-credit-2026-07-01` beta header. The retry needs the header to redeem the token.
  </Step>
</Steps>

The `fallback_has_prefill_claim` field tells you whether the retry can continue the refused model's partial output instead of starting over:

| `fallback_has_prefill_claim` | Retry body                                                                                                                                                                                                                                                          |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `true`                       | The refused request body, unchanged, plus one appended assistant message whose `content` echoes the refused response's `content`. The retry model continues the response from where the refused model stopped, and completed server tool calls are not re-executed. |
| `false`                      | The refused request body, unchanged.                                                                                                                                                                                                                                |

## Example

The following example makes a request that may be refused and redeems the credit token on a retry against Claude Opus 4.8. When a retry attempt is rejected, the example degrades through the rejection ladder: the sequence of progressively simpler retry shapes covered in [When a retry is rejected](https://platform.claude.com/docs/en/build-with-claude/fallback-credit#when-a-retry-is-rejected).

<CodeGroup>
  ```bash cURL
  # Initial request (may be refused)
  response=$(curl --fail-with-body -sS https://api.anthropic.com/v1/messages \
    -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-version: 2023-06-01" \
    -H "anthropic-beta: fallback-credit-2026-07-01" \
    -H "content-type: application/json" \
    -d '{
      "model": "claude-fable-5",
      "max_tokens": 1024,
      "messages": [{"role": "user", "content": "Hello, Claude"}]
    }')

  # A refusal carries a one-time credit token in stop_details
  token=$(jq -r '.stop_details.fallback_credit_token // empty' <<<"${response}")

  if [[ -n "${token}" ]]; then
    # Retry on the fallback model with the credit token (same body)
    response=$(curl --fail-with-body -sS https://api.anthropic.com/v1/messages \
      -H "x-api-key: $ANTHROPIC_API_KEY" \
      -H "anthropic-version: 2023-06-01" \
      -H "anthropic-beta: fallback-credit-2026-07-01" \
      -H "content-type: application/json" \
      -d "$(jq -n --arg token "${token}" '{
        model: "claude-opus-4-8",
        max_tokens: 1024,
        messages: [{"role": "user", "content": "Hello, Claude"}],
        fallback_credit_token: $token
      }')")
  fi

  # See the SDK examples for the full rejection-handling ladder.
  jq -c '{stop_reason, model}' <<<"${response}"
  ```

  ```bash CLI
  # Initial request (may be refused)
  response=$(ant beta:messages create \
    --model claude-fable-5 \
    --max-tokens 1024 \
    --message '{"role":"user","content":"Hello, Claude"}' \
    --beta fallback-credit-2026-07-01 \
    --format json)

  # A refusal carries a one-time credit token in stop_details
  token=$(jq -r '.stop_details.fallback_credit_token // empty' <<<"${response}")

  if [[ -n "${token}" ]]; then
    # Retry on the fallback model with the credit token
    response=$(ant beta:messages create \
      --model claude-opus-4-8 \
      --max-tokens 1024 \
      --message '{"role":"user","content":"Hello, Claude"}' \
      --fallback-credit-token "${token}" \
      --beta fallback-credit-2026-07-01 \
      --format json)
  fi

  # See the SDK examples for the full rejection-handling ladder.
  jq -c '{stop_reason, model}' <<<"${response}"
  ```

  ```python Python
  client = Anthropic()

  request = {
      "max_tokens": 1024,
      "messages": [{"role": "user", "content": "Hello, Claude"}],
  }


  def send(model: str, body: dict[str, object]) -> BetaMessage:
      return client.beta.messages.create(
          model=model, betas=["fallback-credit-2026-07-01"], **body
      )


  response = send("claude-fable-5", request)

  if (
      response.stop_reason == "refusal"
      and (details := response.stop_details)
      and (token := details.fallback_credit_token)
  ):
      exact_body = request | {"fallback_credit_token": token}
      # Prefer the continuation shape unless the claim is False
      if details.fallback_has_prefill_claim is not False:
          echoed = [block.model_dump() for block in response.content]
          match echoed:
              case [*_, {"type": "text"} as final_block]:
                  final_block["text"] = final_block["text"].rstrip()
          attempt = exact_body | {
              "messages": [
                  *request["messages"],
                  {"role": "assistant", "content": echoed},
              ]
          }
      else:
          attempt = exact_body

      try:
          response = send("claude-opus-4-8", attempt)
      except BadRequestError as error:
          if "redemption temporarily unavailable" in error.message:
              raise  # Transient: retry with the token within its five-minute window
          try:
              # Fall back to the unchanged body, still with the token
              response = send("claude-opus-4-8", exact_body)
          except BadRequestError as retry_error:
              if "redemption temporarily unavailable" in retry_error.message:
                  raise  # Transient: retry with the token within its five-minute window
              # The token itself was rejected: forfeit it and retry without.
              response = send("claude-opus-4-8", request)

  print(json.dumps({"stop_reason": response.stop_reason, "model": response.model}))
  ```

  ```typescript TypeScript
  const client = new Anthropic();

  const request: Anthropic.Beta.MessageCreateParamsNonStreaming = {
    model: "claude-fable-5",
    max_tokens: 1024,
    messages: [{ role: "user", content: "Hello, Claude" }],
    betas: ["fallback-credit-2026-07-01"]
  };

  let response = await client.beta.messages.create(request);

  if (
    response.stop_reason === "refusal" &&
    response.stop_details?.type === "refusal" &&
    response.stop_details.fallback_credit_token
  ) {
    const { fallback_credit_token, fallback_has_prefill_claim } = response.stop_details;
    const fallbackModel = "claude-opus-4-8";

    const exactRetry: Anthropic.Beta.MessageCreateParamsNonStreaming = {
      ...request,
      model: fallbackModel,
      fallback_credit_token
    };

    // Richest shape first, degrading on each rejection: the continuation
    // shape (unless the claim is false), the unchanged body still carrying
    // the token, and finally forfeiting the token.
    let attempt = exactRetry;
    if (fallback_has_prefill_claim !== false) {
      const finalBlock = response.content.at(-1);
      const echoed: Anthropic.Beta.BetaContentBlockParam[] =
        finalBlock?.type === "text"
          ? [
              ...response.content.slice(0, -1),
              { ...finalBlock, text: finalBlock.text.trimEnd() }
            ]
          : response.content;
      attempt = {
        ...exactRetry,
        messages: [...request.messages, { role: "assistant", content: echoed }]
      };
    }

    try {
      response = await client.beta.messages.create(attempt);
    } catch (error) {
      // Degrade only on a shape-related 400. "redemption temporarily
      // unavailable" is transient: retry the same way within the token's
      // five-minute window instead.
      if (
        !(error instanceof Anthropic.BadRequestError) ||
        error.message.includes("redemption temporarily unavailable")
      ) {
        throw error;
      }
      try {
        response = await client.beta.messages.create(exactRetry);
      } catch (retryError) {
        if (
          !(retryError instanceof Anthropic.BadRequestError) ||
          retryError.message.includes("redemption temporarily unavailable")
        ) {
          throw retryError;
        }
        response = await client.beta.messages.create({ ...request, model: fallbackModel });
      }
    }
  }

  const { stop_reason, model } = response;
  console.log(JSON.stringify({ stop_reason, model }));
  ```

  ```csharp C#
  var client = new AnthropicClient();
  const string beta = "fallback-credit-2026-07-01";

  List<BetaMessageParam> requestMessages =
  [
      new() { Role = Role.User, Content = "Hello, Claude" },
  ];
  MessageCreateParams Request(string model) => new()
  {
      Model = model,
      MaxTokens = 1024,
      Messages = requestMessages,
      Betas = [beta],
  };
  var response = await client.Beta.Messages.Create(Request("claude-fable-5"));

  if (
      response.StopReason == BetaStopReason.Refusal
      && response.StopDetails is { FallbackCreditToken: string token } details
  )
  {
      var exactBody = Request("claude-opus-4-8") with { FallbackCreditToken = token };
      var attempt = exactBody;
      // Prefer the continuation shape unless the claim is false
      if (details.FallbackHasPrefillClaim is not false)
      {
          var echoed = JsonArray.Create(response.RawData["content"])!;
          if (
              echoed is [.., JsonObject lastBlock]
              && lastBlock["type"]?.GetValue<string>() is "text"
              && lastBlock["text"]?.GetValue<string>() is string text
          )
          {
              lastBlock["text"] = text.TrimEnd();
          }
          attempt = exactBody with
          {
              Messages =
              [
                  .. requestMessages,
                  new()
                  {
                      Role = Role.Assistant,
                      Content = new BetaMessageParamContent(
                          JsonSerializer.SerializeToElement(echoed)
                      ),
                  },
              ],
          };
      }
      // A transient "redemption temporarily unavailable" rejection propagates out of
      // each of the following catch filters: retry with the token within its five-minute window.
      try
      {
          response = await client.Beta.Messages.Create(attempt);
      }
      catch (AnthropicBadRequestException e)
          when (!e.Message.Contains("redemption temporarily unavailable"))

Cut at 300 lines.