What OpenAI announced
OpenAI's deprecations page says that on October 14, 2026 it notified developers using certain GPT-4 series models of their removal from the API on January 14, 2027. The notice covers five dated snapshots, all with the same shutdown date and the same recommended replacement:
| Model | Snapshot ID | Shutdown | Recommended replacement |
|---|---|---|---|
| GPT-4o | gpt-4o-2024-08-06 | 2027-01-14 | gpt-6-luna |
| GPT-4o | gpt-4o-2024-11-20 | 2027-01-14 | gpt-6-luna |
| GPT-4o mini | gpt-4o-mini-2024-07-18 | 2027-01-14 | gpt-6-luna |
| GPT-4.1 | gpt-4.1-2025-04-14 | 2027-01-14 | gpt-6-luna |
| GPT-4.1 mini | gpt-4.1-mini-2025-04-14 | 2027-01-14 | gpt-6-luna |
The sentence that matters most for tuned workloads is short: “Fine-tuned versions of these models follow the same shutdown schedule as their underlying base models.” The supervised fine-tuning guide says the same from the other side: fine-tuned models remain available for inference until their base model shuts down. A fine-tune of gpt-4o-mini-2024-07-18 therefore stops on January 14 along with its base. The page gives a date, not an hour or a grace period.
The replacement is where the plan breaks. The gpt-6-luna model page lists Responses, Chat Completions and Batch as supported, and Fine-tuning (v1/fine-tuning) as not supported. You cannot upload last year's training file and get the same model back. For the general decision between prompting, retrieval and tuning, and the wider wind-down of OpenAI's fine-tuning platform, see RAG vs fine-tuning; this brief stays with the January deadline.
Three dates, one order of work
October 14, 2026 is the notice. January 14, 2027 is the removal. Between them sits January 6, 2027: the fine-tuning guide says eligible existing customers can create training jobs until then, “unless the base model shuts down earlier,” and that the platform is no longer accessible to new users. That cut-off matters only if your fallback is another fine-tune, and the guide's list of bases is not a safe place to look for one. It still names gpt-4.1-nano-2025-04-14, but the deprecations page shut that snapshot down on October 23, 2026, under an April 22 notice. Before you plan a retrain as a bridge, check the deprecations page for the base you would train on: a fine-tune lives only as long as its base, and any retrain would have to start before January 6.
Inventory every affected model ID
A fine-tuned model ID contains its base snapshot. The guide's own example is ft:gpt-4.1-nano-2025-04-14:openai::BTz2REMH. One search for the five dated strings therefore finds both direct snapshot calls and ft: models built on them:
rg -n "gpt-4o-2024-08-06|gpt-4o-2024-11-20|gpt-4o-mini-2024-07-18|gpt-4\.1-2025-04-14|gpt-4\.1-mini-2025-04-14" \
--glob "!node_modules" .
Run it over application code, environment files, infrastructure templates, router rules, stored prompt configurations, evaluation jobs and batch scripts. Two gaps remain. First, code that sends a short name such as gpt-4o or gpt-4.1 will not match; the notice lists dated snapshots, so check request logs for the model that actually answered rather than assuming either way. Second, a fine-tune nobody wrote into a config file can still be called by an old service. The guide points to the fine-tuning dashboard for job status, and to the Playground's model menu, where your fine-tuned models appear. Cross-check that list against your search hits.
For each confirmed caller, write down the exact model ID, whether it is a fine-tune, its training_file and validation_file from the job record (the latter can be null), the owner and the last observed call. An unknown caller is a reason to keep looking, not a reason to delete. The longer-term pattern for keeping model choice in one place is in what to do when an AI model gets pulled.
Turn the training data into an eval set
Your fine-tune was taught from JSONL in chat completions format: each line holds a messages array that ends with the assistant answer you wanted. That is already a test fixture. Everything before the final assistant message is the input; the final message is the expected output. Score the same property the fine-tune was trained for, such as a label, a JSON field or a refusal, not a vague “looks good”.
The trap is data leakage. The fine-tune has seen every training line, so it will look stronger on them than on new traffic. Score the comparison on the validation file if the job had one, or on a fresh, approved sample of recent requests labelled the same way. Use training lines only as a pool for few-shot examples, and never score on a line you also put in the prompt. Lines whose expected answer is a tool_calls message need their own scorer that checks tool name and arguments. On Chat Completions, the gpt-6-luna page notes that function calling works only with reasoning_effort set to none; check that before you blame the model for a missing call.
Rebuild the behaviour on gpt-6-luna
Write down what the fine-tune actually learned: the output shape, the label set, the tone, the cases it refuses. Put that in instructions, add a handful of training lines as few-shot examples, and enforce the shape with structured outputs, which the gpt-6-luna page lists as supported. The script below assumes a fine-tune that returns a JSON ticket label. It runs both models through the same function, on the same held-out cases, with the same scorer, and saves every output.
import json
from openai import OpenAI
client = OpenAI()
FINE_TUNE = "ft:gpt-4o-mini-2024-07-18:your-org::abc123" # your ft: model ID
CANDIDATE = "gpt-6-luna"
INSTRUCTIONS = (
"Label the support ticket. Return the category and priority "
"using only the allowed values in the schema."
)
SCHEMA = {
"type": "object",
"properties": {
"category": {"type": "string", "enum": ["billing", "bug", "account", "other"]},
"priority": {"type": "string", "enum": ["low", "normal", "urgent"]},
},
"required": ["category", "priority"],
"additionalProperties": False,
}
def load_cases(path):
cases = []
with open(path, encoding="utf-8") as f:
for line in f:
*prompt, expected = json.loads(line)["messages"]
if expected["role"] != "assistant" or "tool_calls" in expected:
continue # score tool-call lines with a separate scorer
cases.append((prompt, json.loads(expected["content"])))
return cases
def run(model, prompt, few_shot):
request = {"model": model, "input": [*few_shot, *prompt]}
if model == CANDIDATE:
request["instructions"] = INSTRUCTIONS
request["text"] = {"format": {"type": "json_schema", "name": "ticket_label",
"schema": SCHEMA, "strict": True}}
response = client.responses.create(**request)
try:
return json.loads(response.output_text)
except json.JSONDecodeError:
return None
def score(output, expected):
return isinstance(output, dict) and output == expected
examples = load_cases("training.jsonl")[:6]
few_shot = [m for prompt, label in examples
for m in [*prompt, {"role": "assistant", "content": json.dumps(label)}]]
cases = load_cases("validation.jsonl")
if not cases:
raise ValueError("validation.jsonl must contain held-out JSON label cases")
passed = {FINE_TUNE: 0, CANDIDATE: 0}
with open("comparison.jsonl", "w", encoding="utf-8") as log:
for i, (prompt, expected) in enumerate(cases):
for model in passed:
output = run(model, prompt, few_shot if model == CANDIDATE else [])
passed[model] += score(output, expected)
log.write(json.dumps({"case": i, "model": model, "output": output}) + "\n")
print({model: f"{n}/{len(cases)}" for model, n in passed.items()})
The scorer requires an exact match of both fields and rejects non-object JSON or extra fields. The few-shot examples preserve the full message history, including any system instructions. Use this example with text-only ticket cases whose final assistant content is a JSON label; adapt the loader for other formats. Replace the label values with your own. The fine-tune gets no extra instructions or examples, because it is the baseline as it runs in production today. If a capability beyond output format matters, such as tools, images or context size, check it explicitly with the model migration gate rather than inferring it from this score.
Compare while you still can
After January 14 the baseline is gone, so save comparison.jsonl as a record of what the fine-tune did. Run the full held-out set now and keep the file. Set the pass threshold from your own service requirements before you look at the result, for example no lower exact-match accuracy than the fine-tune on the held-out set and no failed schema parses. That is an example criterion, not a measured result. Read the cases where the two models disagree: some may turn out to be errors in the old labels, which is worth knowing before the old model is no longer there to settle the question.
If the rebuild passes, route a small, monitored share of real traffic to gpt-6-luna and widen it as the numbers hold. If it fails, iterate on instructions, examples and schema, or decide before January 6 whether an eligible retrain on a base that the deprecations page still shows as available is worth it. Either way, close the inventory only when every caller from your search is migrated, retired or paused with a named owner, and rerun the search after the change.
Frequently asked questions
When does GPT-4o shut down in the API?
OpenAI's deprecations page lists January 14, 2027 as the removal date for gpt-4o-2024-08-06 and gpt-4o-2024-11-20, together with gpt-4o-mini-2024-07-18, gpt-4.1-2025-04-14 and gpt-4.1-mini-2025-04-14. The notice was sent on October 14, 2026 and names gpt-6-luna as the recommended replacement.
Can I fine-tune gpt-6-luna?
No. The gpt-6-luna model page lists the fine-tuning endpoint as not supported. Tuned behaviour has to be rebuilt with instructions, few-shot examples and structured outputs, then measured against the old fine-tune.
What happens to my fine-tuned GPT-4o model?
It follows its base model. OpenAI states that fine-tuned versions of the affected models follow the same shutdown schedule as their underlying base models, so a fine-tune of gpt-4o-mini-2024-07-18 stops working for inference on January 14, 2027.
How do I find which models my code calls?
Search code and configuration for the five dated snapshot strings, which also matches fine-tuned IDs because an ft: ID contains its base snapshot name. Then check request logs for the model that actually answered, and review the fine-tuning dashboard for jobs nobody remembers.
Sources checked 2026-10-27
- OpenAI API Docs: Deprecations, GPT-4 series notice dated October 14, 2026
- OpenAI API Docs: gpt-6-luna model page, endpoints and features
- OpenAI API Docs: Supervised fine-tuning guide, January 6, 2027 training cut-off and base models
- OpenAI API Docs: Structured outputs guide, Responses API text format