Fine-tuning / API reference

# Fine-tuning API reference

Every fine-tuning route: datasets, jobs, the estimate, models, endpoints and hardware. OpenAI-compatible where a shape exists.

## Routes

The routes follow OpenAI's Files and Fine-tuning APIs where a shape exists, so an OpenAI SDK can upload files and create, list, retrieve and cancel jobs unchanged; endpoints, hardware and the estimate are the platform's own. Every route takes your API key (`Authorization: Bearer $YELNO_API_KEY`) and answers in the gateway's [error envelope](https://yelno.ai/docs/errors). Every route except the overview checks the `allow_fine_tuning` policy. Amounts are integer nanos as JSON numbers (1 USD = 1,000,000,000), and every price is a final price.

| Method | Path | What it does |
| --- | --- | --- |
| GET | /v1/fine_tuning | Overview: enabled, available, where the data goes, retention, settings in force. |
| GET | /v1/fine_tuning/base_models | Supported base models, their methods and final training prices. |
| GET | /v1/fine_tuning/hardware?model= | Hardware a model can be served on, with the final price per replica-hour. |
| POST | /v1/files | Upload a dataset (multipart: file, purpose=fine-tune, optional format). Validated on upload. |
| GET | /v1/files?purpose=fine-tune | Your fine-tuning datasets. |
| GET | /v1/files/{file_id} | One file, with its validation report. |
| DELETE | /v1/files/{file_id} | Delete a file here and at the provider (409 file_in_use while an open job uses it). |
| POST | /v1/fine_tuning/estimate | The estimate and the hold for a job body, without starting anything. |
| POST | /v1/fine_tuning/jobs | Create a job (Idempotency-Key supported). Places the hold, then starts training. |
| GET | /v1/fine_tuning/jobs | Your jobs, newest first (limit, after, status). |
| GET | /v1/fine_tuning/jobs/{job_id} | One job: status, progress, estimate, hold and charge. |
| GET | /v1/fine_tuning/jobs/{job_id}/events | The job's events, newest first: messages, steps, loss. |
| GET | /v1/fine_tuning/jobs/{job_id}/checkpoints | The job's checkpoints. |
| POST | /v1/fine_tuning/jobs/{job_id}/cancel | Cancel an open job. |
| GET | /v1/models | Includes your organization's ready fine-tuned models (owned_by: your organization). |
| GET | /v1/fine_tuning/models | Your fine-tuned models. |
| GET | /v1/fine_tuning/models/{model_id} | One model, by ftm_… or ft:… id. |
| PATCH | /v1/fine_tuning/models/{model_id} | Rename a model ({name}). |
| DELETE | /v1/fine_tuning/models/{model_id} | Archive a model (refused while its endpoint runs). |
| POST | /v1/fine_tuning/endpoints | Create an endpoint for a model; starts it by default. |
| GET | /v1/fine_tuning/endpoints | Your endpoints (state filter). |
| GET | /v1/fine_tuning/endpoints/{endpoint_id} | One endpoint, with its cost so far. |
| PATCH | /v1/fine_tuning/endpoints/{endpoint_id} | Change auto-stop settings: auto_stop_idle_minutes, max_hours, budget. |
| POST | /v1/fine_tuning/endpoints/{endpoint_id}/start | Start a stopped endpoint. |
| POST | /v1/fine_tuning/endpoints/{endpoint_id}/stop | Stop it: bills up to now and releases the hold. |
| DELETE | /v1/fine_tuning/endpoints/{endpoint_id} | Stop if needed and delete. |

In the console, the same operations live under `/api/v1/fine-tuning/…` with a session token, and lists use the console's cursor page. The examples below show the objects as the API returns them; ids and amounts are illustrative.

GET /v1/fine_tuning · 200

```
{"object": "fine_tuning.overview", "enabled": true, "available": true, "unavailable_reason": null,
 "provider": {"name": "Together AI",
   "data_destination": "Your training and validation files are uploaded to Together AI to run the job.",
   "retention": "Datasets are deleted from Together AI after the job finishes unless you choose to keep them. The platform keeps its own copy under your file retention.",
   "terms_url": "https://www.together.ai/terms-of-service"},
 "settings": {"hold_buffer_bps": 2000, "min_prepaid_hours": 1, "billing_interval_seconds": 300,
   "default_auto_stop_idle_minutes": 15, "approval_threshold_nanos": 0},
 "counts": {"datasets": 0, "jobs_running": 0, "models": 0, "endpoints_running": 0},
 "running_cost_per_hour_nanos": 0, "currency": "USD"}
```

`available` is false, with `unavailable_reason` `kill_switch`, `provider_unavailable` or `zdr_required`, when nothing can be started.

## Datasets

`POST /v1/files`, multipart, with `file`, `purpose=fine-tune` and optionally `format` (`chat`, `completion` or `preference`; detected from the first valid line otherwise). Up to 100 MB and 200,000 lines. The response is OpenAI's file object with a `yelno.fine_tuning` block holding the validation report. An invalid file is stored with `status: "error"` so its report can be read again. `GET /v1/files?purpose=fine-tune` lists them, `GET /v1/files/{file_id}` returns one, and `DELETE` removes it here and at the provider.

Upload

```
curl https://yelno.ai/v1/files \
  -H "Authorization: Bearer $YELNO_API_KEY" \
  -F purpose=fine-tune -F format=chat -F file=@train.jsonl
```

POST /v1/files · 200

```
{"id": "file_…", "object": "file", "purpose": "fine-tune", "filename": "train.jsonl", "bytes": 40960,
 "created_at": 1760000000, "status": "processed", "status_details": null,
 "yelno": {"fine_tuning": {"id": "ftf_…", "format": "chat", "state": "valid", "lines": 120, "bytes": 40960,
   "tokens_estimate": 53000, "sha256": "…",
   "report": {"valid": true, "errors": [], "errors_truncated": 0,
     "warnings": [{"line": 0, "code": "few_examples", "message": "…"}],
     "stats": {"examples": 120, "tokens_estimate": 53000, "tokens_min": 120, "tokens_max": 900, "tokens_mean": 441,
               "duplicates": 0, "empty_lines": 0, "examples_over_context": 0, "assistant_turns": 240, "tool_calls": 0}}}}}
```

The validation codes and formats are in the [guide](https://yelno.ai/docs/fine-tuning).

## Jobs

`POST /v1/fine_tuning/jobs` and `POST /v1/fine_tuning/estimate` take the same body. `model` is a base model's slug, its provider model id or its `ftb_…` id. `method.type` is `supervised` (default) or `dpo`, `method.training_type` `lora` (default) or `full`; OpenAI's `method` object with nested hyperparameters is accepted too. `suffix` matches `^[a-z0-9-]{1,40}$`. `n_evals` above 0 needs a `validation_file`.

Request body (every field but model and training_file is optional)

```
{"model": "meta-llama/Meta-Llama-3.1-8B-Instruct-Reference",
 "training_file": "file_…", "validation_file": null,
 "method": {"type": "supervised", "training_type": "lora"},
 "hyperparameters": {"n_epochs": 1, "learning_rate": 0.00001, "batch_size": "max", "warmup_ratio": 0,
   "n_checkpoints": 1, "n_evals": 0, "train_on_inputs": "auto", "lora_r": 8, "lora_alpha": 16,
   "lora_dropout": 0, "lora_trainable_modules": "all-linear", "max_grad_norm": 1, "weight_decay": 0},
 "suffix": "support-bot", "keep_provider_files": false, "project_id": null}
```

Create accepts an `Idempotency-Key` header and answers `200` with the job. The hold is placed first, then the files go to the provider and the job is created there; a provider refusal fails the job with `error.code: "provider_rejected"` and releases the hold.

POST /v1/fine_tuning/jobs · 200

```
{"id": "ftj_…", "object": "fine_tuning.job", "model": "meta-llama/Meta-Llama-3.1-8B-Instruct-Reference",
 "created_at": 1760000000, "finished_at": null, "fine_tuned_model": null, "organization_id": "org_…",
 "status": "queued", "training_file": "file_…", "validation_file": null, "trained_tokens": null,
 "hyperparameters": {"n_epochs": 3, "…": "the validated set"},
 "method": {"type": "supervised", "training_type": "lora"},
 "suffix": "support-bot", "error": null, "result_files": [], "seed": null, "estimated_finish": null,
 "yelno": {"state": "queued", "project_id": null, "provider": "Together AI", "base_model_id": "ftb_…",
   "estimate_nanos": 4000000000, "hold_nanos": 4800000000, "charged_nanos": null,
   "settlement": "pending", "currency": "USD", "fine_tuned_model_id": null, "keep_provider_files": false,
   "progress": {"step": null, "total_steps": null, "epochs_completed": null, "seconds_remaining": null},
   "provider_status": "queued", "created_by": "usr_…"}}
```

`status` is OpenAI's word (`validating_files`, `queued`, `running`, `succeeded`, `failed`, `cancelled`); `yelno.settlement` is `pending`, `charged`, `released` or `reconciliation` (the provider's final figures are missing and the job is under review, never free). `GET /v1/fine_tuning/jobs` takes `limit`, `after` and `status` and returns `{object: "list", data, has_more}`.

GET /v1/fine_tuning/jobs/{job_id}/events · 200

```
{"object": "list", "has_more": false, "data": [
 {"id": "ftev_…", "object": "fine_tuning.job.event", "created_at": 1760000600, "level": "info",
  "type": "metrics", "message": "Step 40/120", "data": {"step": 40, "total_steps": 120, "loss": 1.12, "epoch": 1, "checkpoint": null}}]}
```

GET /v1/fine_tuning/jobs/{job_id}/checkpoints · 200

```
{"object": "list", "has_more": false, "data": [
 {"id": "…", "object": "fine_tuning.job.checkpoint", "created_at": 1760001200,
  "fine_tuning_job_id": "ftj_…", "step_number": 120, "name": "…", "type": "…"}]}
```

`POST /v1/fine_tuning/jobs/{job_id}/cancel` returns the job; cancelling a finished job is `409 job_not_cancellable`.

## Estimate

`POST /v1/fine_tuning/estimate` computes what the job would cost without starting it or holding anything: billable tokens = training tokens × epochs + validation tokens × evaluations, at the final price per 1M tokens, never below the minimum charge. `hold_nanos` is what create would hold (the estimate plus `hold_buffer_bps`). It is labelled an estimate: the charge is the tokens the provider reports as trained.

POST /v1/fine_tuning/estimate · 200

```
{"object": "fine_tuning.estimate", "label": "estimate", "model": "…",
 "method": {"type": "supervised", "training_type": "lora"},
 "training_tokens_per_epoch": 53000, "validation_tokens": 0, "n_epochs": 3, "n_evals": 0,
 "billable_tokens": 159000, "price_per_million_tokens_nanos": 480000000, "minimum_charge_nanos": 4000000000,
 "estimate_nanos": 4000000000, "hold_nanos": 4800000000, "hold_buffer_bps": 2000,
 "available_nanos": 25000000000, "sufficient_funds": true, "currency": "USD",
 "hosting_note": "Training is a one-off charge. Hosting the result on a dedicated endpoint is billed per hour while it runs and is usually the larger cost.",
 "data_destination": "Together AI"}
```

## Models

A succeeded job creates a model. `GET /v1/models` lists it as `{id: "ft:…", object: "model", created, owned_by: "<org_id>"}`, for your organization only. `GET /v1/fine_tuning/models` and `GET /v1/fine_tuning/models/{model_id}` (`ftm_…` or `ft:…`) return the full object. `PATCH` with `{"name": "…"}` renames it (`409 name_taken`); `DELETE` archives it, refused with `409 endpoint_running` while its endpoint is starting or running.

GET /v1/fine_tuning/models/{model_id} · 200

```
{"id": "ftm_…", "object": "fine_tuning.model", "model": "ft:llama-3-1-8b:support-bot:a1b2c3d4", "name": "support-bot",
 "base_model": "meta-llama/…", "base_model_id": "ftb_…", "job_id": "ftj_…", "state": "ready",
 "created_at": 1760003000, "endpoint": {"id": "fte_…", "state": "running"}}
```

## Endpoints

`POST /v1/fine_tuning/endpoints` creates the endpoint and, unless `start` is false, starts it. One live endpoint per model (`409 endpoint_exists`). Starting needs at least one hour of the endpoint's price available (`402 insufficient_funds`). `budget` is in nanos.

Request body

```
{"model": "ft:llama-3-1-8b:support-bot:a1b2c3d4", "hardware": "fth_…", "min_replicas": 1, "max_replicas": 1,
 "auto_stop_idle_minutes": 15, "max_hours": 8, "budget": 40000000000, "start": true}
```

POST /v1/fine_tuning/endpoints · 200

```
{"id": "fte_…", "object": "fine_tuning.endpoint", "model": "ft:llama-3-1-8b:support-bot:a1b2c3d4", "model_id": "ftm_…",
 "hardware": "fth_…", "hardware_name": "1x_nvidia_h100_80gb_sxm", "min_replicas": 1, "max_replicas": 1,
 "state": "running", "stop_reason": null,
 "auto_stop_idle_minutes": 15, "max_hours": 8, "budget_nanos": 40000000000,
 "price_per_hour_nanos": 3990000000, "currency": "USD",
 "started_at": 1760003100, "stopped_at": null, "last_used_at": null, "last_billed_until": 1760003400,
 "run_cost_nanos": 332500000, "total_cost_nanos": 332500000, "accrued_unbilled_nanos": 0,
 "hold_nanos": 3990000000, "created_at": 1760003100}
```

`price_per_hour_nanos` is the final price per hour for the whole endpoint (the hardware's price × `max_replicas`), fixed when it starts. `run_cost_nanos` is this run so far, billed plus accrued; `total_cost_nanos` covers the endpoint's life. `stop_reason` is `user`, `idle`, `max_hours`, `budget`, `wallet_empty`, `staff`, `provider` or `failed`.

`PATCH /v1/fine_tuning/endpoints/{endpoint_id}` with any of `auto_stop_idle_minutes`, `max_hours` and `budget` changes auto-stop. `POST …/start` starts it (`409 endpoint_running` when it already runs); `POST …/stop` bills up to now and releases the hold; `DELETE` stops it if needed and leaves it `deleted`. `GET /v1/fine_tuning/endpoints?state=` lists them.

## Hardware and base models

`GET /v1/fine_tuning/hardware?model=` takes an `ft:…` id, an `ftm_…` id or a base model and lists the hardware it can run on, each with its final price per replica-hour. Hardware without a published price is not listed. `GET /v1/fine_tuning/base_models` lists the supported base models: each method carries its final price per 1M training tokens and the minimum charge, and `available: false` when no price is published for it yet.

GET /v1/fine_tuning/hardware?model=… · one entry

```
{"id": "fth_…", "object": "fine_tuning.hardware", "hardware": "1x_nvidia_h100_80gb_sxm",
 "gpu_type": "h100-80gb", "gpu_count": 1, "gpu_memory_gb": 80, "available": true,
 "price_per_hour_nanos": 3990000000, "currency": "USD"}
```

GET /v1/fine_tuning/base_models · one entry

```
{"id": "ftb_…", "object": "fine_tuning.base_model", "model": "meta-llama/Meta-Llama-3.1-8B-Instruct-Reference",
 "display_name": "Llama 3.1 8B Instruct", "size_class": "up_to_16b", "max_context": 8192,
 "catalog_model": "meta-llama/llama-3.1-8b-instruct",
 "methods": [{"method": "lora", "objective": "sft", "price_per_million_tokens_nanos": 480000000,
              "minimum_charge_nanos": 4000000000, "available": true}],
 "currency": "USD"}
```

## Errors

Errors use the standard envelope, `{"error": {"type", "code", "message", "param"}}`. The codes specific to fine-tuning:

| HTTP | Code | Meaning |
| --- | --- | --- |
| 400 | invalid_training_file · invalid_validation_file · invalid_file_format | The file failed validation, or is not a fine-tune file in a usable format. |
| 400 | file_too_large | Over 100 MB. |
| 400 | method_not_supported · format_method_mismatch | The base model does not offer the method, or the file format does not fit it (DPO needs preference; SFT chat or completion). |
| 400 | invalid_hyperparameters | A value is out of range or does not apply to the method. |
| 400 | approval_required | An approval policy covers the job or endpoint; approval_request_id names the request. Re-send with X-Yelno-Approval. |
| 402 | insufficient_funds | The job's hold, or one hour of the endpoint's price, cannot be placed. |
| 403 | fine_tuning_disabled | allow_fine_tuning is off for your organization. |
| 403 | zdr_required | Your organization requires zero data retention, which the training provider's terms do not meet. |
| 404 | not_found · model_not_found | Unknown id, or another organization's: never a hint that it exists. |
| 409 | model_not_deployed | Inference on an ft: model with no running endpoint. |
| 409 | endpoint_exists · endpoint_running | The model already has a live endpoint; the endpoint is already running; or archiving a model whose endpoint runs. |
| 409 | job_not_cancellable · file_in_use · name_taken | Cancelling a finished job; deleting a file an open job uses; renaming to a name already in use. |
| 409 | idempotency_key_reused | The Idempotency-Key was used with a different body. |
| 409 | price_unavailable | No final price is published for that model, method or hardware yet. |
| 503 | fine_tuning_unavailable · provider_unavailable | Fine-tuning is paused platform-wide, or the training provider is not available. |

Where the data goes. Training and validation files are sent to Together AI, and training runs on Together AI's infrastructure. They are deleted there after the job unless you set `keep_provider_files`. No accuracy, quality, availability or SLA is promised for fine-tuned models or their endpoints.

Source: https://yelno.ai/docs/fine-tuning-api
