Skip to content

CLI reference

The distil CLI drives the whole platform. Install it from Install the CLI.

This page describes 0.25.0. Check your version with distil --version before trusting a flag, and distil update to move to the current one.

Every stage is an entity created by one command, and every entity is created either from local files or by running a job over the entity before it.

Stage Entity Create Read
Trace processing PreparedTraces → SeedDataset distil traces upload --data <dir>, then distil seed-dataset create-from-traces <traces-id> distil seed-dataset {status,logs,metrics,download,download-metadata,download-traces-predictions}
Job input SeedDataset distil seed-dataset create --data <dir> distil seed-dataset {status,download,download-metadata}
Teacher evaluation TeacherEvaluation distil teacher-evaluation create-from-seed-dataset <seed-dataset-id> distil teacher-evaluation {status,logs,metrics,download-metadata,download-predictions}
Synthetic data generation TrainingDataset distil training-dataset create-from-seed-dataset <seed-dataset-id> distil training-dataset {status,logs,metrics,sample,download,download-metadata}
Model training SLM distil slm create-from-training-dataset <training-dataset-id> distil slm {status,logs,metrics,download,download-metadata,download-predictions}
Deployment Deployment distil deployment create-from-slm <slm-id> distil deployment {status,endpoint,logs}, distil deployment delete

upload and uploads are aliases of seed-dataset, and create-from-upload of create-from-seed-dataset. Write the seed-dataset spelling.

Each group also answers to its plural (slms, deployments, teacher-evaluations, training-datasets), and list answers to ls.

You can create an account and sign in without leaving the terminal.

distil signup                                        # create an account; aliases: register, join
distil auth                                          # sign in; alias: login
distil auth --email <email> --password <password>    # sign in without a browser
distil whoami                                        # the current user
distil logout

distil signup and distil auth are the same command with a different destination page. Each one:

  1. Starts an HTTP server on 127.0.0.1, on a random free port, carrying a one-time state value.
  2. Opens https://app.distillabs.ai/sign-up (or /sign-in) with that port as the return address.
  3. Waits for the browser to post a refresh token back, checks the state matches, writes the token to the session file and stops the server.

Both finish by printing Logged in as <email>, so distil signup leaves you signed in and needs no distil auth after it. If the browser doesn’t open, the CLI prints the URL for you to paste. The wait times out after 20 minutes with Timed out waiting for browser authorization.

--email and --password authenticate directly, with no browser and no local server. This is the form for CI and remote shells.

distil auth --email you@example.com --password "$DISTIL_PASSWORD"

There is no headless signup. Accounts are only created through the browser flow, so on a machine without one, sign up elsewhere and then authenticate here with --email and --password.

The CLI holds its own session in ~/.config/distillabs/token (under $XDG_CONFIG_HOME when set) and refreshes it, so a run spanning hours needs no second login.

--output json is registered on the read commands (list, show, status, logs, metrics, sample, endpoint, plus whoami and credits-balance) and on the job creates that name a parent id: teacher-evaluation create-from-seed-dataset, training-dataset create-from-seed-dataset, slm create-from-training-dataset and deployment create-from-slm.

It isn’t registered on the commands that read local files (traces upload, seed-dataset create, training-dataset create, slm create), on seed-dataset create-from-traces, or on any download command. Passing it there fails with No flag registered for --output and creates nothing.

Without --output json a read prints a panel for a human reader and suggests the next command underneath. Parse the JSON form in scripts.

--data <dir> names a directory and the CLI reads the files out of it by name.

Command Required in --data <dir> Optional
distil traces upload traces.jsonl, config.yaml, job_description.json test.jsonl
distil seed-dataset create train.jsonl, test.jsonl, config.yaml, job_description.json unstructured.jsonl
distil training-dataset create train.jsonl, test.jsonl, config.yaml, job_description.json -
distil slm create model.tar, config.yaml -

Optional here means optional to the CLI, which won’t stop you before the upload. Validation is what enforces the per-task rules: question-answering-open-book and question-answering-closed-book both need unstructured.jsonl, and leaving it out fails the job rather than the command.

config.yml is accepted for config.yaml. The per-file flags (--traces, --train, --test, --unstructured, --config, --job-description, --model) replace one path each, and combining them with --data is refused rather than merged. A file missing from the directory is named before anything uploads.

The download commands write these same names, so a downloaded directory feeds straight back into the matching create --data. That round trip is how a change to the data is made, since no override can reach it.

distil credits-balance
# prepared_traces_post                       100
# seed_datasets_post                         100
# teacher_evaluations_post                    20
# training_datasets_from_seed_datasets_post    5
# slms_from_training_datasets_post             2

The read is free and answers at zero balance. --output json returns {"balances": {…}} under the same keys.

Only metered routes appear. A route the platform never charges for is absent rather than reported as unlimited, so a missing key isn’t a zero, and an account with nothing metered reads No metered endpoints. How metering works: How the platform works.

The four job creates (seed-dataset create-from-traces, teacher-evaluation create-from-seed-dataset, training-dataset create-from-seed-dataset and slm create-from-training-dataset) each accept --config <file> (-c) and --job-description <file>. Both are optional and independent, and omitting one means the job inherits the parent’s.

Each file replaces the parent’s whole. The CLI doesn’t merge, so whatever the file omits takes a library default rather than the parent’s value. A config carrying a complete base but no synthgen or tuning section is accepted, and those sections silently revert. An override is therefore always read-edit-resend, never hand-written:

distil <group> download-metadata -d <dir> <id>  →  edit one field  →  pass to --config

The full rule, with a worked example of what reverts: How the platform works.

download-metadata writes the entity’s config.yaml and job_description.json and costs no credits. What it writes is the fully expanded config, with every default materialised, keys sorted and comments dropped, which is exactly why editing it and sending it back preserves everything you didn’t touch.

distil seed-dataset download-metadata -d ./iter-2 <seed-dataset-id>
# edit ./iter-2/config.yaml
distil training-dataset create-from-seed-dataset --output json \
  --config ./iter-2/config.yaml <seed-dataset-id> | jq -r .id

Uploaded traces are the exception: they echo the config they were staged with, comments intact and nothing expanded.

Because the read-back config is complete, checking what a submission actually ran with is a diff rather than an audit:

distil training-dataset download-metadata -d ./check <training-dataset-id>
diff ./iter-2/config.yaml ./check/config.yaml
distil <group> status --output json <id> | jq -r .status
distil <group> logs   --output json <id> | jq -r .logs

Poll every 20 seconds or so. Status values: How the platform works.

Read the status field, not the exit code. A status command that reaches the platform exits 0 whatever the job did. Deployments answer {"deployment_status": …, "endpoint_status": …} and have no status field at all.

Typical timeouts to plan for:

Stage Command group Typical timeout
Trace processing seed-dataset 45 min
Teacher evaluation teacher-evaluation 30 min
Synthetic data generation training-dataset 90 min
Model training slm 90 min
Deployment deployment 40 min

logs returns the whole job log as one string and fills while the job runs, so it’s also how a long job is watched. When a job fails, search the log for the first error rather than reading the last one, since a crash often unwinds into a second, unrelated error.

distil <group> list --output json

Returns one object per entity, newest first, carrying id, created_at and the parent’s id under its own key (seed_dataset_id, training_dataset_id, slm_id, and so on). It’s how a lost id is recovered and how a child is traced back to its parent. It carries no status, so checking state costs one status call per id.

distil teacher-evaluation metrics --output json <id> | jq .teacher_performance
# {"rouge": 1, "binary": 0.82, "llm-as-a-judge": 1, "llm-as-a-judge-reference-free": 1}

distil slm metrics --output json <id> \
  | jq '{base: .base_model_performance, tuned: .tuned_model_performance}'

distil teacher-evaluation download-predictions <id>
distil slm download-predictions <id>

What the numbers mean: Metrics.

metrics --output json also carries the *_download_url the download command uses. It’s presigned and expires after an hour, so take it directly only to hand the data to another program.

distil training-dataset sample --output json <id> > sample.json   # free
distil training-dataset download -d ./data <id>                   # costs a credit
distil training-dataset metrics --output json <id> | jq .train_data_size_bytes

sample returns at most 128 train rows, drawn deterministically from the first 384, and never test rows. metrics reports bytes rather than rows, so for a full run divide the byte count by the mean row size in the sample and treat the result as an estimate.

Every command that writes a directory takes --destination <dir> (-d) and otherwise names one after the entity: <id>-traces, <id>-data, <id>-slm, <id>-metadata. The *-predictions commands write a single file and take --file-name instead. Downloads overwrite what’s already there.

distil slm download --destination ./model <slm-id>          # model.tar + config.yaml
distil slm download-metadata --destination ./model <slm-id> # config, job description, client

slm download writes a tarball of several gigabytes, about 1.2 GB for a Qwen3-0.6B run, and checks free disk space before it starts. slm download-metadata writes the model’s inference client alongside its config, a few kilobytes instead.

Metrics and data downloads answer only once that entity’s own job reaches JOB_SUCCESS. Until then a metrics field is null, and a download names the reason and exits 1 rather than writing an empty file: SLM is still training, SLM training failed, The model is not available for this SLM. A download that writes nothing always fails loudly, so the exit code is worth trusting.

distil deployment create-from-slm --output json <slm-id> | jq -r .id
distil deployment status --output json <id> | jq -r .deployment_status
distil deployment endpoint --output json <id>    # {"url": ..., "api_key": ...}
distil deployment delete <id>                    # alias: shutdown

The deployment serves the model with vLLM, and the job doesn’t return until vLLM answers, so JOB_SUCCESS means serving rather than merely scheduled. Before that, endpoint answers {"url": null, "api_key": null} and exits 0 either way, so poll the status rather than probing the endpoint.

A deployment is a session, not a permanent endpoint. It stops after six hours, or after one hour with no traffic, and it bills until that idle timeout, so delete it when you’re finished. It can’t be restarted, and a new deployment carries a new URL and a new key. After the delete, deployment_status stays JOB_SUCCESS, and endpoint_status going to stopped is what confirms it’s down.

The API key protects the endpoint. The tunnel has no authentication of its own and the URL is open to all.

See distil labs inference for querying it.

distil --version          # installed version
distil update             # replace the binary in place
distil docs               # open these docs
distil credits-balance    # metered routes and remaining calls

distil model … is deprecated and hidden. Use the entity chain at the top of this page instead. Every distil model subcommand prints a pointer to its equivalent.