The Replacement Is a Raise

OpenAI shuts down gpt-3.5-turbo, o3-mini, o4-mini, gpt-4 and o1 on October 23, 2026, and its suggested replacements raise the bill for the cheap tier. The deprecations page names a replacement for each retiring model. For the expensive ones that's a price cut. For the cheap ones it's a raise: on the same request, gpt-3.5-turbo to gpt-5.6-terra costs about 5.7x, o3-mini to gpt-5.6-sol about 6.3x, and o4-mini and gpt-4.1-nano about 2.5x. The table was written in April, before GPT-6 shipped, and the current generation is cheaper in every row. What retires, what each path costs, a Python script that finds the retiring IDs in your code and prices every option from your own token counts, and a three-day plan to ship before the shutdown.

Published 2026-10-17 by Dor Amir on the Nadir blog.

Filed under Pricing & Models.

TL;DR.

On October 23, 2026, six days from today, OpenAI shuts down a long list of legacy API models: gpt-3.5-turbo, gpt-4, gpt-4-turbo, gpt-4-1106-preview, gpt-4o-2024-05-13, gpt-4.1-nano, o1, o1-pro, o3-mini, o4-mini and gpt-image-1, plus fine-tunes built on them. After that date, calls to those IDs fail.

OpenAI's deprecations page lists a "recommended replacement" for each one. Most teams will paste it in and move on. For the expensive old models that's a price cut. For the cheap ones, it's a raise:

The replacement table was published in April, before the GPT-6 family shipped, and it points at GPT-5.6. Today the current generation is cheaper: gpt-6.1-sol lists at $2 / $10 per million tokens against gpt-5.6-sol's $5 / $30, and gpt-6-luna at $0.10 / $0.50 against gpt-5.6-luna's $0.20 / $1.20. A forced migration is the one week a year nobody asks why you're changing models. This post shows how to use it: what's retiring, what each path costs, a Python script that finds the retiring IDs in your code and prices every option from your own token counts, and a three-day plan to ship it before Friday.

Prices are OpenAI list prices as of October 17, 2026. Request shapes are illustrative and hold token counts constant across models, which newer reasoning models may not. Quality is not equal across these models. Every cheaper row in this post is a hypothesis to test on your traffic, not a recommendation.

What retires on October 23.

OpenAI announced this wave on April 22, 2026. Here's the part of the table that matters for cost, with OpenAI's own suggested replacement:

Retiring modelList price (in / out per 1M)OpenAI's suggested replacementReplacement price
gpt-3.5-turbo (and -0125)$0.50 / $1.50gpt-5.6-terra$2.00 / $12.00
gpt-4.1-nano$0.10 / $0.40gpt-5.6-luna$0.20 / $1.20
o3-mini$1.10 / $4.40gpt-5.6-sol$5.00 / $30.00
o4-mini$1.10 / $4.40gpt-5.6-terra$2.00 / $12.00
gpt-4o-2024-05-13$5.00 / $15.00gpt-5.6-sol$5.00 / $30.00
gpt-4-turbo$10.00 / $30.00gpt-5.6-sol$5.00 / $30.00
o1$15.00 / $60.00gpt-5.6-sol$5.00 / $30.00
gpt-4$30.00 / $60.00gpt-5.6-sol$5.00 / $30.00
o1-pro$150.00 / $600.00gpt-5.6-sol (reasoning.mode: pro)Sol rates, pro mode

Fine-tunes follow their base: ft-gpt-3.5-turbo, ft-o4-mini, ft-babbage-002 and ft-davinci-002 point to Terra, ft-gpt-4 to Sol, ft-gpt-4.1-nano to Luna. A fine-tune can't be "migrated". The training data can, but you're retraining or replacing it with prompting, and that's a project, not a config change.

There's a second wave behind this one. On October 14, OpenAI notified developers that gpt-4o-2024-08-06, gpt-4o-2024-11-20, gpt-4o-mini-2024-07-18, gpt-4.1 and gpt-4.1-mini leave the API on January 14, 2027, all pointing to gpt-6-luna. If you build the migration path once, this week, you'll reuse it in 89 days.

The replacement is a raise.

Price per token isn't the bill, so we priced each migration on a request. We used three illustrative shapes: 1,000 input and 250 output tokens for chat and classification (gpt-3.5-turbo, gpt-4.1-nano), 1,500 / 400 for general GPT-4-class work, and 2,000 / 1,500 for the reasoning models, with reasoning tokens counted as output (3,000 output for o1).

Chart: Cost of each retiring model's suggested replacement relative to the retiring model, on the same request, log scale. gpt-3.5-turbo to gpt-5.6-terra 5.71x; gpt-4.1-nano to gpt-5.6-luna 2.50x; o3-mini to gpt-5.6-sol 6.25x; o4-mini to gpt-5.6-terra 2.50x; gpt-4o-2024-05-13 to gpt-5.6-sol 1.44x; gpt-4-turbo 0.72x; o1 0.48x; gpt-4 0.28x. Dots show the current-generation alternative: gpt-6-luna for the cheap tier, gpt-6.1-sol for the others.
Chart: Cost of each retiring model's suggested replacement relative to the retiring model, on the same request, log scale. gpt-3.5-turbo to gpt-5.6-terra 5.71x; gpt-4.1-nano to gpt-5.6-luna 2.50x; o3-mini to gpt-5.6-sol 6.25x; o4-mini to gpt-5.6-terra 2.50x; gpt-4o-2024-05-13 to gpt-5.6-sol 1.44x; gpt-4-turbo 0.72x; o1 0.48x; gpt-4 0.28x. Dots show the current-generation alternative: gpt-6-luna for the cheap tier, gpt-6.1-sol for the others.
Retiring modelToday, per 1M requestsSuggested replacementMultipleCurrent-gen alternativeMultiple
gpt-3.5-turbo$875$5,000 (Terra)5.71x$225 (gpt-6-luna)0.26x
gpt-4.1-nano$200$500 (5.6 Luna)2.50x$225 (gpt-6-luna)1.12x
o3-mini$8,800$55,000 (5.6 Sol)6.25x$19,000 (gpt-6.1-sol)2.16x
o4-mini$8,800$22,000 (Terra)2.50x$19,000 (gpt-6.1-sol)2.16x
gpt-4o-2024-05-13$13,500$19,500 (5.6 Sol)1.44x$7,000 (gpt-6.1-sol)0.52x
gpt-4-turbo$27,000$19,500 (5.6 Sol)0.72x$7,000 (gpt-6.1-sol)0.26x
o1$210,000$100,000 (5.6 Sol)0.48x$34,000 (gpt-6.1-sol)0.16x
gpt-4$69,000$19,500 (5.6 Sol)0.28x$7,000 (gpt-6.1-sol)0.10x

Three patterns fall out.

The cheap tier pays for the upgrade. The teams most likely to still be on gpt-3.5-turbo or o3-mini in late 2026 are the ones who chose them for price: high-volume classifiers, routers, tagging jobs, support triage. The suggested path moves them up a tier. gpt-3.5-turbo to Terra isn't a like-for-like swap, it's a mid-tier model on a job that ran on a budget model for three years.

The suggested replacement is a generation old. In every row, the current-generation alternative is cheaper than the suggested one, by 1.2x to 22x. That's not a criticism of OpenAI. The table was written in April, and GPT-6 shipped later. But the table is what people will copy.

Same-name doesn't mean same-cost. gpt-4o-2024-05-13 to gpt-5.6-sol looks like a lateral move, flagship to flagship, at the same $5 input. Output doubles, from $15 to $30, and on a 1,500 / 400 request that's a 44% raise.

The o3-mini problem, worked through.

o3-mini is the sharpest case, because it was a cheap reasoning model and there's no longer a direct equivalent at its price. Here are six ways off it, on the same 2,000 / 1,500 request:

Chart: Cost per 1M o3-mini-shaped requests. Today on o3-mini $8,800. Suggested gpt-5.6-sol $55,000. gpt-5.6-terra $22,000. gpt-6.1-sol $19,000. Routed, 70% gpt-6-luna and 30% gpt-6.1-sol, $6,365. gpt-6-luna only $950, if it passes evaluation.
Chart: Cost per 1M o3-mini-shaped requests. Today on o3-mini $8,800. Suggested gpt-5.6-sol $55,000. gpt-5.6-terra $22,000. gpt-6.1-sol $19,000. Routed, 70% gpt-6-luna and 30% gpt-6.1-sol, $6,365. gpt-6-luna only $950, if it passes evaluation.

Every single-model replacement that's plausibly as capable costs more than o3-mini did. The only rows below today's bill are the ones that send part of the traffic to a small model, and that's the actual decision. An o3-mini workload is rarely uniform: a slice of it needed reasoning, and the rest was there because one model served everything. If 70% of those requests are simple enough for gpt-6-luna, the blended cost lands at about $6,365, 28% under today's bill, with gpt-6.1-sol covering the 30% that need it. If only 40% are, it's about $11,780, still well under any single-model path.

The 70% is an assumption. Measure your own split before you trust it. That's what the shadow test below is for.

Tutorial: find it, price it, ship it.

Step 1: find every retiring ID.

Model names hide in more places than the client call: config files, environment templates, eval fixtures, seed data, notebooks. This script walks a repo and lists every hit with its replacement, so the migration is a list, not a grep session.

# find_retiring.py: list every reference to a model OpenAI retires on Oct 23 or Jan 14.
import pathlib, re, sys

OCT_23 = {  # retiring id -> OpenAI's suggested replacement
    "gpt-3.5-turbo": "gpt-5.6-terra", "gpt-4-turbo": "gpt-5.6-sol",
    "gpt-4-1106-preview": "gpt-5.6-sol", "gpt-4o-2024-05-13": "gpt-5.6-sol",
    "gpt-4.1-nano": "gpt-5.6-luna", "o1-pro": "gpt-5.6-sol (pro mode)",
    "o1": "gpt-5.6-sol", "o3-mini": "gpt-5.6-sol", "o4-mini": "gpt-5.6-terra",
    "gpt-4": "gpt-5.6-sol", "gpt-image-1": "gpt-image-2.5-sunburst",
}
JAN_14 = {
    "gpt-4o-2024-08-06": "gpt-6-luna", "gpt-4o-2024-11-20": "gpt-6-luna",
    "gpt-4o-mini-2024-07-18": "gpt-6-luna", "gpt-4.1-2025-04-14": "gpt-6-luna",
    "gpt-4.1-mini-2025-04-14": "gpt-6-luna",
}
# Longest ids first so "gpt-4-turbo" wins over "gpt-4". The lookarounds stop
# "gpt-4" from matching inside "gpt-4o" or "gpt-4.1", while still catching dated
# snapshots like "gpt-4-turbo-2024-04-09" and "o1-2024-12-17".
IDS = sorted({**OCT_23, **JAN_14}, key=len, reverse=True)
PATTERN = re.compile(
    r"(?<![\w.-])(" + "|".join(map(re.escape, IDS)) + r")(?:-\d{4}-\d{2}-\d{2}|-\d{4})?(?![\w.])"
)
SKIP = {".git", "node_modules", ".venv", "dist", "build"}
EXTS = {".py", ".ts", ".tsx", ".js", ".json", ".yaml", ".yml", ".toml", ".ipynb", ".md", ".sql"}

def scan(root):
    for path in pathlib.Path(root).rglob("*"):
        if path.is_dir() or SKIP & set(path.parts):
            continue
        if path.suffix not in EXTS and not path.name.startswith(".env"):
            continue
        for n, line in enumerate(path.read_text(errors="ignore").splitlines(), 1):
            for m in PATTERN.finditer(line):
                base = m.group(1)
                when = "Oct 23" if base in OCT_23 else "Jan 14"
                target = OCT_23.get(base) or JAN_14[base]
                print(f"{when}  {path}:{n}  {m.group(0)}  ->  {target}")

if __name__ == "__main__":
    scan(sys.argv[1] if len(sys.argv) > 1 else ".")

Run it with python find_retiring.py path/to/repo. Treat the output as the inventory, not the plan: the right-hand column is OpenAI's suggestion, and the next step decides whether to take it.

Step 2: price every path from your own tokens.

The multiples above use made-up request shapes. Yours are in your usage export. Pull last month's input, cached input and output tokens per retiring model from the OpenAI usage dashboard or your own logs, and price each candidate on them.

# price_migration.py: what each replacement would have cost on last month's tokens.
# USD per 1M tokens: (input, cached input, output). OpenAI list prices, Oct 17, 2026.
PRICES = {
    "gpt-3.5-turbo": (0.50, 0.50, 1.50),   # no cache discount on this model
    "o3-mini":       (1.10, 0.55, 4.40),
    "o4-mini":       (1.10, 0.275, 4.40),
    "gpt-5.6-sol":   (5.00, 0.50, 30.00),
    "gpt-5.6-terra": (2.00, 0.20, 12.00),
    "gpt-5.6-luna":  (0.20, 0.02, 1.20),
    "gpt-6.1-sol":   (2.00, 0.10, 10.00),
    "gpt-6-luna":    (0.10, 0.01, 0.50),
}

def cost(model, tin, tcached, tout):
    p_in, p_cached, p_out = PRICES[model]
    return ((tin - tcached) * p_in + tcached * p_cached + tout * p_out) / 1e6

def options(current, tin, tcached, tout, candidates, output_inflation=1.0):
    """output_inflation: how many more output tokens the new model emits for the
    same job. Reasoning models often emit more. Measure it in the shadow test."""
    base = cost(current, tin, tcached, tout)
    print(f"{current:<40} {base:>12,.2f}  (today)")
    for c in candidates:
        if isinstance(c, dict):  # a routed split, e.g. {"gpt-6-luna": 0.7, "gpt-6.1-sol": 0.3}
            name = " + ".join(f"{int(s * 100)}% {m}" for m, s in c.items())
            usd = sum(s * cost(m, tin, tcached, tout * output_inflation) for m, s in c.items())
        else:
            name, usd = c, cost(c, tin, tcached, tout * output_inflation)
        print(f"{name:<40} {usd:>12,.2f}  {usd / base:5.2f}x")

# Example: an o3-mini workload with 2B input (600M cached) and 1.5B output tokens a month.
options("o3-mini", 2.0e9, 0.6e9, 1.5e9, [
    "gpt-5.6-sol", "gpt-5.6-terra", "gpt-6.1-sol", "gpt-6-luna",
    {"gpt-6-luna": 0.7, "gpt-6.1-sol": 0.3},
], output_inflation=1.2)

Two knobs matter more than the price table. `output_inflation`: a newer model with reasoning on by default can emit more tokens for the same job than the model it replaces. Set it from the shadow test, not from hope. The split: the routed row is only as good as your estimate of how much traffic the small model handles. Both are measurements, and Step 3 produces them.

Step 3: shadow, then cut over.

With six days, there's time for one honest test, not a quarter-long eval program:

  1. Day 1. Run find_retiring.py, pull last month's tokens per model, run price_migration.py. Pick two or three candidates per retiring model: OpenAI's suggestion, the current-generation equivalent, and a small model.
  2. Day 2. Sample 500 to 1,000 real requests per retiring model from logs. Replay them against each candidate. Score with whatever you already trust: exact match for classifiers, your eval set for extraction, a judge with a rubric for open text. Record output tokens to get output_inflation.
  3. Day 3. Pick per workload, not per company. Ship behind a flag with the old model as fallback until the 22nd, then remove the fallback. On the 23rd, the old model stops answering, so a fallback to it is only a fallback until then.

Two settings to check on whatever you pick. Reasoning effort: if you're replacing a non-reasoning model like gpt-3.5-turbo, set effort to the lowest level the job tolerates, or you'll pay for thinking the old model never did. We covered the controls in the reasoning effort tutorial. Pinned snapshots: pin the dated snapshot of whatever you move to. Aliases are what put most of these teams on a three-year-old model without noticing, and a moving alias is how prices change under you, as we wrote about in the July deprecation window.

What this doesn't tell you.

Where Nadir fits.

A retirement like this is expensive for one reason: the model name is written into the application, in many places, so changing it is a code change in every service that calls it. Nadir moves that choice out of the code. Send model="auto", and Nadir's trained classifier reads each prompt and picks from the models you've configured, against the quality floor you set on the API key, with the routing decision and cost reported in each response's metadata. When a model retires, you change the configured set, not every call site. And the routed row in the o3-mini chart, small model for the simple share and a stronger one for the rest, is what per-request routing does by default.

If you'd rather keep calling OpenAI directly, POST /v1/bucket returns simple, medium or complex with a probability for each, so your own code can send the simple share to gpt-6-luna and keep the rest on a larger model.

To see what that split looks like on your traffic before October 23, Observe mode records the decision Nadir would make on every request without changing a single response. That's the Day 2 measurement, without building the replay harness. Start with a free key.

Conclusion.

On October 23, you'll have to change model names whether you want to or not. The default answer, OpenAI's replacement column, is a fine answer for anyone leaving gpt-4, gpt-4-turbo or o1, and a raise of 2.5x to 6x for anyone leaving the cheap models. Find the IDs, price the options on your own tokens, run one shadow test, and pick per workload. The teams that paste in the suggested replacement will meet the result on November's invoice, and they'll get another chance on January 14.


Sources: OpenAI, ["Deprecations"](https://developers.openai.com/api/docs/deprecations), accessed October 17, 2026, for the October 23, 2026 and January 14, 2027 shutdown tables, suggested replacements and announcement dates; CloudZero, ["GPT-5.6 pricing: Sol, Terra, and Luna costs"](https://www.cloudzero.com/blog/gpt-5-6-pricing/), for GPT-5.6 list prices after the July 30 cut; GPT-6.1 Sol and GPT-6 Luna list prices as in [The Price of a Second](/blog/gpt-6-1-sol-ultrafast-6x-price-latency-tier-cost); legacy model prices are OpenAI's published list prices for those models. Request shapes, the 70/30 and 40/60 splits and all per-request and per-1M-request costs are our own illustrative calculations, not OpenAI figures or Nadir customer data.

More on pricing & models

What Nadir is

Nadir is an LLM router. Nadir sends each prompt to the cheapest model that can handle it. A small decision model reads each prompt against your model menu and picks the lowest-cost model it expects to handle it. When the decision model is uncertain or unavailable, the request goes to the strongest model on the menu. The routing step makes no call to your model provider.

Nadir runs two ways. The decision API returns a model, reasoning-effort, cache, context, and policy recommendation without calling a model provider, beside the gateway you already run. That is how a shadow-mode evaluation works, and its projected savings stay advisory. The OpenAI compatible managed proxy executes the route, and migration is a two-line change: point the base URL at api.getnadir.com and set model to auto. On that path an optional verifier can score a complete non-streaming answer and escalate to a stronger model when it misses the configured bar. Streaming bypasses post-generation verification. BYOK is supported on every tier.

For coding agents, Nadir Auto is a terminal launcher for Claude Code or Codex that asks Nadir for the main-session model before each turn, with your configured model as the fallback and cost ceiling. Delegation integrations for Claude Code, Codex, or Cursor instead recommend a model tier for subagent work, and the agent decides; the Claude plugin can also move a main-thread turn to Haiku when Nadir's cache check prices it cheaper, unless its switch_main_model option is off. Either way inference runs on your own provider account, so Nadir holds no provider keys on that path.

What the numbers are, and what they are not

Nadir publishes each evaluation with its scope. On checkable code, run-check-escalate solved 392 of 395 common HumanEval and MBPP problems (99.2%), graded by running the canonical tests; that applies only to tasks with runnable deterministic tests. Nadir-Tumbler posts an arena_score of 72.3 on RouterArena's public scorer, 5th of 23 routers, which measures the routing decision on RouterArena's own model pool. A reference-assisted RouterBench evaluation over 11,420 held-out triples produced a 60% lower projected cost than always-Opus with about 98% retained quality and a 1.7% catastrophic-route rate. That experiment gave the verifier the expensive-model reference answer, which production does not have, so it is a research ceiling and not the deployed path.

None of these is a production guarantee, a universal savings rate, or a forecast for any particular workload. Customer savings are reported from measured execution against a declared baseline, and customer quality only from outcome-labelled traffic. Projected savings and realized savings are separate artifacts and are never blended.

Design-partner program

Three rungs, picked by risk appetite. Rung 0 Shadow runs advisory decision calls alongside live traffic and returns a projected receipt, with nothing in the request path changed. Rung 1 Hosted is the two-line swap on a production slice and returns a realized receipt. Rung 2 On-prem is a supervised six-week proof of concept inside the partner's VPC, where no prompt, response, or usage reaches Nadir. The commitments are the same at every rung. Apply for a rung directly: Rung 0 Shadow, Rung 1 Hosted, or Rung 2 On-prem. Not sure which fits? Start at getnadir.com/contact/?reason=design-partner.

Licensing

NadirClaw is the self-hosted core, source-available under the PolyForm Noncommercial License. Source-available is the correct label; NadirClaw is not open source. Nadir Route's hosted plan has no base fee and charges a variable fee only on measured savings from requests Nadir executed.

Pages on this site

Machine-readable summaries of this site: llms.txt and llms-full.txt.