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.
The entity model
Section titled “The entity model”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.
Authentication
Section titled “Authentication”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
How the browser handoff works
Section titled “How the browser handoff works”distil signup and distil auth are the same command with a different destination page. Each one:
- Starts an HTTP server on
127.0.0.1, on a random free port, carrying a one-timestatevalue. - Opens
https://app.distillabs.ai/sign-up(or/sign-in) with that port as the return address. - Waits for the browser to post a refresh token back, checks the
statematches, 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.
Signing in without a browser
Section titled “Signing in without a browser”--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.
Which commands speak JSON
Section titled “Which commands speak JSON”--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.
Supplying files
Section titled “Supplying files”--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.
Credits
Section titled “Credits”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.
Overrides
Section titled “Overrides”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
Monitoring jobs
Section titled “Monitoring jobs”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.
Recovering a lost id
Section titled “Recovering a lost id”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.
Fetching metrics
Section titled “Fetching metrics”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.
Reading a training dataset
Section titled “Reading a training dataset”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.
Downloads
Section titled “Downloads”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.
Deployment
Section titled “Deployment”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.
Other commands
Section titled “Other commands”distil --version # installed version
distil update # replace the binary in place
distil docs # open these docs
distil credits-balance # metered routes and remaining calls
Deprecated: the model command group
Section titled “Deprecated: the model command group”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.