# Your OpenAI fine-tunes have an end date: a migration playbook

> Canonical: https://www.overmindlab.ai/research/openai-fine-tuning-migration

- Author: Sam Morris
- Category: Tutorials
- Tags: Models, Data
- Published: 2026-11-02
- Updated: 2026-11-09

> OpenAI is winding down self-serve fine-tuning. New training jobs stop on January 6, 2027, and fine-tuned GPT-4o and GPT-4.1 models, minis included, stop answering on January 14. OpenAI's docs offer no way to export the weights, so this playbook rebuilds the model from data you own, with code and a week-by-week plan.

![Your OpenAI fine-tunes have an end date: a migration playbook](https://cdn.sanity.io/images/gv4t9qa3/production/bcb29c71ec9572e98d518b48b976f66d46f37ce9-2048x2048.png)

If your product calls a fine-tuned OpenAI model, that model now has an end date. OpenAI is winding down its self-serve fine-tuning platform. Eligible existing customers can create training jobs until January 6, 2027. Eight days later, on January 14, fine-tuned versions of GPT-4o, GPT-4o mini, GPT-4.1 and GPT-4.1 mini shut down with their base models.

The weights stay with OpenAI, but the data is yours to take. That covers the examples you trained on, the test cases you judged the model with and the production requests it has answered since. That is enough to train a replacement you own. Start while the old model still answers, because it is the bar the new one has to clear, and you can only measure it while it runs.

## What is changing, and when

| Date | What changes |
| --- | --- |
| May 7, 2026 | Organizations that had never run fine-tuning can no longer create fine-tuning jobs. |
| July 2, 2026 | Organizations with no fine-tuned inference in the past 60 days can no longer create jobs. |
| October 23, 2026 | Fine-tuned GPT-3.5 Turbo, GPT-4, GPT-4.1 nano and o4-mini models shut down. |
| October 31, 2026 | The OpenAI Evals platform becomes read-only. |
| November 30, 2026 | The OpenAI Evals dashboard and API shut down. |
| January 6, 2027 | Existing customers can no longer create new fine-tuning jobs. |
| January 14, 2027 | GPT-4o, GPT-4o mini, GPT-4.1 and GPT-4.1 mini shut down, and their fine-tuned versions with them. |

*OpenAI fine-tuning and Evals wind-down, checked November 9, 2026*

The dates come from OpenAI's [deprecations page](https://developers.openai.com/api/docs/deprecations) and its [supervised fine-tuning guide](https://developers.openai.com/api/docs/guides/supervised-fine-tuning). A fine-tuned model keeps answering until its base model shuts down, so check the base of each model you run against the table.

After January 6 you can't retrain or refresh an OpenAI fine-tune, and after January 14 the common fine-tunes stop answering. If your test cases live in OpenAI Evals, your deadline is sooner, because the Evals API shuts down on November 30.

## Step 1: audit what you run today

Before you rebuild anything, write down what the old model does and how well it does it.

1. **Which fine-tuned models does your code call?** Search code and config for model IDs that start with `ft:`, then match each one to its job, base model and training file.
2. **How much traffic does each one get?** Note requests per day, typical input and output length, and the latency your product needs. This decides how large a replacement you can afford to run.
3. **Where is the training data?** Find your own copy of every training file, and note who wrote the answers in it, whether your team, a labeling vendor or a model.
4. **How do you know it works?** Find the test set and the score the fine-tune gets on it. If there is no test set, building one is the first job, while the model still answers.
5. **Which API features do you rely on?** Note whether you call Chat Completions or the Responses API, and whether you use tool calls, structured outputs or streaming. The replacement has to support each one.

This script lists every fine-tuned model in your organization with its base model and training file ID:

**list_finetunes.py**

```python
from openai import OpenAI

client = OpenAI()  # reads OPENAI_API_KEY

# The SDK pages through every job.
for job in client.fine_tuning.jobs.list(limit=100):
    if job.fine_tuned_model:
        print(job.fine_tuned_model, job.model, job.training_file, sep="\t")
```

Keep a local copy of every file on that list. OpenAI's API reference says the training data can be retrieved with the Files API, but an OpenAI staff reply on its forum in 2021 said files uploaded for fine-tuning can't be downloaded, and developers reported the same in 2025. Look for your own copy first. If you need the API, try it now rather than in January.

## Step 2: collect the data you own

[OpenAI's fine-tuning docs](https://developers.openai.com/api/docs/guides/supervised-fine-tuning) describe no way to export a fine-tuned model's weights, so the model itself stays behind. The data comes with you.

- **Your training files.** Each line is a conversation that ends in the answer you wanted. They are already in the chat `messages` format that open-weight training tools accept.
- **Your test cases.** Export them from wherever they live. If that is OpenAI Evals, export them before November 30. OpenAI's own [migration guide](https://developers.openai.com/cookbook/examples/evaluation/moving-from-openai-evals-to-promptfoo) points to Promptfoo.
- **Production traffic.** The requests your fine-tune has answered show what real inputs look like today, which your original training set may not. If your app set `store: true` on its Chat Completions calls, OpenAI kept those requests, and `client.chat.completions.list(model=...)` returns them.

Production traffic needs care, because the replies in it were written by an OpenAI model. OpenAI's [usage policies](https://openai.com/policies/usage-policies/), effective November 20, 2026, say you must not use OpenAI models or their outputs "to generate, prepare, select, or supply training material or signals for models that are intended as alternatives to or substitutes for OpenAI models or services." The inputs your users sent are not OpenAI outputs, and neither are answers your own team wrote or corrected. If you're not sure which kind of data you have, ask your legal team before you train on it.

## Step 3: rebuild the dataset from your own records

There is an engineering reason to take training targets from your own records as well. A model trained on the old model's replies learns its mistakes too. The answer a person on your team accepted, or the final outcome in your system of record, is a better target than whatever the model said.

The script below assumes you log each request with the messages you sent and, where one exists, the answer your team accepted. It skips requests with no accepted answer and removes exact duplicates. Then it holds out one conversation in five for testing, so no conversation ends up on both sides.

**build_dataset.py**

```python
import hashlib
import json
import random

examples, seen = [], set()
for line in open("requests.jsonl"):
    row = json.loads(line)
    # Written or approved by a person, or taken from your records.
    accepted = row.get("accepted_answer")
    if not accepted:
        continue
    key = json.dumps(row["messages"], sort_keys=True)
    fingerprint = hashlib.sha256(key.encode()).hexdigest()
    if fingerprint in seen:
        continue
    seen.add(fingerprint)
    examples.append({
        "conversation": row["conversation_id"],
        "messages": row["messages"] + [{"role": "assistant", "content": accepted}],
    })

conversations = sorted({e["conversation"] for e in examples})
random.Random(0).shuffle(conversations)
held_out = set(conversations[: len(conversations) // 5])

with open("train.jsonl", "w") as train, open("test.jsonl", "w") as test:
    for e in examples:
        out = test if e["conversation"] in held_out else train
        out.write(json.dumps({"messages": e["messages"]}) + "\n")
```

`requests.jsonl` stands in for however you log requests. If yours sit in an observability tool, export them first. [From traces to a fine-tuned model](https://www.overmindlab.ai/research/traces-to-fine-tuned-model) shows the export from Langfuse and LangSmith, and [how to turn traces into a training dataset](https://www.overmindlab.ai/research/traces-to-training-dataset) covers sampling and labeling.

Then merge the result with your original training file and remove duplicates across the two. Before you train, read 50 rows at random. Check that every kind of request your product handles shows up, and that no test input also sits in the training file. OpenAI's [fine-tuning guide](https://developers.openai.com/api/docs/guides/supervised-fine-tuning) recommends starting with 50 well-crafted examples, and its [best practices](https://developers.openai.com/api/docs/guides/fine-tuning-best-practices) expect a similar gain each time you double the count. Use the same check here: train on half your data, then all of it, and keep adding examples only while the test score rises.

## Step 4: pick an open-weight base model

An open-weight model's weights are public, so you can train it and run it wherever you like. Your fine-tune did one narrow job, and a smaller open model trained on that job can often match it. In Overmind's [contract review benchmark](https://www.overmindlab.ai/research/when-bigger-isnt-better), a small model trained with Overmind and tested on 4,000+ questions across 100+ contracts made roughly half as many mistakes as a frontier flagship (91.6% vs 82.0% F1) and cost 20x less to run. Other published results point the same way. In [LoRA Land](https://arxiv.org/abs/2405.00732), a 2024 technical report from the fine-tuning company Predibase, the best LoRA fine-tune of ten small open models beat GPT-4 on most of 31 tasks, many of them classification. GPT-4 stayed ahead on 6 broader tasks, such as Python coding.

| Model | Parameters | License | Context window | Weights at 16-bit | Worth knowing |
| --- | --- | --- | --- | --- | --- |
| Qwen3.5-2B | 2B | Apache 2.0 | 262K tokens | About 4.5 GB | Small enough for classification and routing |
| Llama 3.1 8B Instruct | 8B | Llama 3.1 Community License | 128K tokens | About 16 GB | Custom license with naming and attribution rules |
| Qwen3.5-9B | 9B | Apache 2.0 | 262K tokens | About 19 GB | A common next step up from 2B |
| Gemma 4 12B | 12B | Apache 2.0 | 256K tokens | About 24 GB | Reads text, images and audio |
| gpt-oss-20b | 21B, 3.6B active | Apache 2.0 | 131K tokens | About 42 GB | OpenAI's open-weight model; ships 4-bit to fit 16 GB |
| Qwen3.8-27B | 27B | Apache 2.0 | 262K tokens | About 56 GB | Needs an 80 GB GPU at 16-bit |

*Six open-weight base models Overmind can train, from their Hugging Face model cards, November 9, 2026*

Licenses, sizes and context windows come from the model cards for [Qwen3.5-2B](https://huggingface.co/Qwen/Qwen3.5-2B), [Llama 3.1 8B Instruct](https://huggingface.co/meta-llama/Llama-3.1-8B-Instruct), [Qwen3.5-9B](https://huggingface.co/Qwen/Qwen3.5-9B), [Gemma 4 12B](https://huggingface.co/google/gemma-4-12B-it), [gpt-oss-20b](https://huggingface.co/openai/gpt-oss-20b) and [Qwen3.8-27B](https://huggingface.co/Qwen/Qwen3.8-27B). Weights at 16-bit is the parameter count times two bytes.

Running cost falls with size, though not always below what you pay today. OpenAI's [pricing page](https://developers.openai.com/api/docs/pricing) charges twice the base rate to run a fine-tuned GPT-4.1 mini or GPT-4o mini. On [OpenRouter](https://openrouter.ai/models), which lists several hosts' prices for the same open models, the base Qwen3.5-9B's list price per output token on November 9, 2026 was about 13x lower than OpenAI's price for a fine-tuned GPT-4.1 mini, and gpt-oss-20b's about 36x lower. Qwen3.8-27B's was higher than a fine-tuned GPT-4o mini's. Each host prices a fine-tuned copy on dedicated hardware differently, so treat these as a guide to scale.

Train the smallest model that passes your test set. Try two sizes at once, such as a 2B and a 9B, and move up only if both fall short. Size also decides the hardware. At 16-bit precision each billion parameters takes about 2 GB of GPU memory, plus room for the requests in flight.

## Step 5: prove the new model matches the old one

Decide what "as good as" means before you look at any results, then hold both models to it.

- **Freeze a test set.** Use held-out production requests (split by conversation), your original test cases and a short list of hard cases, such as past incidents and the requests that cost the most when they go wrong.
- **Score both models with the same checks.** Exact match or F1 works for labels and extracted fields. For JSON, check that it parses and fits your schema. For free text, use an LLM judge with a short rubric of yes-or-no checks, and compare a sample of its verdicts with human ones.
- **Run the old model now.** Score your OpenAI fine-tune on the full test set and save its replies and scores. After January 14 you can't run it again.
- **Write the pass rule down.** For example, switch only if the new model is within one point of the old one overall, misses none of the hard cases the old one got right, and returns valid JSON every time.

Both models speak the same API, so one script can score them. This version uses exact match, which suits labels and short extractions:

**parity.py**

```python
import json
import os

from openai import OpenAI

current = OpenAI()  # your OpenAI fine-tune
candidate = OpenAI(
    base_url="https://api.overmindlab.ai/api/v1",
    api_key=os.environ["OVERMIND_API_KEY"],
)

CURRENT_MODEL = "ft:gpt-4.1-mini-2025-04-14:your-org::abc123"  # from list_finetunes.py
CANDIDATE_MODEL = "your-deployment-id"

def accuracy(client, model, rows):
    correct = 0
    for row in rows:
        reply = client.chat.completions.create(
            model=model, messages=row["messages"][:-1], temperature=0
        )
        answer = reply.choices[0].message.content.strip()
        correct += answer == row["messages"][-1]["content"].strip()
    return correct / len(rows)

rows = [json.loads(line) for line in open("test.jsonl")]
old = accuracy(current, CURRENT_MODEL, rows)
new = accuracy(candidate, CANDIDATE_MODEL, rows)
print(f"current {old:.1%}, candidate {new:.1%}")
assert new >= old - 0.01, "More than a point behind: fix the data and retrain."
```

## Step 6: switch traffic without rewriting your app

An OpenAI-compatible endpoint means the switch is a configuration change. Your code keeps the OpenAI SDK. You change the base URL, the API key and the model name, as in `parity.py` above. Check which OpenAI APIs your new host supports before you switch. Overmind's endpoint, for example, serves Chat Completions with streaming and tool calls but not the Responses API, so Responses calls need moving to Chat Completions first.

If you would rather run the model yourself, download the trained weights and serve them with an OpenAI-compatible server such as vLLM. After a LoRA run the download is an adapter that [vLLM loads](https://docs.vllm.ai/en/latest/features/lora/) on top of the open base model it was trained from:

```bash
vllm serve meta-llama/Llama-3.1-8B-Instruct --enable-lora --lora-modules support-router=./adapter
```

Either way, move traffic in stages. Start in shadow mode, where the new model answers a copy of each live request and users still see the old model's reply. Compare the two on live traffic, then move 5% of users, then half, then everyone. Keep the OpenAI model as the fallback until it shuts down.

## A week-by-week plan

Seven working weeks fit before January 14 if you start in mid-November. If your team takes a holiday break, aim to finish by mid-December.

| Week | What to do | Done when |
| --- | --- | --- |
| 1 | Audit models, traffic and data. Export test cases from OpenAI Evals before it shuts on November 30. | Every ft: model has an owner, a training file and a test set |
| 2 | Rebuild the dataset, freeze the test split, and score the current fine-tune on it | train.jsonl, test.jsonl and baseline scores are saved |
| 3 | Train two sizes of open-weight model, smallest first | Both are scored on the frozen test set |
| 4 | Fix the data where the candidate fails, then retrain | The candidate meets the pass rule |
| 5 | Shadow traffic: the new model answers copies of live requests | Live scores match the offline ones |
| 6 | Move 5% of traffic, then half | Product metrics hold steady |
| 7 | Move all traffic and keep the OpenAI model as a fallback | Nothing in production calls an ft: model |

*A seven-week migration, assuming a start in mid-November 2026*

If you want one last refreshed OpenAI fine-tune as a fallback, start that job before January 6.

## Making the move with Overmind

Overmind runs the steps above in one place, and your coding agent can drive each one.

1. **Bring the data.** Upload the JSONL file you trained on as it is. Overmind trains on the same chat `messages` format, tool calls included. There is no one-click OpenAI import, so production traffic comes in through the Python SDK, OpenTelemetry or a Langfuse, LangSmith, Braintrust or Galileo connector.
2. **Prepare it.** In the [Data Workshop](https://www.overmindlab.ai/product/data-workshop), your coding agent writes each preparation step as a small Python script. Overmind runs each script in an isolated sandbox and keeps every version. It also checks that each version fits the training format. When you split data into training and test sets, it removes exact duplicates and keeps rows from the same conversation on one side.
3. **Train.** [Fine-tune](https://www.overmindlab.ai/product/model-training) any model in the table above with LoRA, or with full fine-tuning on models up to 14B.
4. **Compare.** Overmind can score the trained model against its untouched base on your test set. It can't call a private OpenAI fine-tune, so score your fine-tune's saved replies with the same checks yourself.
5. **Serve.** Call the model with the OpenAI SDK at `https://api.overmindlab.ai/api/v1`, or download the weights from the model page or with `overmind model download-checkpoint`. Serverless deployments scale down when idle, so the first request after a quiet spell waits for a worker. For steady traffic, a dedicated GPU deployment lets you set the replica range.

**Have a fine-tuned OpenAI model in production?** Your coding agent can do this move for you. Open your product's repo in Claude Code, Cursor, Codex or OpenCode and paste `Set me up on Overmind: https://console.overmindlab.ai/setup.md` into the chat. No repo to hand? [Start for free](https://console.overmindlab.ai/?utm_source=research&utm_medium=article&utm_campaign=always-on&utm_content=openai-fine-tuning-migration-next-step) and upload the JSONL file you trained on. The console then gives you a prompt for your coding agent that works without a repo.

## FAQ

**When does OpenAI fine-tuning shut down?**

Eligible existing customers can create new fine-tuning jobs until January 6, 2027. A fine-tuned model keeps answering until its base model shuts down. For GPT-4o, GPT-4o mini, GPT-4.1 and GPT-4.1 mini, that is January 14, 2027.

**Can I download my fine-tuned OpenAI model?**

Not as far as OpenAI's documentation shows. Its fine-tuning docs describe no way to export a fine-tuned model's weights, and checkpoints can only be shared with other projects inside OpenAI. You keep your training files and test cases, and train a new model on them.

**Can I train a new model on my fine-tune's replies?**

Check first. From November 20, 2026, OpenAI's usage policies bar using its outputs to prepare training data for models meant to substitute for OpenAI's. Inputs from your users and answers your team wrote or corrected are not OpenAI outputs. Ask your legal team if you're unsure.

**Which open model should replace a fine-tuned GPT-4.1 mini?**

Start small. Train a 2B to 9B model on the same task and score it against your fine-tune on the same test set. Move to a larger model only if the small ones fall short.

**Do I need to change my application code?**

Very little, if the new model sits behind an OpenAI-compatible endpoint. You change the base URL, the API key and the model name. Calls to the Responses API need moving to Chat Completions first if your new host only serves Chat Completions.

Overmind is the model training platform for AI teams. [Start for free](https://console.overmindlab.ai/?utm_source=research&utm_medium=article&utm_campaign=always-on&utm_content=openai-fine-tuning-migration-footer).

## Related research

- [From traces to a fine-tuned model: what actually works](https://www.overmindlab.ai/research/traces-to-fine-tuned-model): Traces from an observability tool can train a model that does one of your agent's jobs, but only a small slice is worth training on. Keep runs with a good outcome, remove duplicates and personal data, and test on data the model never saw. This guide shows each step, with export code for Langfuse and LangSmith.
- [RAG vs fine-tuning](https://www.overmindlab.ai/research/rag-vs-fine-tuning): RAG retrieves documents and adds them to the prompt, so the model has the right facts at the moment it answers. Fine-tuning trains the model on examples, so the behavior you want becomes its default. Use RAG for knowledge that changes or needs a source. Fine-tune when the model has the facts but gets the format, tone or decision wrong on a narrow task that repeats.
- [My summer at Overmind](https://www.overmindlab.ai/research/my-summer-at-overmind): A summer internship reflection on autonomy, hard problems, and building across the Overmind product.