CSV Import
CSV import lets you create or update many jobs in one pass, from a spreadsheet, instead of going through the job form one job at a time. It’s the same create/update path as the admin UI and the API — every imported row runs through the same validation and defaults, so an imported job can’t bypass a rule a hand-authored one follows.
In the admin UI, open the Jobs page and click Import CSV, next to New job.
Getting the template
Section titled “Getting the template”curl https://<your-host>/api/jobs/import/template -o jobs-template.csvThe Import CSV modal links to the same download. The template’s header row covers every settable job field — name, scheduling, HTTP or script config, loop, retry, pooling, and so on — plus one filled-in example row to copy from. See Job Fields for what each column means and how it’s validated; the CSV columns map directly to those fields.
Volumes in a CSV
Section titled “Volumes in a CSV”The capabilities_json column holds a script job’s volume declaration as JSON, for example
{"volumes":[{"name":"reports","mode":"rw"}]}. Leave it blank for no declaration. An import
row runs the same validation as the API: the volumes must exist, the mode cannot exceed a
volume’s max_mode, and a row for an HTTP job with a non-empty value is reported as a per-row
error. See Volumes. The column is part of the downloaded
template.
Uploading
Section titled “Uploading”curl -X POST https://<your-host>/api/jobs/import \ -F on_collision=merge \ -F merge_priority=sourceThe response streams as newline-delimited JSON — one row result per line, printed as
soon as that row finishes — rather than a single JSON body, so curl prints each row’s
outcome as it happens instead of all at once at the end.
A row is matched against existing jobs by group_name + name. No match is a plain
create. A match is a collision, handled according to on_collision:
on_collision |
Effect |
|---|---|
overwrite |
Replace the existing job’s fields with the CSV row’s. |
skip |
Leave the existing job untouched. |
merge |
Combine the existing job and the CSV row — see below. |
Merge priority
Section titled “Merge priority”merge also needs merge_priority, source (the CSV row wins) or target (the existing
job wins). Priority only decides the outcome when both sides have a non-blank value for
a field — if a field is blank on one side, the other side’s value is used regardless of
priority. A blank cell never wipes out an existing value in a merge.
job_type can’t change via import
Section titled “job_type can’t change via import”Just as editing a job in the UI can’t change its job_type after creation, an import
row that would change an existing job’s type across a collision is not applied — it’s
reported as a per-row error instead, and the rest of the batch continues.
Script jobs still need the script permission
Section titled “Script jobs still need the script permission”A CSV row with job_type set to bash, javascript, typescript, or groovy needs the
same jobs:script permission a script job created directly in the UI needs (see
Script Jobs). A script row uploaded without that permission
is reported as a per-row error — it doesn’t fail the rest of the import.
group_name limits
Section titled “group_name limits”A group_name is limited to 255 bytes and 16 levels deep — the name is normalized
first (each /-separated segment is trimmed of whitespace and blank segments are dropped),
and the limits apply to the normalized name, so a/ /b is 2 levels. A row over either limit
is always reported as a per-row error and never skipped, whatever the on_collision
policy, with the message group_name must be at most 255 bytes or
group_name must be at most 16 levels deep; the rest of the batch continues. An existing
job whose group_name is over the limits cannot be repaired by import: the row’s own
group_name is validated before the collision lookup, so an over-limit value is a row
error. Fix it through the job’s edit form or a PUT update.
Nothing aborts the batch, and progress is live
Section titled “Nothing aborts the batch, and progress is live”Every row in the file gets its own outcome — created, updated, skipped, or error. The response streams one result per row as it happens, rather than waiting for the whole file to finish, so the admin UI’s import screen fills in its results table live, row by row, while the upload is still running. One malformed or rejected row never blocks the rows around it — it just reports its own error and the rest of the batch keeps going.