Launch Fine-Tuning via the API
On this page
Update November 2, 2026 — Fine-tuning availability. OpenAI’s self-service fine-tuning is being phased out: as of May 7, 2026, organizations that had never run a fine-tuning job can no longer create one; since July 2, you must have used a fine-tuned model within the last 60 days; and starting January 6, 2027, no customer will be able to launch new jobs. Already trained models remain usable until their base model is retired. This course remains useful for understanding and leveraging an existing model; for new projects, prioritize prompt engineering, RAG, and evaluations.
Process Overview
Your data is ready, validated, and split into training and validation sets. The actual training step boils down to three moves: upload your data file, create a fine-tuning job, then wait for training to complete. Everything goes through the official Python SDK, and you’ll see the code fits in about thirty lines—most of the work happened upstream.
Install and Configure the SDK
The client instantiates without arguments: it automatically reads the OPENAI_API_KEY environment variable.
# Installation
# pip install openai
from openai import OpenAI
# The client automatically uses the OPENAI_API_KEY environment variable
client = OpenAI()
Make sure this variable is set before running the script, otherwise instantiation will fail on the first request. In production, never hard-code your key in the code: a key written in plain text ends up in Git history, and removing it after the fact is far more costly than reading it from the environment from day one.
Upload Files
Upload uses purpose="fine-tune", which tells OpenAI the file is intended for training and triggers the corresponding format checks. Upload the validation file right away: it’s technically optional, but without it training runs blind, since no validation loss will signal overfitting while the job runs.
# Upload training file
training_file = client.files.create(
file=open("training_data.jsonl", "rb"),
purpose="fine-tune"
)
print(f"File ID: {training_file.id}")
print(f"Status: {training_file.status}")
# Optional: upload validation file
validation_file = client.files.create(
file=open("validation_data.jsonl", "rb"),
purpose="fine-tune"
)
print(f"Validation ID: {validation_file.id}")
The file goes through a processing phase on OpenAI’s side. Wait until the status is processed before launching the job: a job created on a file still being processed will fail, and you’ll waste a few minutes hunting for a non-existent cause in your JSONL.
Create the Fine-Tuning Job
job = client.fine_tuning.jobs.create(
training_file=training_file.id,
validation_file=validation_file.id, # optional but recommended
model="gpt-5.6-luna", # or "gpt-5.6-luna"
suffix="my-assistant-v1" # suffix to identify your model
)
print(f"Job ID: {job.id}")
print(f"Status: {job.status}")
print(f"Base model: {job.model}")
The suffix parameter is appended to your fine-tuned model name: with "my-assistant-v1", the resulting model will be named ft:gpt-5.6-luna:your-org::my-assistant-v1:xxxxxxxx. Get in the habit of putting the use case and a version number there. The day three models coexist in your account, that’s the only thing that will let you know which one does what without opening the console.
Track Progress
A job passes through several statuses in sequence, and retrieve gives you the current one at any time.
# Get job status
job = client.fine_tuning.jobs.retrieve(job.id)
print(f"Status: {job.status}")
# Possible statuses: validating_files, queued, running, succeeded, failed, cancelled
The status alone stays laconic. Events, on the other hand, tell the story: file validation, start, step-by-step progress, end.
# View job events
events = client.fine_tuning.jobs.list_events(
fine_tuning_job_id=job.id,
limit=20
)
for event in events.data:
print(f"[{event.created_at}] {event.message}")
Rather than calling retrieve manually every five minutes, wrap the wait in a loop that returns control as soon as the job reaches a terminal state. Note that the three exit cases are handled separately: a cancelled job is not a failed job, and you’ll want to distinguish them in your logs.
import time
def track_job(client: OpenAI, job_id: str, interval: int = 30):
"""Tracks a fine-tuning job until completion."""
while True:
job = client.fine_tuning.jobs.retrieve(job_id)
print(f"Status: {job.status}")
if job.status == "succeeded":
print(f"Fine-tuned model: {job.fine_tuned_model}")
return job
if job.status == "failed":
print(f"Failure: {job.error}")
return job
if job.status == "cancelled":
print("Job cancelled.")
return job
time.sleep(interval)
# Usage
final_job = track_job(client, job.id)
Use the Fine-Tuned Model
Once the job completes successfully, your model is called exactly like a standard model. Only the value passed to model changes, which means switching to your custom model boils down, in an existing application, to modifying a configuration variable.
response = client.responses.create(
model=final_job.fine_tuned_model, # e.g., "ft:gpt-5.6-luna:org::suffix:xxxxxxxx"
input="Hello, I have a problem with my order."
)
print(response.output_text)
Manage Your Jobs
Two calls are enough to administer everything: list what exists, and interrupt what’s running. Cancellation is useful as soon as monitoring shows training going off the rails—no point paying for tokens from a job you already know will produce a bad model.
# List all your fine-tuning jobs
jobs = client.fine_tuning.jobs.list(limit=10)
for j in jobs.data:
print(f"{j.id} | {j.status} | {j.model} | {j.fine_tuned_model}")
# Cancel a running job
client.fine_tuning.jobs.cancel(job.id)
Costs and Duration
Training is billed by the number of tokens processed. Duration, on the other hand, depends on the size of your data and the number of epochs.
| Dataset size | Training duration |
|---|---|
| 50 examples | A few minutes |
| 500 examples | 15 to 30 minutes |
| 5,000 examples | 1 to 2 hours |
These orders of magnitude mainly help you size your work loop: at a few minutes per run, you iterate in the same session; at two hours, you plan. Exact prices vary by chosen model; check the OpenAI pricing page for current rates.
Key Takeaways
- The workflow is: upload file, create job, wait, use the model
- Add a descriptive
suffixto easily identify your models - Always use a validation file to evaluate quality
- The fine-tuned model is used exactly like the base model via the API
- Keep your job and model IDs—you’ll need them for versioning