---
title: "Create Training Job"
method: POST
path: "/v1/training/jobs"
tags: ["training-jobs"]
---

# Create Training Job

`POST /v1/training/jobs`

## Request

- Base URL: `https://api.fastino.ai`
- URL: `https://api.fastino.ai/v1/training/jobs`
- Auth: one of:
  - API key in header `X-API-Key`
  - HTTP bearer
- TypeScript: <https://skmtc.dev/fastino-ai/apis/pioneer-inference-api/docs/v1/training/jobs/post.ts> (a function with typed inputs; its response is checked against the schema)

Create a new training job.

## Headers

- `Idempotency-Key` string, nullable: Client-generated key (a UUID is ideal) that makes the create safe to retry. A repeat with the same key and body returns the original job instead of launching another; the same key with a different body is rejected with 422.

## Request body

- TrainingJobCreate: Request to create a training job.
  - `auto_data_sizing` boolean, nullable: GLiNER only. Opt-in: when true, downsample each training dataset to min(max_samples_per_dataset, max(min_samples_per_dataset, samples_per_label * num_labels)). When omitted or false, the full provided dataset is used (no silent downsampling). Defaults to false in the Modal container.
  - `base_model` string, required: Trainable model ID from GET /v1/base-models?supports_training=true (e.g. 'fastino/gliner2-base-v1').
  - `batch_size` integer: Training batch size (1 to 256). Prefer omitting this field so the training service applies the catalog default for ``base_model``. GLiNER encoder batches that do not fit the training GPU are reduced to the largest that fits. For decoder models, explicit values equal to this Field default (4) that exceed the model's safe maximum are treated as legacy unset clients and clamped to the catalog default; any other oversize value is rejected.
  - `datasets` DatasetReference[], required: Datasets to train on (supports multi-dataset training)
    - `name` string, required: Dataset name
    - `version` string, nullable: Version (latest if omitted)
  - `early_stopping_min_delta` number: Minimum validation loss improvement to count as progress. Prevents early stopping from triggering on noise.
  - `early_stopping_patience` integer: Validation epochs without improvement before stopping. Requires validation_data_percentage > 0. Set to 0 to disable.
  - `encoder_learning_rate` number, nullable: GLiNER only: learning rate applied to encoder parameters. When omitted, falls back to `learning_rate`.
  - `eval_steps` integer, nullable: GLiNER2 (encoder) only: run periodic mid-training validation every N optimizer steps, in requested-batch units like save_steps (scaled by the same ratio when GPU admission reduces the batch). When omitted, validation runs only at each epoch end — the standard cadence. Periodic evals are for cost/visibility (loss curve, W&B): they do NOT select the deployed checkpoint. Checkpoint selection uses the full-split epoch-end and final validation. Checkpoint saving still follows save_steps independently.
  - `gradient_accumulation_steps` integer, nullable: Accumulate gradients over N mini-batches before each optimizer step. Effective batch size = batch_size * N. Honoured by GLiNER and dense decoder LoRA training; when omitted, dense LoRA applies the per-model catalog default (8 for the H200 Nemotron 3.5 profiles, whose per-device batch is pinned to 1).
  - `learning_rate` number, nullable: Peak learning rate for AdamW. When omitted, the trainer's default applies — the per-model catalog rate (2e-4) for decoder LoRA on Modal, 2e-5 elsewhere. Pinning the old 2e-5 default on a decoder LoRA run under-trains the adapter by an order of magnitude.
  - `lora_alpha` integer, nullable: LoRA alpha. When omitted, the trainer's default applies: the per-model catalog alpha for decoder LoRA on Modal, or 32 elsewhere.
  - `lora_dropout` number, nullable: LoRA dropout. When omitted, the trainer's default applies: the per-model catalog dropout for decoder LoRA on Modal, or 0.1 elsewhere.
  - `lora_r` integer, nullable: LoRA rank (1 to 256). When omitted, the trainer's default applies: the per-model catalog rank for decoder LoRA on Modal (including 32 for the qualified Nemotron 3.5 Lightning profile), or 16 elsewhere.
  - `lr_scheduler_type` string: LR decay schedule after warmup: 'constant', 'linear', or 'cosine'. Decoder training defaults to cosine; GLiNER defaults to linear unless this is set explicitly; encoder jobs accept only the three named values.
  - `max_eval_samples` integer, nullable: GLiNER2 (encoder) only: per-dataset cap on the periodic mid-training validation set (deterministic first-N subsample). When omitted, no cap. Periodic evals are for cost/visibility only and never select the deployed checkpoint; selection uses the full-split epoch-end and final validation, which ignore this cap.
  - `max_gold_per_query` integer, nullable: GLiNER2 only: maximum gold spans retained for each query. Omit to use GLiNER2's default of 32.
  - `max_length` integer, nullable: GLiNER only: maximum tokens kept from the start of each training row. Omit to use the model's context window, shortened when that window does not fit the L4 batch.
  - `max_samples_per_dataset` integer, nullable: GLiNER only: upper bound for auto-sized dataset cap.
  - `min_samples_per_dataset` integer, nullable: GLiNER only: lower bound for auto-sized dataset cap.
  - `min_training_steps` integer, nullable: GLiNER only: minimum number of optimizer steps; raises epoch count if the provided `nr_epochs` would yield fewer steps.
  - `model_name` string, required: User-friendly name for the trained model. Must not contain '<', '>', or control characters.
  - `nr_epochs` integer: Maximum training epochs (1 to 10000). With early stopping enabled, training typically terminates well before this ceiling.
  - `on_capacity_exceeded` 'raise' | 'truncate_with_warning' | 'skip_sample', nullable: GLiNER2 only: behavior when a query has more gold spans than max_gold_per_query. 'raise' fails the job without dropping gold; 'truncate_with_warning' keeps the first spans and drops the rest; 'skip_sample' keeps the row in the batch but removes all of its extraction supervision. Omit to use 'raise'.
  - `profile_training` boolean: Enable structured training profiling for this run and persist a training_profile.json artifact.
  - `project_id` string, nullable: Project ID to associate with this training job. When omitted, the job is anchored to the caller's auto-managed "Default" project so it is always deployable and fleet-eligible.
  - `provider_name` string, nullable: Pin training to a specific provider (e.g. 'modal'). Bypasses automatic provider selection.
  - `samples_per_label` integer, nullable: GLiNER only: scaling factor used when computing the auto-sized per-dataset cap.
  - `save_steps` integer: Requested-batch checkpoint interval: optimizer steps at the requested batch size (sample-normalized). The stored job value stays this requested interval. When GLiNER GPU admission reduces the batch, the worker interval is scaled by ceil(save_steps * requested_batch / applied_batch) so the same number of samples pass between saves. Dense-LoRA uses this value as the optimizer-step interval.
  - `seed` integer, nullable: Optional reproducibility seed for Modal decoder or GLiNER2 (encoder) training. Requests must pin provider_name to 'modal'; Fireworks and other unsupported architectures reject this field. When omitted, encoder and decoder jobs share the same trainer default (3407). Decoder contract: A pinned seed governs LoRA initialisation and the trainer's own RNG (dataloader shuffle order and dropout). It does not select the train/validation split: that partition uses a dedicated split seed so two runs that differ only in `seed` are scored on the same held-out rows. It does not make runs bit-identical: GPU reduction order stays non-deterministic, so metrics can differ between otherwise identical runs. Across six observed same-config decoder pairs, final validation loss agreed to within 6.8% relative and two pairs agreed exactly. Treat that as an observed envelope from production history, not a guaranteed bound. Encoder contract: A pinned seed governs dataset shuffle and auto-sizing downsample order, and the trainer's own weight-initialisation and dropout RNG. It does not select the train/validation split on a Brain-dispatched run: that partition is a fixed left-to-right split derived from validation_data_percentage (validation is the tail), independent of seed, so two runs that differ only in `seed` are scored on the same held-out rows. Runs are seeded but not bit-identical: GPU reduction order stays non-deterministic (cuBLAS GEMM split-k, and the embedding-backward scatter_add/index_add path, which is not forced deterministic), so metrics can still differ between identically-configured runs. Before encoder seed control existed, three identically-configured launches measured classification macro-F1 ranging 0.38–0.63 — treat pinning a seed as removing one real, measured source of that noise, not as a guaranteed bound on the rest. Every GLiNER2 job unconditionally pins cuDNN's own algorithm selection (the same flags GLiNER2's bundled Trainer sets) -- this is a fixed container default, not a per-request knob. The cuDNN pin currently costs nothing and changes nothing on this backbone: cuDNN governs convolution/pooling/RNN kernels, and this encoder's DeBERTa-v2 backbone has none, so pinning it has nothing to pin there; the throughput trade-off only materializes if a future backbone adds conv/pooling/RNN layers. torch deterministic algorithms are not enabled: they would roughly halve training throughput without making runs bit-identical.
  - `task_learning_rate` number, nullable: GLiNER only: learning rate applied to task-head parameters. When omitted, falls back to `learning_rate`.
  - `training_algorithm` 'sft': Training algorithm. Only 'sft' is accepted: decoder SFT, DPO and GRPO train step by step with the training job routes under /v1/training/jobs (see https://docs.fastino.ai/training/quickstart).
  - `training_type` 'full' | 'lora': Training type: 'full' or 'lora'
  - `validation_data_percentage` number: Fraction of data held out for validation, 0.0 to 0.9 in steps of 0.01 (e.g. 0.15). Higher values leave too little data to train on.
  - `wandb_api_key` string, nullable: Optional W&B API key for logging
  - `warmup_ratio` number, nullable: Fraction of total training steps for linear LR warmup. Ignored when warmup_steps is set (decoders; encoder jobs reject warmup_steps). When omitted, the trainer's default applies — the per-model catalog warmup (0.03) for decoder LoRA on Modal, 0.1 for GLiNER, none elsewhere.
  - `warmup_steps` integer, nullable: Absolute number of linear LR warmup steps. When set, takes priority over warmup_ratio. Decoder training only: GLiNER encoder jobs reject it (use warmup_ratio).
  - `weight_decay` number: AdamW weight decay (L2 penalty). 0 disables weight decay. Honoured by Modal decoder/dense-LoRA training paths. Not forwarded to GLiNER2 Modal training, which uses its own container default.

## Response `200`

Successful Response

- TrainingJobResponse: Training job response model
  - `artifact_ready` boolean, nullable: Whether an artifact location is recorded, so there is something to serve.
  - `base_model` string, required: The catalog base model the adapter was trained on (for an `interactive_checkpoint` model, the base of the interactive job it came from).
  - `batch_size` integer, nullable, required: Training batch size. Null for an `interactive_checkpoint` model.
  - `completed_at` string, nullable
  - `created_at` string, required
  - `current_epoch` integer, nullable: Epoch currently in progress (1-indexed). Updated live during training.
  - `datasets` DatasetReference[], required
    - `name` string, required: Dataset name
    - `version` string, nullable: Version (latest if omitted)
  - `deployability_reason` string, nullable: Why the job is not deployable (e.g. 'job_incomplete', 'missing_artifact', 'provider_incompatible', 'adapter_inference_unavailable'). Null when deployable.
  - `deployment_status` string, nullable, required: Deprecated. Always returns None -- deployment_status no longer exists. Kept for backward compat with clients that read this field.
  - `error_message` string, nullable
  - `example` string, nullable: Sample text to pre-load into inference input
  - `experiment_id` string, nullable: Experiment associated with this training job, when available. Null means no associated experiment was recorded.
  - `hub_model_id` string, nullable: HuggingFace repo id (e.g. 'username/model-name') set only after a successful push_training_job_to_hub call. This is the sole source of truth for whether a checkpoint has been pushed to the Hub -- it does not mean the repo is reachable by anyone other than the pusher: see hub_model_private for that. trained_model_path is an internal storage key and must never be parsed to infer a Hub repo id.
  - `hub_model_private` boolean, nullable: Whether the pushed Hub repository is private, as recorded at push time. Null means the job was not pushed or its repository visibility was not recorded.
  - `id` string, required
  - `instance_type` string, nullable
  - `is_deployable` boolean, nullable: Whether this job passes server-side deployability validation for its own project. Authoritative -- the same check the deployment endpoints enforce.
  - `is_terminal_status` boolean, nullable: Whether this status is terminal for polling loops
  - `job_reference` string, nullable
  - `labels` string[], nullable: Merged labels from training datasets (entity types for NER, class labels for classification)
  - `learning_rate` number, nullable, required: Learning rate. Null for an `interactive_checkpoint` model.
  - `metrics` object, nullable: Training and evaluation metrics dictionary. Contains final_training_loss, final_validation_loss, best_validation_loss from training logs, and optional evaluation metrics (f1_score, precision_score, recall_score, accuracy) if an evaluation has been run.
  - `model_auto_selected` boolean, nullable
  - `model_kind` 'lora' | 'full', nullable: Normalized fine-tune kind: 'lora' for adapters (lora/qlora) or 'full' for merged weights. Null when the persisted training type is unrecognised -- clients must not claim a kind in that case.
  - `model_name` string, nullable
  - `model_selection_reason` string, nullable
  - `normalized_status` string, nullable: Canonical status alias for compatibility handling (requested, running, complete, deployed, failed, cancelled)
  - `notices` TrainingNotice[]: Advisories to show alongside this job, such as the dedicated-endpoint serving terms for full fine-tunes.
    - `code` string, required: Stable machine-readable notice code.
    - `message` string, required: Customer-facing notice text.
  - `nr_epochs` integer, nullable, required: Training epochs. Null for an `interactive_checkpoint` model.
  - `progress_percent` integer, nullable: Overall training completion percentage (0-100). Updated live during training.
  - `project_id` string, nullable: Project ID this training job is associated with
  - `provider_deployments` object, nullable: Provider-specific deployment metadata written by the training monitor, keyed by provider: {"modal": {...}}.
  - `provider_name` string, nullable: Training provider that handled this job (e.g. 'modal'). Jobs predating a provider removal carry an 'archived_<provider>' label.
  - `provider_ready` boolean, nullable: Whether a provider is already serving this artifact. False is not a deployment blocker: promotion provisions or re-warms a provider.
  - `resolved_recipe` object, nullable: Immutable snapshot of what this job actually trained with, taken at dispatch: LoRA rank/alpha/dropout, learning rate, warmup ratio, gradient accumulation, packing, precision, attention backend, reasoning parser, container image, runtime profile, max sequence length, and seed, for every seed-capable provider/model. Null for jobs dispatched before the snapshot existed, and for providers that resolve no catalog recipe -- except Modal encoder and Modal RL strategies, which have no dense-LoRA catalog recipe but still return a non-null (seed) snapshot, since seed has no other persisted column. Read this rather than re-deriving from the catalog: the catalog reports what a job dispatched *today* would get, which is a different question.
  - `root_job_id` string, nullable: ID of the original/root training job this version derives from
  - `seed` integer, nullable: Effective reproducibility seed for Modal decoder or GLiNER2 (encoder) training. Null for Fireworks, unknown, and other providers/architectures that cannot honor this contract, and also null for a legacy Modal decoder/encoder job created before seed provenance was recorded, because its seed is unknown, and for an `interactive_checkpoint` model, which this job did not train. A non-null value is always a genuinely recorded seed. Decoder contract: A pinned seed governs LoRA initialisation and the trainer's own RNG (dataloader shuffle order and dropout). It does not select the train/validation split: that partition uses a dedicated split seed so two runs that differ only in `seed` are scored on the same held-out rows. It does not make runs bit-identical: GPU reduction order stays non-deterministic, so metrics can differ between otherwise identical runs. Across six observed same-config decoder pairs, final validation loss agreed to within 6.8% relative and two pairs agreed exactly. Treat that as an observed envelope from production history, not a guaranteed bound. Encoder contract: A pinned seed governs dataset shuffle and auto-sizing downsample order, and the trainer's own weight-initialisation and dropout RNG. It does not select the train/validation split on a Brain-dispatched run: that partition is a fixed left-to-right split derived from validation_data_percentage (validation is the tail), independent of seed, so two runs that differ only in `seed` are scored on the same held-out rows. Runs are seeded but not bit-identical: GPU reduction order stays non-deterministic (cuBLAS GEMM split-k, and the embedding-backward scatter_add/index_add path, which is not forced deterministic), so metrics can still differ between identically-configured runs. Before encoder seed control existed, three identically-configured launches measured classification macro-F1 ranging 0.38–0.63 — treat pinning a seed as removing one real, measured source of that noise, not as a guaranteed bound on the rest. Every GLiNER2 job unconditionally pins cuDNN's own algorithm selection (the same flags GLiNER2's bundled Trainer sets) -- this is a fixed container default, not a per-request knob. The cuDNN pin currently costs nothing and changes nothing on this backbone: cuDNN governs convolution/pooling/RNN kernels, and this encoder's DeBERTa-v2 backbone has none, so pinning it has nothing to pin there; the throughput trade-off only materializes if a future backbone adds conv/pooling/RNN layers. torch deterministic algorithms are not enabled: they would roughly halve training throughput without making runs bit-identical.
  - `source` 'config_training' | 'interactive_checkpoint': How the model came to exist: `config_training` for a job Fastino trained from data and a config, `interactive_checkpoint` for a sampler checkpoint of an interactive job served as a model (`POST /v1/training/jobs/{job_id}/checkpoints/sampler_weights/{name}/deploy`). An `interactive_checkpoint` model ran no training of its own, so it lists no datasets and its training settings are null.
  - `started_at` string, nullable
  - `status` string, required
  - `task_type` string, nullable: Task type derived from training datasets: 'ner', 'classification', 'custom', or 'decoder'
  - `trained_model_path` string, nullable
  - `training_type` string, nullable: Raw training method as persisted: 'lora', 'qlora', or 'full'.
  - `type` 'config': A config training job (you give data and a config, Fastino runs the loop). ``/v1/trained-models`` also lists interactive jobs (``type: interactive``).
  - `updated_at` string, required
  - `user_id` string, required
  - `validation_data_percentage` number, nullable, required: Share of the data held out for validation (0-1). Null for an `interactive_checkpoint` model.
  - `version_number` string, nullable: Version number for this training job (e.g., '1', '2', '3')

## Other responses

- `409`: ``duplicate_training_submission``: an identical job is live. ``idempotency_key_job_deleted``: the job this key created was deleted. A referenced dataset may also retire before insertion.
- `422`: Validation Error

## Changes

- **2026-10-15** `5efbc8b70225`: 4 breaking, 1 info
  - response property `batch_size` list-of-types was widened by adding types `null` to media type `application/json` of response `200`
  - response property `learning_rate` list-of-types was widened by adding types `null` to media type `application/json` of response `200`
  - response property `nr_epochs` list-of-types was widened by adding types `null` to media type `application/json` of response `200`
  - response property `validation_data_percentage` list-of-types was widened by adding types `null` to media type `application/json` of response `200`
  - …1 more
- **2026-10-15** `b48a0d1d78d5`: 4 breaking, 4 warning
  - the `training_algorithm` request property const value `sft` was added
  - removed the enum value `dpo` of the request property `training_algorithm`
  - removed the enum value `grpo` of the request property `training_algorithm`
  - removed the enum value `sft` of the request property `training_algorithm`
  - …4 more
- **2026-10-13** `5ebfd9fe8f46`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/fastino-ai/apis/pioneer-inference-api/changes/v1/training/jobs/post.md)

---

[API](https://skmtc.dev/fastino-ai/apis/pioneer-inference-api.md) - [All operations](https://skmtc.dev/fastino-ai/apis/pioneer-inference-api/llms.txt) - [OpenAPI document](https://skmtc.dev/fastino-ai/apis/pioneer-inference-api/revisions/81465e18c1d2?raw)
