DEV Community

Cover image for Claude Fable 5.2 migration for TypeScript backends
Sascha Mirow-Stanitzok
Sascha Mirow-Stanitzok

Posted on Originally published at sascha-ms.de

Claude Fable 5.2 migration for TypeScript backends

Claude Fable 5.2 migration starts with a surprise for many teams. The model has been available since November 9, 2026 – on the Claude API, on Amazon Bedrock, on Google Cloud, in Microsoft Foundry and in GitHub Copilot. But if you just swap the model ID, you will soon see a few 400 errors: the new model no longer accepts sampling parameters, forced tool calls or disabled thinking.

What interests me about releases like this is not the benchmark table. It is the question: what do I have to change in my code, and when is the more expensive model worth it at all? That is exactly what we will work through here – with a small TypeScript wrapper that handles refusals cleanly and a check script that finds old parameters before the API does.

In short

Claude Fable 5.2 is Anthropic’s strongest generally available model, but it costs twice as much as Opus 5.5 and needs a few code changes. The most important change for your backend: refusals come back as a normal response with stop_reason “refusal”. The biggest pitfall is old parameters such as temperature or tool_choice “any”, which now fail with a 400.

What you will learn in this article

  • When Fable 5.2 makes sense and when Opus 5.5 is enough
  • Which parameters cause a 400 error with Fable 5.2
  • How to detect refusals via stop_reason and fall back to another model
  • How to set effort and max_tokens sensibly
  • How to safeguard the switch with Vitest

What is new in Claude Fable 5.2?

On November 9, Anthropic introduced two models: Claude Fable 5.2 and Claude Mythos 5.2. Under the hood, they are the same model. The difference lies in the safeguards: Fable has strict classifiers for cybersecurity and biology and is therefore available to everyone. Mythos is only available to organizations that are approved through verification programs, for example for security research.

For developers, these key facts from the official model overview matter most:

Property Claude Fable 5.2
Model ID (Claude API) claude-fable-5-2
Model ID (Amazon Bedrock) anthropic.claude-fable-5-2
Context window / max output 1 million tokens / 128,000 tokens
Price per million tokens $8 input, $40 output (Batch: 50% cheaper)
Thinking adaptive, cannot be turned off
Default effort high
Knowledge cutoff June 2026
Data retention 30 days required, zero data retention only with explicit approval

According to Anthropic, Fable 5.2 solves more hard coding tasks than Opus 5.5 and is up to 30% faster than its predecessor Fable 5.1. On the FrontierSWE v2 benchmark, the vendor reports 69.9% versus 62.3% for Opus 5.5. These are vendor figures – for your code, only your own evaluation counts. In GitHub Copilot, the model is also available for Pro+, Max, Business and Enterprise, billed by usage at list price.

Fable 5.2 or Opus 5.5: which model when?

I find it remarkable how openly Anthropic itself puts it: for most coding and knowledge tasks, the vendor still recommends Opus 5.5 as the everyday model. Fable is meant for the really hard problems – long agent runs, large migrations, tasks where Opus fails even at high effort.

Model Price input / output Default effort Typical use
Claude Fable 5.2 $8 / $40 high Large refactorings, tricky debugging, long agent tasks
Claude Opus 5.5 $4 / $20 medium Code reviews, architecture questions, most agent steps
Claude Sonnet 5.5 $2 / $10 high Quick coding help, chat, tool calls
Claude Haiku 5.5 from $0.10 / from $0.50 medium Classification, extraction, high-volume subagents

A small example calculation makes the difference tangible. Suppose an agent run uses 200,000 input and 30,000 output tokens (thinking included). With Fable 5.2 that costs $1.60 plus $1.20, so $2.80. With Opus 5.5 it is $1.40 for the same number of tokens. In practice, the models of course use different amounts of tokens, so this is only an order of magnitude. If you want to cap such runs anyway, my article on the token budget for AI agents on Amazon Bedrock will help.

My rule of thumb is therefore: do not swap the model globally, create profiles instead. Everyday work runs on Opus 5.5, and only selected tasks get Fable. That is exactly how the code below is structured.

Claude Fable 5.2 migration: breaking changes at a glance

Anthropic’s migration guide is long, so here are the points that really show up in typical TypeScript backends. By the way, almost all of this already applies to Fable 5.1 and Opus 5 – but if you are coming from Opus 4.x or Sonnet, you will hit it for the first time.

In your code so far What happens with Fable 5.2 What to do instead
temperature, top_p, top_k 400 error Omit the parameters
tool_choice: { type: "any" } or "tool" 400 error Use auto, name the tool in the prompt, set strict: true
thinking: { type: "disabled" } or budget_tokens 400 error Remove the thinking configuration, control depth via effort
Prefill (last message from the assistant) 400 error Specify the format via the system prompt or structured outputs
content[0].text often returns a thinking block Filter blocks by type
small max_tokens stops with max_tokens after thinking, without text Set it generously or lower the effort
Zero data retention 400 error without approval Clarify 30-day data retention
Priority Tier not supported Plan for the standard tier

Preserved thinking: stop rewriting earlier messages

One change mainly affects home-grown agent loops. Thinking blocks are bound to the previous conversation history. If you change the system prompt, the tool list or older messages between two requests, the API rejects the old blocks with a signature error. With Fable 5.2, this check now applies to all accounts.

The fix is usually simple: send thinking blocks back unchanged, including empty ones, and only append to the history. If you need to shorten the context, do so deliberately and start a new conversation. As a transition, there is the setting thinking.block_binding.prefix_mismatch_behavior: "drop_block" with the beta header thinking-binding-controls-2026-08-01. Anthropic itself calls it a stopgap, because dropped blocks cost more output tokens.

Step by step: Claude Fable 5.2 migration for your TypeScript backend

Now it gets practical. We will build a small module with three parts: model profiles, a check function for old parameters and a wrapper that detects refusals and falls back. Everything is tested with Vitest and works without an AWS account.

Prerequisites

  • Node.js 22 or later (for --experimental-strip-types in the example)
  • a Claude API key for the real call – the tests run without one
  • basic TypeScript knowledge; you do not need prior experience with the Anthropic SDK

Step 1: Create the project

mkdir claude-fable-5-2-migration && cd claude-fable-5-2-migration
npm init -y
npm pkg set type=module
npm install @anthropic-ai/sdk@^1.7.0
npm install -D typescript@^5.9.3 vitest@^4.0.18 @types/node@^22
Enter fullscreen mode Exit fullscreen mode

The tsconfig.json stays simple. The key part is allowImportingTsExtensions, so Node can run the files directly:

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "noEmit": true,
    "allowImportingTsExtensions": true,
    "skipLibCheck": true,
    "types": ["node"]
  },
  "include": ["src", "test"]
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Model profiles instead of a hard-coded model ID

The profiles bundle model, effort and max_tokens in one place. I always set the effort explicitly on purpose: Fable 5.2 starts at high, Opus 5.5 at medium. If you do not know that, you will be surprised by cost and latency after switching models.

// src/models.ts
// Model profiles in one place: switching models means changing only this file.
export type Effort = 'low' | 'medium' | 'high' | 'xhigh' | 'max';

export interface ModelProfile {
  /** Claude API model ID (on Amazon Bedrock: anthropic.<id>) */
  id: string;
  /** Set explicitly because the defaults differ per model */
  effort: Effort;
  /** Thinking tokens count too – so be generous */
  maxTokens: number;
}

export const PROFILES = {
  // Everyday work: reviews, summaries, most agent steps
  daily: { id: 'claude-opus-5-5', effort: 'medium', maxTokens: 32_000 },
  // The hard problems: large migrations, long agent runs
  heavy: { id: 'claude-fable-5-2', effort: 'high', maxTokens: 64_000 },
  // High volume, little reasoning: classification, extraction
  bulk: { id: 'claude-haiku-5-5', effort: 'low', maxTokens: 8_000 },
} as const satisfies Record<string, ModelProfile>;

// Fallback models to use when Fable 5.2 declines a request
export const FALLBACK_MODELS = ['claude-opus-4-8'] as const;
Enter fullscreen mode Exit fullscreen mode

Why 64,000 tokens for Fable? Thinking tokens count against max_tokens. If the value is too small, the response ends with stop_reason: "max_tokens" after the model has only thought and not yet written any text. You still pay for it.

Step 3: Find old parameters automatically

Before you send your first request to Fable, take a look at your existing calls. The following function checks a request object and reports everything Fable 5.2 would reject. You can use it in tests, in a lint step or right before the call.

// src/migration-check.ts
// Checks request parameters before sending for anything Claude Fable 5.2 rejects with a 400.
export interface RequestLike {
  model: string;
  max_tokens: number;
  messages: Array<{ role: string }>;
  temperature?: number | null;
  top_p?: number | null;
  top_k?: number | null;
  tool_choice?: { type: string } | null;
  thinking?: { type: string } | null;
}

export interface Finding {
  level: 'error' | 'warning';
  message: string;
}

const STRICT_MODELS = /^(anthropic\.)?claude-(fable|mythos)-5-2/;

export function checkForFable(req: RequestLike): Finding[] {
  if (!STRICT_MODELS.test(req.model)) return [];
  const findings: Finding[] = [];

  for (const key of ['temperature', 'top_p', 'top_k'] as const) {
    if (req[key] != null) {
      findings.push({ level: 'error', message: `remove ${key} – omit sampling parameters` });
    }
  }

  const choice = req.tool_choice?.type;
  if (choice === 'any' || choice === 'tool') {
    findings.push({
      level: 'error',
      message: `tool_choice "${choice}" is rejected – use "auto" and name the tool in the prompt`,
    });
  }

  if (req.thinking && req.thinking.type !== 'adaptive') {
    findings.push({
      level: 'error',
      message: `remove thinking.type "${req.thinking.type}" – thinking is always adaptive`,
    });
  }

  if (req.messages.at(-1)?.role === 'assistant') {
    findings.push({ level: 'error', message: 'remove prefill (last message from the assistant)' });
  }

  if (req.max_tokens < 16_000) {
    findings.push({
      level: 'warning',
      message: `max_tokens ${req.max_tokens} is tight – thinking tokens count too`,
    });
  }

  return findings;
}
Enter fullscreen mode Exit fullscreen mode

Step 4: The wrapper with a fallback model

At the core is askClaude(). The function sends the prompt to the model from the profile, checks stop_reason and, on a refusal, tries the next model. We pass the client in as a parameter – which makes testing trivial later.

// src/ask-claude.ts
// Calls Claude, detects refusals (stop_reason "refusal") and falls back to another model.
import type Anthropic from '@anthropic-ai/sdk';
import { FALLBACK_MODELS, type ModelProfile } from './models.ts';

type StreamParams = Anthropic.MessageStreamParams;
type Message = Anthropic.Message;

// Only the one method we need – this makes the client easy to replace in tests.
// We always stream: with a large max_tokens the SDK refuses non-streaming calls.
export interface MessagesClient {
  messages: { stream(params: StreamParams): { finalMessage(): Promise<Message> } };
}

export interface RefusalRecord {
  model: string;
  category: string | null;
}

export interface AskResult {
  text: string;
  servedBy: string;
  refusals: RefusalRecord[];
  stopReason: Message['stop_reason'];
}

export interface AskOptions {
  profile: ModelProfile;
  system?: string;
  fallbackModels?: readonly string[];
  onRefusal?: (record: RefusalRecord) => void;
}

// Select text blocks by type – never content[0].text, a thinking block often comes first
export function extractText(message: Message): string {
  return message.content
    .filter((block): block is Anthropic.TextBlock => block.type === 'text')
    .map((block) => block.text)
    .join('');
}

export async function askClaude(
  client: MessagesClient,
  prompt: string,
  options: AskOptions,
): Promise<AskResult> {
  const { profile, system, onRefusal } = options;
  const models = [profile.id, ...(options.fallbackModels ?? FALLBACK_MODELS)];
  const refusals: RefusalRecord[] = [];

  for (const model of models) {
    const message = await client.messages
      .stream({
        model,
        max_tokens: profile.maxTokens,
        output_config: { effort: profile.effort },
        ...(system ? { system } : {}),
        messages: [{ role: 'user', content: prompt }],
      })
      .finalMessage();

    if (message.stop_reason !== 'refusal') {
      return { text: extractText(message), servedBy: message.model, refusals, stopReason: message.stop_reason };
    }

    const record = { model, category: message.stop_details?.category ?? null };
    refusals.push(record);
    onRefusal?.(record);

    // Another model will not help with "reasoning_extraction" – stop right away
    if (record.category === 'reasoning_extraction') break;
  }

  return { text: '', servedBy: 'none', refusals, stopReason: 'refusal' };
}
Enter fullscreen mode Exit fullscreen mode

Two details matter to me. First, the wrapper uses messages.stream() and waits for the result with finalMessage(). With a non-streaming messages.create() and 64,000 tokens, the SDK aborts immediately – pointing out that calls longer than ten minutes require streaming. If you run the example without streaming, you will see exactly that message. Second, the category reasoning_extraction ends the loop, because according to the documentation Anthropic does not retry these cases on the server side either.

Step 5: Tests with Vitest

The tests replace the client with a fake. That way we check the flow without network access and without cost:

// test/ask-claude.test.ts
import { describe, expect, it, vi } from 'vitest';
import type Anthropic from '@anthropic-ai/sdk';
import { askClaude, extractText, type MessagesClient } from '../src/ask-claude.ts';
import { PROFILES } from '../src/models.ts';

// Builds a minimal response in Messages API format
function reply(model: string, partial: Partial<Anthropic.Message>): Anthropic.Message {
  return {
    id: 'msg_test',
    type: 'message',
    role: 'assistant',
    model,
    content: [],
    stop_reason: 'end_turn',
    stop_sequence: null,
    stop_details: null,
    usage: { input_tokens: 10, output_tokens: 5 },
    ...partial,
  } as Anthropic.Message;
}

// Replaces messages.stream(): each answer is returned via finalMessage()
function fakeClient(...answers: Anthropic.Message[]) {
  const streamMock = vi.fn();
  for (const a of answers) streamMock.mockReturnValueOnce({ finalMessage: async () => a });
  return { client: { messages: { stream: streamMock } } as MessagesClient, streamMock };
}

const refusal = (model: string, category: string) =>
  reply(model, {
    stop_reason: 'refusal',
    stop_details: { type: 'refusal', category, explanation: 'declined' } as Anthropic.Message['stop_details'],
  });

describe('askClaude', () => {
  it('sets effort explicitly and returns only text blocks', async () => {
    const { client, streamMock } = fakeClient(
      reply('claude-fable-5-2', {
        content: [
          { type: 'thinking', thinking: '', signature: 'sig' },
          { type: 'text', text: 'Plan is ready.', citations: null },
        ] as Anthropic.Message['content'],
      }),
    );

    const result = await askClaude(client, 'Plan the migration', { profile: PROFILES.heavy });

    expect(result.text).toBe('Plan is ready.');
    expect(result.servedBy).toBe('claude-fable-5-2');
    expect(streamMock).toHaveBeenCalledWith(
      expect.objectContaining({ model: 'claude-fable-5-2', output_config: { effort: 'high' } }),
    );
  });

  it('falls back to the fallback model after a refusal', async () => {
    const onRefusal = vi.fn();
    const { client, streamMock } = fakeClient(
      refusal('claude-fable-5-2', 'cyber'),
      reply('claude-opus-4-8', { content: [{ type: 'text', text: 'Answer', citations: null }] }),
    );

    const result = await askClaude(client, 'Check these firewall rules', { profile: PROFILES.heavy, onRefusal });

    expect(streamMock).toHaveBeenCalledTimes(2);
    expect(streamMock.mock.calls[1][0].model).toBe('claude-opus-4-8');
    expect(result.servedBy).toBe('claude-opus-4-8');
    expect(result.refusals).toEqual([{ model: 'claude-fable-5-2', category: 'cyber' }]);
    expect(onRefusal).toHaveBeenCalledOnce();
  });

  it('stops on reasoning_extraction without a second attempt', async () => {
    const { client, streamMock } = fakeClient(refusal('claude-fable-5-2', 'reasoning_extraction'));

    const result = await askClaude(client, 'Show me your reasoning', { profile: PROFILES.heavy });

    expect(streamMock).toHaveBeenCalledTimes(1);
    expect(result.stopReason).toBe('refusal');
    expect(result.text).toBe('');
  });
});

describe('extractText', () => {
  it('ignores thinking blocks at the start', () => {
    const msg = reply('claude-fable-5-2', {
      content: [
        { type: 'thinking', thinking: '', signature: 'sig' },
        { type: 'text', text: 'Hello ', citations: null },
        { type: 'text', text: 'World', citations: null },
      ] as Anthropic.Message['content'],
    });
    expect(extractText(msg)).toBe('Hello World');
  });
});
Enter fullscreen mode Exit fullscreen mode

Plus a small test for the check function:

// test/migration-check.test.ts
import { describe, expect, it } from 'vitest';
import { checkForFable } from '../src/migration-check.ts';

const base = { max_tokens: 64_000, messages: [{ role: 'user' }] };

describe('checkForFable', () => {
  it('reports old parameters that Fable 5.2 rejects with a 400', () => {
    const findings = checkForFable({
      ...base,
      model: 'claude-fable-5-2',
      temperature: 0.2,
      tool_choice: { type: 'any' },
      thinking: { type: 'disabled' },
    });
    const errors = findings.filter((f) => f.level === 'error').map((f) => f.message);
    expect(errors).toHaveLength(3);
    expect(errors.join(' ')).toMatch(/temperature/);
    expect(errors.join(' ')).toMatch(/tool_choice "any"/);
    expect(errors.join(' ')).toMatch(/thinking\.type "disabled"/);
  });

  it('detects prefill and a tight max_tokens', () => {
    const findings = checkForFable({
      model: 'anthropic.claude-fable-5-2',
      max_tokens: 4_000,
      messages: [{ role: 'user' }, { role: 'assistant' }],
    });
    expect(findings.map((f) => f.level)).toEqual(['error', 'warning']);
  });

  it('leaves other models alone', () => {
    expect(checkForFable({ ...base, model: 'claude-opus-5-5', temperature: 0.2 })).toEqual([]);
  });

  it('accepts a clean request', () => {
    expect(checkForFable({ ...base, model: 'claude-fable-5-2', tool_choice: { type: 'auto' } })).toEqual([]);
  });
});
Enter fullscreen mode Exit fullscreen mode
npx tsc --noEmit
npx vitest run
Enter fullscreen mode Exit fullscreen mode

Expected result: the type check passes without output, and Vitest reports two test files with eight passing tests in total.

Step 6: A real call

Finally, an example with a fictional task: a migration plan for the backend of a bike rental service.

// src/example.ts
// Example: migration plan for a fictional bike rental backend with Claude Fable 5.2
import Anthropic from '@anthropic-ai/sdk';
import { askClaude } from './ask-claude.ts';
import { PROFILES } from './models.ts';

const client = new Anthropic(); // reads ANTHROPIC_API_KEY from the environment

const result = await askClaude(
  client,
  'Plan the migration of our bike rental backend from Express 4 to Fastify 5. ' +
    'Return a numbered list of risks and test steps.',
  {
    profile: PROFILES.heavy,
    system: 'You are an experienced Node.js architect. Answer briefly.',
    onRefusal: (r) => console.warn(`Declined by ${r.model} (category: ${r.category ?? 'none'})`),
  },
);

console.log(`Served by: ${result.servedBy}`);
console.log(result.text);
Enter fullscreen mode Exit fullscreen mode
export ANTHROPIC_API_KEY=your-key
node --experimental-strip-types src/example.ts
Enter fullscreen mode Exit fullscreen mode

You first see Served by: claude-fable-5-2 and the plan below it. With an invalid key you get an AuthenticationError with status 401 – which is also a good test that everything is wired up.

Detecting refusals: stop_reason refusal instead of an error

The most important point for operations: when Fable 5.2 declines a request, you do not get an error, but HTTP 200. The content is empty, stop_reason is "refusal", and stop_details.category names the reason. Possible categories are cyber, bio, frontier_llm, reasoning_extraction, general_harms or null.

🎬 Flow: request to Claude Fable 5.2 with a fallback model – this animated diagram only runs on my site: watch it on sascha-ms.de

Three things follow from this. Your error rate in monitoring does not see these cases – so log refusals as a separate metric. If the refusal arrives mid-stream, the text streamed so far is incomplete and should be discarded. And depending on the category, the request is billed anyway: for bio, frontier_llm and reasoning_extraction you pay even without output according to the documentation, for cyber and general_harms you do not.

This is relevant for security teams: according to Anthropic, Fable 5.2 blocks most cybersecurity tasks, such as penetration testing or writing exploits. If you are building a tool that analyzes code for vulnerabilities, try it with real prompts first.

Server-side fallback as an alternative

On the Claude API, Anthropic can also handle the fallback for you. For that, there is the beta parameter fallbacks. For Fable 5.2, the permitted targets are claude-opus-4-8 and claude-opus-5:

// Server-side fallback (beta)
const message = await client.beta.messages
  .stream({
    model: 'claude-fable-5-2',
    max_tokens: 64_000,
    output_config: { effort: 'high' },
    messages: [{ role: 'user', content: 'Analyze this stack trace …' }],
    fallbacks: 'default',
    betas: ['server-side-fallback-2026-07-01'],
  })
  .finalMessage();

console.log(`Served by: ${message.model}`);
Enter fullscreen mode Exit fullscreen mode

That is convenient, but there is a catch: according to the documentation, the parameter only works on the Claude API and on Google Cloud when operated by Anthropic, and not in the Batch API. If your backend runs on Amazon Bedrock, you need the fallback in your own code – just like in askClaude(). I described how to handle model fallbacks centrally on the AWS side in Bedrock model fallback with AgentCore Gateway.

Replacing tool_choice without losing the tool call

Many extraction pipelines have so far forced a specific call with tool_choice: { type: "tool" }. That no longer works with Fable 5.2. Anthropic recommends: keep auto, name the tool clearly in the prompt and set strict: true so the inputs match the schema.

// Instead of tool_choice { type: 'tool' }: keep auto, name the tool in the prompt, set strict
const message = await client.messages
  .stream({
    model: 'claude-fable-5-2',
    max_tokens: 32_000,
    tools: [
      {
        name: 'save_rental',
        description: 'Saves a rental at the bike rental shop. Always call it when a rental is detected.',
        strict: true,
        input_schema: {
          type: 'object',
          properties: {
            bikeId: { type: 'string' },
            hours: { type: 'integer' },
          },
          required: ['bikeId', 'hours'],
          additionalProperties: false,
        },
      },
    ],
    tool_choice: { type: 'auto' },
    system: 'If the text describes a rental, call save_rental before you answer.',
    messages: [{ role: 'user', content: 'Bike B-17 for three hours, please.' }],
  })
  .finalMessage();
Enter fullscreen mode Exit fullscreen mode

Afterwards, check in your code whether a tool_use block actually arrived. There is no longer a guarantee like with a forced tool_choice, so this check belongs in every pipeline that depends on it.

Common mistakes when switching

  • Forgetting effort: Fable starts at high. If you come from Opus 5.5 with medium, you pay for more reasoning without noticing.
  • Prompts like “answer directly without deliberating”: From medium upwards, such lines no longer help. Anthropic recommends removing them and lowering the effort instead.
  • xhigh and max without a cost cap: At these levels, the model may start subagents to review its own work. That can get expensive.
  • Overlooking the separate rate limit: Fable 5.2 has its own limit, separate from Fable 5.1. So run your load tests again.
  • Not logging refusals: Without a dedicated metric, you only notice missing answers through user complaints.

One more data protection note: Fable 5.2 only runs if Anthropic may keep your data for 30 days for safety monitoring. If you have agreed on zero data retention with Anthropic, you will get a 400 error without additional approval. Clarify this with your data protection team before you send production data.

My take

I see Fable 5.2 as a tool, not a new default model. The benchmarks are impressive, but the price is twice that of Opus 5.5, and Anthropic itself says Opus remains the right choice for most tasks. I find that honest and quite helpful.

More important to me than the model itself is the direction of the API: fewer knobs, more responsibility with the model. No temperature, no forced tool call, no way to turn off thinking. That makes the code simpler in places, but it takes away control that older pipelines rely on. And refusals as normal responses are a detail that is easily overlooked in operations. If you build in a model profile and a clean fallback today, you can handle the next model switch in a single file. If you have hard-wired the model everywhere, you will be searching every service again.

FAQ

How much does Claude Fable 5.2 cost?

$8 per million input tokens and $40 per million output tokens. The Batch API gives a 50% discount, and cache reads cost $0.20 per million tokens.

Is Claude Fable 5.2 available on Amazon Bedrock?

Yes, under the model ID anthropic.claude-fable-5-2. According to the documentation, however, server-side fallback via the fallbacks parameter is not available there, so you need it in your own code.

What is the difference between Claude Fable and Claude Mythos?

It is the same model. Fable has stricter safeguards for cybersecurity and biology and is generally available, while Mythos is only available to verified organizations.

Can I turn off thinking in Fable 5.2?

No. Adaptive thinking is always on. You control how thoroughly the model reasons via output_config.effort with the levels low, medium, high, xhigh and max.

Should I switch all calls to Fable 5.2?

Probably not. Anthropic still recommends Opus 5.5 for most tasks. Fable pays off for hard, long tasks where Opus demonstrably falls short.

Your next step

Run the checkForFable() function over the request objects of your existing services once, before you change the model ID anywhere. Then you will know how big the switch really is. After that, pick a single task where Opus 5.5 reaches its limits today and compare both models there with real data. If you are still on the older SDK, first take a look at my article on the migration to Claude API 2026-10-21.

I am curious: which task in your daily work would you try Fable 5.2 on first – and where do you deliberately stay with a cheaper model?

All code from this article, ready to run: smirowstanitzok/sascha-ms-examples

Tested with @anthropic-ai/sdk 1.7.0, TypeScript 5.9.3, Vitest 4.0.18 and Node.js 22: type check and all tests pass. The real API call was only tested up to authentication.

Sources

This post was created with AI assistance.


👉 Next step: New here? My start page collects the best articles, tools and learning paths in one place.

📬 Stay updated: New articles on serverless, AI and leading tech teams via RSS, browser push or the weekly newsletter (in German).

Originally published on sascha-ms.de. More articles on serverless, AI in everyday development and leading tech teams: sascha-ms.de/en

Top comments (0)