Fable 5 is the most capable model most teams can actually call, and three of its defaults will quietly break an integration written for an ordinary chat model. None of them throw an exception. That's the problem. Here's each one and the code that survives it.

1. A refusal comes back as a 200 OK#

Start here, because it's the one that ships a bug to users without a single log line.

When Fable's safety classifier declines a request, the API does not return an error. It returns HTTP 200 with stop_reason: "refusal" and no usable content. Your transport layer sees success. Your try/catch stays quiet. Your app renders an empty answer.

This is the same failure shape we flagged for tool calls, where the most dangerous failure returns 200 OK: the status code says "fine," the payload says otherwise. The fix is to stop trusting the status and start branching on stop_reason:

const res = await client.messages.create({
  model: "claude-fable-5",
  max_tokens: 8000,
  messages,
});

switch (res.stop_reason) {
  case "refusal":
    // Safety classifier declined. You are NOT billed for output here,
    // so retrying on another model is close to free.
    return await client.messages.create({
      model: "claude-opus-4-8",   // fallback
      max_tokens: 8000,
      messages,
    });
  case "max_tokens":
    // Truncated mid-generation — handled in section 3.
    return await continueOrFallback(res, messages);
  default:
    return res;   // "end_turn" / "stop_sequence" — the good path
}

Two details make this cheap to run. A pre-output refusal isn't billed for output, so the fallback attempt costs you almost nothing. And because refusals are a normal branch — not an outage — you want them on a per-request path, not a global retry-with-backoff wrapper that would happily re-refuse.

2. Adaptive thinking is always on — you budget it, you don't kill it#

The reflex for cost control on a reasoning model is to turn thinking off for easy calls. On Fable 5 you can't. Adaptive thinking is the only mode; there is no thinking: { type: "disabled" }. The single lever is the effort parameter:

// Routine call escalated to Fable for capability, not for deep reasoning:
await client.messages.create({
  model: "claude-fable-5",
  effort: "low",        // fewer thinking tokens -> lower cost + latency
  max_tokens: 4000,
  messages,
});

// The hard task you actually reached for Fable to solve:
await client.messages.create({
  model: "claude-fable-5",
  effort: "high",       // spend the thinking budget where it pays
  max_tokens: 16000,
  messages,
});

Lower effort spends fewer thinking tokens, which cuts both the bill and the wall-clock. Set it per route, not globally: high on the escalated hard task, low on anything routine that only landed on Fable for its ceiling. And don't plan to display the model's reasoning — the raw chain-of-thought is never returned. You get a summarized thinking block, or by default an omitted empty one.

3. The bill is past 2× — here's how to claw it back#

Fable 5 is $10 per 1M input and $50 per 1M output, double Opus 4.8's $5 / $25. Treat that as the optimistic case, because two multipliers stack on top:

Fable runs slower, and its newer tokenizer emits ~30% more tokens for the same text. A task bills more tokens and pays a higher rate per token — so effective cost-per-task is comfortably above 2×.

Three moves recover most of the gap:

4. Don't forget the 128K ceiling#

Fable's max output is 128K tokens per request — generous, but not infinite. A big refactor or a long structured document can hit it, and when it does the response ends with stop_reason: "max_tokens" and truncated content: valid tokens, incomplete answer, still a 200. Detect it the same way you detect a refusal — on stop_reason, never on the status code — then continue the generation or fall back.

The pattern: wrap it, don't default to it#

Every catch above points the same direction. Don't make Fable your default client. Put it behind a small router:

async function complete(task, messages) {
  if (!task.isHard) {
    return call("claude-opus-4-8", messages, { effort: "medium" });
  }
  const res = await call("claude-fable-5", messages, { effort: "high" });
  if (res.stop_reason === "refusal" || res.stop_reason === "max_tokens") {
    return call("claude-opus-4-8", messages, { effort: "high" }); // fallback
  }
  return res;
}

That one boundary contains all three surprises at once: the 2× premium only touches tasks that earned it, the refusal-as-200 always has somewhere to land, and the 128K truncation can't silently ship half an answer. Fable 5 is worth reaching for — when a task is genuinely hard enough to justify the ceiling. Everywhere else, Opus 4.8 is the cheaper, faster, and Anthropic-recommended default.