AnthropicClaude APIAI Integration

Migrating Your Claude Integration to a New Model Without Breaking Production

Swapping the model ID string is where a Claude upgrade starts, not where it ends. Here are the breaking changes that will 400 your requests — and the exact edits to ship the migration safely.

Deepak Kaushal··11 min read

Every few months Anthropic ships a stronger model, and the temptation is to swap the ID string and move on. On the current generation that will often 400 your production traffic. Adaptive thinking replaced fixed thinking budgets, sampling parameters were removed, and assistant prefills are no longer allowed. This is the checklist I run through to migrate a Claude integration without an incident.

Step 1: Confirm the target model ID

Use the exact current IDs — claude-opus-4-8, claude-sonnet-5, claude-haiku-4-5 — and never append a date suffix to an alias. Older IDs still work while they are active, but retired models return a 404: Claude Sonnet 3.7 and Haiku 3.5 were retired in February 2026, and Opus 3 in January 2026. Find every place your codebase references a model string before you start, including config files, tests, and fallback constants.

Step 2: Replace budget_tokens with adaptive thinking

Fixed thinking budgets — thinking: { type: 'enabled', budget_tokens: N } — return a 400 on Claude Opus 4.7 and later, Claude Sonnet 5, and Claude Fable 5. Switch to adaptive thinking and control depth with the effort parameter. Delete the budget_tokens plumbing entirely; there is no drop-in numeric equivalent.

// Before — errors on Opus 4.7/4.8 and Sonnet 5
const res = await client.messages.create({
  model: 'claude-sonnet-4-5',
  max_tokens: 16000,
  thinking: { type: 'enabled', budget_tokens: 8000 },
  messages,
});

// After — adaptive thinking + effort
const res = await client.messages.create({
  model: 'claude-sonnet-5',
  max_tokens: 16000,
  thinking: { type: 'adaptive' },
  output_config: { effort: 'high' },
  messages,
});

Step 3: Remove sampling parameters

temperature, top_p, and top_k are no longer accepted on Claude Opus 4.7 and later or on Claude Sonnet 5 — passing any of them returns a 400. If you relied on temperature for determinism, drop to a lower effort level and a tighter prompt; if you relied on it for creative variance, ask for that variance explicitly in the prompt instead.

// Before — 400 on current models
await client.messages.create({ model, temperature: 0.7, top_p: 0.9, /* ... */ });

// After — omit them; steer behaviour through the prompt
await client.messages.create({ model, /* ... */ });

Step 4: Replace assistant prefills

Prefilling the final assistant turn — a common trick to force JSON or skip a preamble — now returns a 400. The clean replacement for forcing structured output is the structured-outputs feature: pass a json_schema through output_config.format and Claude constrains its response to match.

// Force JSON without a prefilled assistant turn
const res = await client.messages.create({
  model: 'claude-sonnet-5',
  max_tokens: 1024,
  output_config: {
    format: {
      type: 'json_schema',
      schema: {
        type: 'object',
        properties: {
          name: { type: 'string' },
          email: { type: 'string' },
        },
        required: ['name', 'email'],
        additionalProperties: false,
      },
    },
  },
  messages: [{ role: 'user', content: 'Extract the name and email.' }],
});

Step 5: Re-baseline your token budgets

Claude Sonnet 5 uses a new tokenizer that produces roughly 30% more tokens for the same text than the previous Sonnet generation. Per-token pricing is unchanged, but everything measured in tokens shifts: your context usage, your max_tokens headroom, and your cost dashboards. Do not reuse counts measured against an older model or apply a flat multiplier — re-measure with count_tokens against the model you are shipping.

// Re-baseline token counts against the new model
const { input_tokens } = await client.messages.countTokens({
  model: 'claude-sonnet-5',
  messages,
});

// Verify the rollout is actually hitting the new model
const res = await client.messages.create({
  model: 'claude-sonnet-5',
  max_tokens: 64,
  messages,
});
if (!res.model.startsWith('claude-sonnet-5')) {
  throw new Error(`Unexpected model: ${res.model}`);
}

Step 6: Decide what to show from thinking

On the current models, thinking display defaults to 'omitted', so thinking blocks arrive with empty text. If your product streams reasoning to users, that default reads as a long pause before any output appears. Set thinking.display to 'summarized' to restore a readable summary — display only controls visibility, so thinking still happens and is billed the same either way.

Step 7: Test one request before rollout

Do not flip the whole fleet at once. Send a single request against the new model, inspect the response — confirm response.model, check stop_reason, and review token usage — then roll out gradually. Changing the model string also invalidates any existing prompt cache, so the first request on the new model pays a cold cache write; that is expected, not a bug.

The migration checklist

  • Update every model ID string, including config, tests, and fallback constants.
  • Replace thinking budget_tokens with thinking: { type: 'adaptive' } plus output_config.effort.
  • Remove temperature, top_p, and top_k — steer behaviour through the prompt instead.
  • Replace assistant-turn prefills with output_config.format for structured output.
  • Stream any request with max_tokens above roughly 16,000 to avoid SDK HTTP timeouts.
  • Re-baseline token counts and cost with count_tokens on the target model.
  • Set thinking.display to 'summarized' if you surface reasoning to users.
  • Test one request, verify response.model, then roll out gradually.

Further Reading

Frequently Asked Questions

Can I just change the model ID string to upgrade Claude?

No. On the current models, several request parameters that older models accepted now return a 400 error — fixed thinking budgets, sampling parameters, and assistant-turn prefills. A safe migration means changing the model string and applying the corresponding parameter edits together.

Why does my request 400 after upgrading to a newer Claude model?

The most common causes are a leftover thinking budget_tokens field or a temperature/top_p/top_k sampling parameter. Both were removed on Claude Opus 4.7 and later, Claude Sonnet 5, and Claude Fable 5, and now reject the request. Remove them and switch to adaptive thinking.

How do I replace the thinking budget_tokens parameter?

Use adaptive thinking — thinking: { type: 'adaptive' } — and control depth with output_config.effort instead of a fixed token budget. Claude then decides how much to reason per request, which outperforms a hard-coded budget in practice.

Do newer Claude models cost more per request?

Not per token, but Claude Sonnet 5 uses a new tokenizer that produces roughly 30% more tokens for the same text than the previous Sonnet. That shifts your token budgets and cost baselines even though the price per token is unchanged. Re-measure with the count_tokens endpoint before you finalise limits.

How do I keep showing the model's reasoning after upgrading?

Thinking display defaults to 'omitted' on the current models, so thinking blocks stream with empty text. Set thinking.display to 'summarized' to restore a readable summary of the reasoning in your UI.

What happens if I keep using a retired model ID?

It returns a 404. Several older models have been retired — for example Claude Sonnet 3.7 and Haiku 3.5 in February 2026 — so update to a current ID like claude-sonnet-5, claude-opus-4-8, or claude-haiku-4-5 before those requests start failing.

How do I force JSON output without assistant prefills?

Prefilling the assistant turn now returns a 400 on the current models. Use structured outputs instead: pass output_config.format with a json_schema, and Claude constrains the response to your schema.

More Articles

Need help with this?

I'm available for Sharetribe Flex, Shopify, Next.js, and AI integration projects.

Get In Touch