Overview
OpenAI's API stops accepting new fine-tuning jobs on 6 January 2027, and models fine-tuned from gpt-5 or o3 shut down with those models on 11 December 2026 (OpenAI's deprecations page).
- Set the SDK's base URL to Gridline's and its API key to a Gridline token.
- Upload the same files: chat-format training files with
purpose=fine-tune, batch input withpurpose=batch. - Name a Gridline base model where you named a GPT model. A job that succeeds names its model
ft:<base>:<org>:<job>; use that in batches. - Check the fields and features Gridline does not offer: each is refused by name, never silently ignored.
Point your client at Gridline
Python, with the official openai package:
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.gridlinegpu.com/openai/v1",
api_key=os.environ["GRIDLINE_TOKEN"],
)Node, with the official openai package (JavaScript; in TypeScript, Gridline's extra fields below need a cast):
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.gridlinegpu.com/openai/v1",
apiKey: process.env.GRIDLINE_TOKEN,
});Both SDKs also read the base URL and key from the environment, so code that builds a client with no arguments needs no change:
export OPENAI_BASE_URL="https://api.gridlinegpu.com/openai/v1"
export OPENAI_API_KEY="$GRIDLINE_TOKEN"Keep /openai/v1 in the base URL. The OpenAI-compatible routes are all under it, so a request without it, such as https://api.gridlinegpu.com/v1/models, answers 404. The models list is https://api.gridlinegpu.com/openai/v1/models.
Your token
Sign up, verify your email and add credits by card at Get started. Then create a token with the Gridline CLI. gridline setup --apply asks you to approve it in your browser, then saves a private token, valid for 30 days:
python -m pip install 'gridlinegpu[source-contract]>=0.3.2,<0.4'
gridline setup --apply
export GRIDLINE_TOKEN="$(cat ~/.config/gridline/python-token)"Send it as the bearer token, where you sent your OpenAI key. OpenAI's organization and project settings have no Gridline equivalent; leave them unset.
Check before you upload
The Gridline CLI checks your OpenAI training and validation files and your OpenAI job offline. It lists every line Gridline's upload would refuse, each job field Gridline would refuse or treat differently, and prints the equivalent Gridline call. Nothing is sent. --job takes the job's create body, or the job object OpenAI returns for client.fine_tuning.jobs.retrieve, saved as JSON:
gridline migrate openai train.jsonl \
--validation-file valid.jsonl \
--job openai-job.json \
--model qwen2.5-7b-instructIt exits 0 when Gridline would accept the files and the job, and 1 otherwise. The API's answer at upload and at create decides.
Files
client.files.create works unchanged. The purposes:
| purpose | For |
|---|---|
fine-tune | Training and validation files: one chat conversation per line, a messages list, as OpenAI's chat fine-tuning format. |
batch | Batch input: one request per line, with custom_id, method, url and body. |
batch_output | Written by Gridline for a batch's output and error files. You download them; you never upload with it. |
A fine-tuning job's training and validation files are each at most 64 MiB. A batch input file is at most 200 MB and 50,000 requests.
Model names
Gridline fine-tunes and serves open models. Name one of these where you named a GPT model:
| Model | Weights | Fine-tuning | Batch endpoint |
|---|---|---|---|
qwen2.5-0.5b-instruct | Qwen/Qwen2.5-0.5B-Instruct (Apache-2.0) | Yes | /v1/chat/completions |
qwen2.5-1.5b-instruct | Qwen/Qwen2.5-1.5B-Instruct (Apache-2.0) | Yes | /v1/chat/completions |
qwen2.5-7b-instruct | Qwen/Qwen2.5-7B-Instruct (Apache-2.0) | Yes | /v1/chat/completions |
mistral-7b-instruct-v0.3 | mistralai/Mistral-7B-Instruct-v0.3 (Apache-2.0) | Yes | /v1/chat/completions |
qwen3-8b | Qwen/Qwen3-8B (Apache-2.0) | Yes | /v1/chat/completions |
llama-3.1-8b-instruct | RedHatAI/Llama-3.1-8B-Instruct (Llama 3.1 Community License) | Yes | /v1/chat/completions |
gpt-oss-20b | openai/gpt-oss-20b (Apache-2.0) | Yes (adapter download only) | /v1/chat/completions |
qwen3.5-9b | Qwen/Qwen3.5-9B (Apache-2.0) | Yes (adapter download only) | /v1/chat/completions |
qwen3.8-27b | Qwen/Qwen3.8-27B-FP8 (Apache-2.0) | No | /v1/chat/completions |
nemotron-3.5-lightning-30b-a3b | nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-NVFP4 (OpenMDW-1.1) | No | /v1/chat/completions |
gemma4-26b-a4b-it | google/gemma-4-26B-A4B-it (Apache-2.0) | No | /v1/chat/completions |
qwen3-embedding-0.6b | Qwen/Qwen3-Embedding-0.6B (Apache-2.0) | No | /v1/embeddings |
bge-m3 | BAAI/bge-m3 (MIT) | No | /v1/embeddings |
A fine-tuned model is named ft:<base>:<org>:<job-suffix>, where <job-suffix> is the first 12 hex characters of the job id after ftjob-:
ft:qwen2.5-7b-instruct:o3f9a1c2d4e5:7b2c9e41a0d3Your suffix is not part of the name. A fine-tuned model is a chat model: name it in /v1/chat/completions batch lines. GET /models lists the catalog and your ft: models; DELETE /models/{model} deletes one.
Fine-tuning jobs
client.fine_tuning.jobs.create takes OpenAI's fields. Gridline trains a LoRA or QLoRA adapter on the base model:
| Field | On Gridline | Notes |
|---|---|---|
model | Different | A Gridline base model id from the table above. |
training_file | As OpenAI | A file id uploaded with purpose=fine-tune. |
validation_file | As OpenAI | Optional. Without one, the job holds out 5% of the training rows to measure eval loss. |
hyperparameters | As OpenAI | The table below. Send it at the top level or under method.supervised.hyperparameters, once. |
method | Different | {"type": "supervised"} or {"type": "dpo"} (preference rows); reinforcement is refused. Gridline adds the adapter: method.supervised.adapter (method.dpo.adapter), or method as the string "lora" or "qlora". |
suffix | Ignored | Accepted (at most 64 characters) and kept on the job, but not part of the model name: the job records a suffix_ignored event. |
seed | As OpenAI | An integer from 0 to 2,147,483,647. Default 0. |
metadata | As OpenAI | At most 16 string pairs. |
integrations | Different | Accepted only as null or []: Gridline offers no Weights & Biases or other integration. |
Any other field is refused with Unsupported parameter and its name. The hyperparameters, OpenAI's and Gridline's:
| Hyperparameter | From | Range | Default |
|---|---|---|---|
n_epochs | OpenAI | 1 to 10 | 1 (also "auto") |
learning_rate_multiplier | OpenAI | 0.01 to 10 Times the base learning rate, 2e-4. | 1 (also "auto") |
batch_size | OpenAI | 1 to 64 | 8 (also "auto") |
learning_rate | Gridline | 1e-6 to 0.01 Instead of the multiplier, never with it. | 2e-4 |
lora_rank | Gridline | 1 to 64 Up to 64 with method lora or qlora (adapters up to 383 MiB; on a 7B or 8B base, QLoRA runs on a 48 GB GPU at the LoRA rate at any rank); with DPO, at most the base's max rank, 24 to 64, so its adapter fits 128 MiB. | 16 |
lora_alpha | Gridline | 1 to 256 | 32 |
max_seq_length | Gridline | 128 to 8,192 Up to 8,192 for supervised tuning on Qwen2.5-7B, Qwen3-8B and Llama 3.1 8B. Above 2,048, use rank 16 or lower. DPO and other bases have a 2,048-token limit. | 2,048 |
Gridline's hyperparameters go in the same dictionary; the SDKs pass them through. Python:
training = client.files.create(file=open("train.jsonl", "rb"), purpose="fine-tune")
job = client.fine_tuning.jobs.create(
model="qwen2.5-7b-instruct",
training_file=training.id,
method={
"type": "supervised",
"supervised": {"adapter": "qlora", "hyperparameters": {"n_epochs": 2, "lora_rank": 32}},
},
)
print(job.gridline["quote"]) # creating the job started it; the quote shows what it can costNode:
import fs from "node:fs";
const training = await client.files.create({
file: fs.createReadStream("train.jsonl"),
purpose: "fine-tune",
});
const job = await client.fineTuning.jobs.create({
model: "qwen2.5-7b-instruct",
training_file: training.id,
hyperparameters: { n_epochs: 2, lora_rank: 32 },
});- Every job object carries Gridline's fields under
gridline: the quote when it is created (an estimate, never a price) and, once it succeeds, the adapter and the eval loss before and after training. - A job must finish within 2 hours; create refuses one estimated longer.One job trains at a time per account. New accounts can have 1 open at once, other accounts 3, unless Gridline has set a different limit; a refused create gives yours in error.limit.
- Download the adapter of a model that succeeded, two files:
GET /gridline/adapters/{model}/files/adapter_model.safetensorsand.../adapter_config.json, under the base URL (https://api.gridlinegpu.com/openai/v1/gridline/adapters/...). - A QLoRA adapter is trained against a 4-bit quantized base and served on the bf16 base, so its outputs can differ slightly from what training measured.
The Fine-tuning API page has the training format, the quote and the errors.
Batches
client.batches.create works unchanged. completion_window is 24h or 72h only, and endpoint is one of /v1/chat/completions or /v1/embeddings. Input lines keep OpenAI's shape: custom_id, method, url and body.
batch_input = client.files.create(file=open("requests.jsonl", "rb"), purpose="batch")
batch = client.batches.create(
input_file_id=batch_input.id,
endpoint="/v1/chat/completions",
completion_window="24h",
)const input = await client.files.create({
file: fs.createReadStream("requests.jsonl"),
purpose: "batch",
});
const batch = await client.batches.create({
input_file_id: input.id,
endpoint: "/v1/chat/completions",
completion_window: "24h",
});- One base model per batch. A chat batch may also name up to 8 of your
ft:models of that base (fewer on some bases: Models), line by line inbody.model. - These request fields are refused at validation, never ignored:
tools,tool_choice,parallel_tool_calls,functions,function_call,stream,stream_options,logit_bias,service_tier,store,prediction,audio,modalities,metadata,reasoning_effort,web_search_options,suffix,best_of,dimensions. On chat linesresponse_formatis accepted astext,json_objectorjson_schema(see Structured outputs);useris accepted and ignored. - Output and error files are written as with OpenAI; download them with
client.files.content. 72his Gridline's flex window, at the same price as24h. The OpenAI Python SDK's type admits only"24h", so passcompletion_window="24h", extra_body={"completion_window": "72h"}(see Completion window).
The Batch API page has the line format, statuses, limits and retention.
Usage and limits by API key
Each batch and fine-tuning job records the API key that created it. GET /gridline/usage/keys lists your keys with their batches, fine-tuning jobs, tokens (input, output, trained), the amounts held and charged, and their limits. from and to are UTC days (YYYY-MM-DD), at most 31 days in all; without them, this month so far. Batches and jobs made before per-key tracking are one line with api_key_id null.
GET /gridline/usage/keys/{key} pages one key's batches and jobs, newest first (limit 1 to 100, default 20; after a batch or job id), with the key's spend today and in total. {key} is a key id from the list, or current for the key you call with.
The account owner can give each key a daily and a total spend limit when signed in to Gridline, under Usage by API key. A create that would pass one is refused with HTTP 429, error.code limit_exceeded and error.limit naming the limit, the key, its spend and the maximum; nothing is created. A daily refusal carries Retry-After until 00:00 UTC.
| Limit | Counts | Resets | At most |
|---|---|---|---|
api_key_daily_spend_usd | The key's spend in the current UTC day. | At 00:00 UTC. | 1,000 USD |
api_key_total_spend_usd | The key's spend over its life. | Never. | 100,000 USD |
How the limits behave today:
- Limits are checked when a batch or fine-tuning job is created, not while it runs: a batch already running can finish above its key's limit.
- A batch or job counts toward its key's spend once it starts and reserves credit. Batches and jobs created but not yet started do not count yet, so several created close together, or at the same time, can each pass the same limit.
- A batch's cost is not known when it is created, so one batch can pass a limit by up to its own cost. A fine-tuning job is checked with its quoted maximum.
- Your account's credit and the account's own limits still bound what every key can spend.
Routes
Every route under https://api.gridlinegpu.com/openai/v1:
| Route | Shape | Use |
|---|---|---|
POST /files | OpenAI's | Upload a file with purpose=fine-tune or purpose=batch. |
POST /files/distill | Gridline's | Turn a batch's requests and results into fine-tune training and validation files. |
GET /files | OpenAI's | List your files. |
GET /files/{file_id} | OpenAI's | Retrieve a file object. |
GET /files/{file_id}/content | OpenAI's | Download a file, such as a batch's output. |
DELETE /files/{file_id} | OpenAI's | Delete a file. |
POST /fine_tuning/jobs | OpenAI's | Create a fine-tuning job (fields below). |
GET /fine_tuning/jobs | OpenAI's | List your jobs. |
GET /fine_tuning/jobs/{job_id} | OpenAI's | Retrieve a job. |
GET /fine_tuning/jobs/{job_id}/events | OpenAI's | Progress, warnings and eval metrics. |
GET /fine_tuning/jobs/{job_id}/checkpoints | OpenAI's | A succeeded job's one checkpoint: its ft: model at the last step, with its metrics. |
POST /fine_tuning/jobs/{job_id}/cancel | OpenAI's | Cancel a job. |
POST /batches | OpenAI's | Create a batch. |
GET /batches | OpenAI's | List your batches. |
GET /batches/{batch_id} | OpenAI's | Retrieve a batch. |
POST /batches/{batch_id}/cancel | OpenAI's | Cancel a batch. |
GET /models | OpenAI's | The catalog models and your ft: models. |
GET /models/{model} | OpenAI's | One model. |
DELETE /models/{model} | OpenAI's | Delete one of your ft: models. |
GET /gridline/adapters/{model}/files/{name} | Gridline's | Download your ft: model's adapter: adapter_model.safetensors and adapter_config.json. |
POST /gridline/adapters/{model}/merged | Gridline's | Start a merge of your ft: model's adapter into its base, to download as full safetensors at no charge. |
GET /gridline/adapters/{model}/merged | Gridline's | The merge's status and, once it has succeeded, the export's file list. |
DELETE /gridline/adapters/{model}/merged | Gridline's | Delete the merged export, or cancel a pending merge. |
GET /gridline/adapters/{model}/merged/files/{path} | Gridline's | Download one file of the merged export: a redirect that carries the file's SHA-256 in x-gridline-sha256. |
GET /gridline/usage/keys | Gridline's | Usage per API key over a window of UTC days: batches, fine-tuning jobs, tokens, held and charged amounts, and each key's spend limits. |
GET /gridline/usage/keys/{key} | Gridline's | One API key's batches and jobs, and its spend today and in total against its limits. {key} is a key id, or current for the key you call with. |
What is different
Settings both take, with a different default or effect:
| Setting | On OpenAI | On Gridline |
|---|---|---|
| n_epochs "auto" or absent | Chosen from the dataset size. | 1 epoch. Set n_epochs to the number your OpenAI job ran. |
| seed absent | OpenAI generates one. | 0. |
| Example length | Up to 65,536 tokens an example. | Rows longer than max_seq_length (default 2,048) are cut at max_seq_length tokens; a row left with no trained token is dropped. |
What OpenAI offers that Gridline does not, and what to do instead:
| On OpenAI | On Gridline |
|---|---|
| Real-time requests (POST /chat/completions, /embeddings, /responses) | Not offered: Gridline runs offline batches. Send the same request bodies as batch lines, or download your adapter and serve it yourself. |
| GPT base models | Fine-tune and batch the open models in the table above. |
| Reinforcement fine-tuning | Refused at create: supervised fine-tuning and DPO only (method.type "supervised" or "dpo"). |
| Full fine-tunes | Gridline trains a LoRA or QLoRA adapter on the base; full fine-tunes are not offered. To take the model with you, download the adapter merged into its base (POST /gridline/adapters/{model}/merged). |
| Tools, function calling and images in training files | Refused at upload, by name: tools, functions, name, tool_calls, content parts and the tool and function roles. |
| Intermediate fine-tuning checkpoints | Not offered: the checkpoints route lists only the final step's adapter, registered as one ft: model. |
| The Uploads API (POST /uploads) | Not offered: upload each file in one request. |
| Other batch endpoints (/v1/responses, /v1/completions, moderations, images, audio) | Refused at validation: batches target chat completions and embeddings. |
| Streaming, tools and the other refused fields in batch lines | Refused at validation, never ignored: the list above. On chat lines response_format is accepted as text, json_object or json_schema. |
Costs
| Per 1M tokens | OpenAI, fine-tuned gpt-4o-mini-2024-07-18 | Gridline, Llama 3.1 8B |
|---|---|---|
| Training | $3.00 | $0.29 |
| Batch input | $0.15 | $0.049 |
| Batch output | $0.60 | $0.115 |
Same price for Qwen 2.5 7B and Mistral 7B.
For example, train a 250,000-token file for 3 epochs (750,000 training tokens), then run a batch on the fine-tuned model with 10,000,000 input and 1,000,000 output tokens. On OpenAI that is $2.25 for training and $2.10 for the batch, $4.35 in all. On Gridline it is $0.50 for training (the minimum per job) and $0.605 for the batch, $1.105 in all.
Different models; quality not compared. OpenAI prices as of 13 November 2026.
Fine-tuning and batch jobs are paid for from your prepaid credits.
What a credit is worth, the credit packs and each model's rates are on the pricing page.