XT.PT The API → This story
Filed

Updated 13:57
Reporting
Prelo
Verified by Roger Morais
3 min · 511 words
Analysis The API

A refusal is a 200: the Claude stop_reason that breaks lazy clients

Fable 5 and Opus 5 return classifier refusals as successful responses with empty content. The response shape, the billing, and the one-parameter fallback.

Filed01 Aug 2026, 13:15 UTC Length3 min · 511 words ReportingPrelo
Code network error

The HTTP status code of a declined Claude request is 200. That single fact breaks a pattern sitting in production code everywhere: check the status, then read response.content[0].

The shape

Claude Fable 5 and Claude Opus 5 ship safety classifiers that can decline a request, and Anthropic's documentation is precise about what comes back: "a normal response, not an error," shaped like this —

{
  "content": [],
  "stop_reason": "refusal",
  "stop_details": {
    "type": "refusal",
    "category": "cyber"
  }
}

content is an empty array. A client that assumes at least one content block — response.content[0].text, the first line of most quickstart code ever written — throws an index error precisely when the response is a refusal. The failure is not hypothetical edge-case handling; it is the difference between showing a user "this request was declined" and showing them a stack trace.

Streaming makes it worse: per the same page, "a refusal can arrive before any output, or mid-stream after partial output. In either case, treat any partial output as incomplete and discard it." So a streaming client must be prepared to throw away text it has already rendered.

What stop_details tells you

stop_details is null for every stop reason except refusal, and when present its category field names the policy area the classifier matched: the documented values are "cyber", "bio", "frontier_llm" and "general_harms", each with the caveat that benign work can also trigger it. Both fields can also be null when the refusal maps to no named category — the documentation calls that "a normal, permanent value, not a placeholder," so do not poll it hoping for enrichment.

The billing is fair, with one asterisk

A refusal that arrives before any output is not billed: "content is empty, and token counts appear in usage but are not charged." The asterisk: "the request still counts against your rate limits," and a mid-stream refusal bills the input plus whatever output already streamed. Refusals are cheap, not free.

The fix, in two tiers

Tier one is client-side and unconditional: branch on stop_reason before touching content, on every response, streaming or not. The full list of values worth handling is on Anthropic's stop-reasons pagerefusal is one of seven, and max_tokens and model_context_window_exceeded also produce responses your happy path did not plan for.

Tier two is one parameter. In beta on the Claude API, setting "fallbacks": "default" (with the server-side-fallback-2026-07-01 beta header) makes the API retry a declined request on the model Anthropic recommends for that refusal category — one round trip, one response, and the response names the model that actually answered. The documented limits: it is not supported on the Batch API, and not available on Amazon Bedrock, Google Cloud or Microsoft Foundry, where Anthropic points to client-side SDK middleware instead.

Neither tier is optional if you build on Fable 5 or Opus 5. The refusal response is a documented, first-class outcome of these models — the only question is whether your client treats it as one.

Primary sources: Anthropic's Refusals and fallback and Handling stop reasons documentation, read 2026-07-30.

Corrections and source documents: contact the desk
Read next →
Read next
The API · 3 min

Anthropic halves the prompt-cache minimum: 512 tokens on Opus 5 and Fable 5

Procurement · 3 min

Fable 5 will not run without 30-day data retention