claude-fable-5
Claude Fable 5 was Anthropic's most capable widely released model, built for demanding reasoning and long-horizon agentic work. Two things set it apart from everything else in this catalogue. Thinking is permanently on — there is no parameter that disables it, only an effort dial that sets how deep it goes. And it ships with safety classifiers that can decline a request, returning a successful response carrying a refusal rather than an error, which means any integration must inspect why a response ended rather than assuming a 200 means an answer. It holds a million-token context with a 128,000-token output ceiling, and reads text and images.

Claude Fable 5
Anthropic's most capable widely released model at launch, aimed at demanding reasoning and long-horizon agentic work. Released June 2026.
Refusals Arrive as Successful Responses
The single most consequential fact for anyone integrating this model, and the easiest to miss.
Fable 5 carries safety classifiers capable of declining a request. When one fires, the Messages API
returns HTTP 200 with stop_reason: "refusal". No exception is raised. No error code comes back.
The response also names which classifier declined.
So a handler written like this quietly ships a refusal to your user as though it were an answer:
if response.ok:
return response.content # a refusal reaches production looking like a resultCheck why the response ended, not merely that it arrived:
if response.stop_reason == "refusal":
# Route to review, retry on a different model, or tell the user plainly.
handle_declined(response)
else:
deliver(response.content)This behaviour is specific to Fable within its tier — the Project Glasswing variant of the same underlying model carries no such classifiers.
Anthropic tunes these conservatively, which means harmless requests are sometimes caught. Their published figure puts the trigger rate at under five percent of sessions on average. Plan a path for that minority rather than discovering it in a support ticket.
A Request May Be Answered by a Different Model
Related, and equally worth knowing before you build.
Anthropic operates a safeguards routing mechanism: queries on certain topics sent to Fable can be served by Claude Opus 5 instead. The request succeeds. The answer is real. It simply came from somewhere else.
Two implications for anything you build on top.
Do not assume the responding model matches the requested one. Read the model field on the response if your logic, logging, or billing depends on which one answered.
Evaluations can drift without explanation. A benchmark suite run against Fable that includes topics the routing catches is measuring a mixture, not a model. Check the responses if a result looks inconsistent with earlier runs.
Thinking Cannot Be Switched Off
Adaptive thinking is always on. There is no disable flag — thinking: {"type": "disabled"} is
not supported on this model, unlike Opus-tier models where it is accepted below a certain effort.
The effort parameter is the only control, and it sets depth rather than presence.
Effort defaults to high. Anthropic's guidance is to start there and adjust from your own
evaluation results rather than from intuition.
Because the model always reasons, max_tokens must cover thinking plus answer. A ceiling sized
for the answer alone truncates the model mid-thought and returns something unusable rather than
something shorter.
Specifications
| Model ID | anthropic/claude-fable-5 |
| Context window | 1,000,000 tokens — default |
| Max output | 128,000 tokens |
| Thinking | Adaptive, always on |
| Default effort | high |
| Input → output | Text and images → text |
| Reliable knowledge cutoff | January 2026 |
| Released | 9 June 2026 |
| Status | Active (legacy) |
The full million-token window carries no surcharge and needs no beta header.
Anthropic publishes no parameter counts, architectural details, or weights for Claude models.
Capabilities
| Capability | Value |
|---|---|
input_types | text, image |
output_types | text |
audio_input | Not supported |
video_input | Not supported |
context_window | 1000000 |
max_output_tokens | 128000 |
reasoning | Adaptive — permanently enabled |
thinking_disable | Not supported |
safety_classifiers | Present — can return stop_reason: "refusal" |
streaming | Supported |
tool_calling | Supported |
structured_output | Supported |
requires_prompt | Yes — text prompt required, image optional |
Where It Sits Now
Anthropic marks this model Active (legacy) and has published a newer release in the same tier,
claude-fable-5-1, carrying a knowledge cutoff five months more recent.
That does not make this model a wrong choice. It makes the choice deliberate: pick it when something downstream was tuned against it, when you are reproducing earlier results, or when a migration has a cost you are not ready to pay.
Three changes are breaking if you move to the newer release, so a migration is more than a model string:
- Forced tool use returns an error — only
autoandnoneare accepted fortool_choice - Earlier models cannot read its thinking blocks
- Editing earlier turns invalidates thinking blocks
Anthropic's own routing advice is worth repeating: for most workloads they suggest starting with Claude Opus 5, and reaching for this tier when evaluations at higher effort on Opus still fall short.
Using Claude Fable 5 on DEVUP AI
Base URL: https://api.devupai.com/v1 · Model ID: anthropic/claude-fable-5
First request — cURL
curl https://api.devupai.com/v1/chat/completions \
-H "Authorization: Bearer $DEVUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-fable-5",
"messages": [
{
"role": "user",
"content": "Our reconciliation job double-counts refunds issued in the same minute as the original charge, but only under load. Rank the mechanisms that could produce that, and say which one the timing detail points to."
}
],
"max_tokens": 32768
}'Handling refusals properly — Python
The integration pattern this model requires.
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["DEVUP_API_KEY"],
base_url="https://api.devupai.com/v1",
)
def ask(prompt: str, *, max_tokens: int = 32768):
"""Send a request and distinguish a real answer from a declined one."""
reply = client.chat.completions.create(
model="anthropic/claude-fable-5",
messages=[{"role": "user", "content": prompt}],
max_tokens=max_tokens,
)
choice = reply.choices[0]
# A decline arrives as a successful response, not an exception.
if getattr(choice, "finish_reason", None) == "refusal":
return {"declined": True, "content": None}
# The answering model is not guaranteed to be the one requested.
if reply.model and reply.model != "anthropic/claude-fable-5":
logger.info("served by %s", reply.model)
return {"declined": False, "content": choice.message.content}Both checks earn their place. The first stops a refusal from reaching a user dressed as an answer. The second tells you when routing sent the request elsewhere, which matters for logs, evaluations, and anything that reasons about which model produced what.
Sizing the output budget — Python
reply = client.chat.completions.create(
model="anthropic/claude-fable-5",
messages=[{"role": "user", "content": hard_question}],
max_tokens=64000, # reasoning and answer share this
)
usage = reply.usage
print(f"in {usage.prompt_tokens} · out {usage.completion_tokens}")Thinking runs on every request and consumes output tokens. Watch the completion count on a few real requests before settling on a ceiling — a budget that worked on a model where reasoning was optional will not transfer.
A long-horizon agent — Python
The workload this tier exists for.
import json
TOOLS = [
{
"type": "function",
"function": {
"name": "query_ledger",
"description": "Run a read-only query against the accounting ledger.",
"parameters": {
"type": "object",
"properties": {"sql": {"type": "string"}},
"required": ["sql"],
},
},
},
{
"type": "function",
"function": {
"name": "fetch_statement",
"description": "Retrieve a bank statement for a given month.",
"parameters": {
"type": "object",
"properties": {"month": {"type": "string"}},
"required": ["month"],
},
},
},
]
def query_ledger(sql: str) -> dict:
"""Replace with your real, read-only data layer."""
raise NotImplementedError
def fetch_statement(month: str) -> dict:
"""Replace with your real statement source."""
raise NotImplementedError
HANDLERS = {"query_ledger": query_ledger, "fetch_statement": fetch_statement}
thread = [
{
"role": "system",
"content": (
"You are reconciling accounts. Cite the query or statement behind every figure you "
"report. Where two sources disagree, show both and explain the discrepancy rather "
"than picking one."
),
},
{"role": "user", "content": "Reconcile January against the bank statement and explain every variance above 1000 DZD."},
]
CEILING = 30
for step in range(CEILING):
reply = client.chat.completions.create(
model="anthropic/claude-fable-5",
messages=thread,
tools=TOOLS,
max_tokens=32768,
)
choice = reply.choices[0]
if getattr(choice, "finish_reason", None) == "refusal":
print("Request declined by a safety classifier.")
break
message = choice.message
thread.append(message)
if not message.tool_calls:
print(message.content)
break
for call in message.tool_calls:
handler = HANDLERS.get(call.function.name)
if handler is None:
result = {"error": "unknown tool", "name": call.function.name}
else:
try:
result = handler(**json.loads(call.function.arguments or "{}"))
except Exception as exc:
# Describe the failure. The model works with it; an exception ends the session.
result = {"error": type(exc).__name__, "detail": str(exc)}
thread.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})
else:
print(f"Reached the {CEILING}-step ceiling without concluding.")The refusal check sits inside the loop deliberately. A classifier can fire on any turn, not only the first, and a loop that ignores it will keep iterating on a response that contains no tool call and no useful content.
CEILING is what ends a run that does not end itself.
Reading documents and charts — Python
import base64
with open("annual_report_page.png", "rb") as handle:
encoded = base64.b64encode(handle.read()).decode("utf-8")
reply = client.chat.completions.create(
model="anthropic/claude-fable-5",
messages=[
{
"role": "user",
"content": [
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{encoded}"}},
{
"type": "text",
"text": (
"Report every figure on this page with its label and unit. Separate values "
"printed in the document from values you inferred by reading a chart axis, "
"and mark anything illegible as such."
),
},
],
}
],
max_tokens=16384,
)Separating printed figures from axis-read estimates is a small ask with a large payoff. One category belongs in a spreadsheet unchecked; the other does not, and they look identical once transcribed.
Node.js — DEVUP AI SDK
npm install devupaiimport DevupAI from "devupai";
const client = new DevupAI({
apiKey: process.env.DEVUP_API_KEY,
});
const reply = await client.chat.completions.create({
model: "anthropic/claude-fable-5",
messages: [
{
role: "system",
content:
"Assess this architecture against a two-engineer team with no platform specialist. " +
"Say what you would rule out and why before saying what you would choose.",
},
{ role: "user", content: proposal },
],
max_tokens: 32768,
});
const choice = reply.choices[0];
if (choice.finish_reason === "refusal") {
console.warn("Declined by a safety classifier.");
} else {
console.log(choice.message?.content);
}Streaming
stream = client.chat.completions.create(
model="anthropic/claude-fable-5",
messages=[{"role": "user", "content": "Design an idempotency scheme for a payment callback that may arrive twice."}],
max_tokens=32768,
stream=True,
)
for chunk in stream:
if not chunk.choices:
if getattr(chunk, "usage", None):
print(f"\n\nin {chunk.usage.prompt_tokens} · out {chunk.usage.completion_tokens}")
continue
piece = chunk.choices[0].delta
if getattr(piece, "content", None):
print(piece.content, end="", flush=True)The model reasons before anything becomes visible, and at high effort that gap is real. Raise
client timeouts before deploying — a value tuned for a non-reasoning model expires while this one is
still working, and the reasoning it performed is already spent.
Prompting Notes
Ask for the argument alongside the conclusion. At this tier the reasoning is frequently the more useful half, and it is what makes a conclusion something you can disagree with intelligently.
Supply your constraints up front — team size, timeline, existing stack, what is already ruled out. A deep analysis that ignores your real limits is a well-made answer to somebody else's question.
Send one substantial question rather than several small ones. Splitting a problem across requests discards the cross-cutting reasoning that justifies reaching for this tier.
Iterate on a faster model, then run the final question here. A model that thinks at length is not a model you develop prompts against.
Limitations
A refusal is a successful response. Inspect stop_reason on every call, or a declined request
reaches your users looking like an answer.
The responding model may differ from the requested one. Safeguards routing can serve a Fable request from Opus 5 — infrequently, but not never.
Thinking cannot be disabled. There is no fast path; the effort dial changes depth only.
Reasoning shares the output ceiling. Size max_tokens for both, or requests truncate mid-thought.
Knowledge ends January 2026. Later facts require retrieval or a search tool, and the newer release in this tier reaches five months further.
Text and images only. No audio, no video, no image generation.
Marked legacy by Anthropic, with a current release available in the same tier. Migration there is not a model-string swap: three documented behaviours change.
Internals are undisclosed — no parameter count, no architecture, no weights.
A well-argued answer is not a verified one. Extended reasoning improves the argument, not the premises. Check anything you intend to act on.