Get the status and result of a config export/import job
Poll a job submitted via GET or PUT /v2/config/. Jobs are scoped to
the authenticated Agent; an unknown or unowned job_id returns 404.
Three kinds of job are polled here. They share this response but not a
lifecycle, a result shape, or a set of failures — so read the section
for the one you submitted.
Both import modes report type: import and the response never restates
the mode. Tell them apart by result: only a validate carries
result.status.
Any job can also end at failed, from any non-terminal status. Then
failure_reason is the slug to branch on and failure_detail the
sentence to show a person — see ConfigJobFailureReason for all 14 and
what to do about each.
Export
Builds the archive in the background. Nothing about the Agent changes.
completed gives a temporary download link in result.url, minted
fresh on every poll, so it is never stale — poll again rather than
re-exporting if one expires. result.secrets lists the client_secret
variable names the snapshot needs, each null; that is the list to fill
in for the import, and it needs no download.
The one failure peculiar to export is archive_expired: the export
itself succeeded, but the archive was removed from storage before you
polled. The job row underneath is still completed; this response
reports failed rather than hand you a URL that 404s.
Import — mode: validate
The default, and where every import should start. Nothing is ever written, whatever the verdict.
The job begins at waiting_for_upload and moves on by itself once the
archive lands at the upload.url you were given — a validate needs the
upload too, since the answer is in the archive.
It reaches completed even when the answer is bad; the verdict is
result.status, valid or blocked. result.changes reports what an
apply would create, update and delete, per section and with names —
including knowledge.articles.delete, the number of articles the full
replace would remove from this Agent. On a selective import
result.selection reports what the include closure actually dragged in
and which references it could not resolve.
Run this before every apply. It is the only place the destructive half of an import is enumerated before it happens.
Import — mode: apply
Writes the snapshot to the Agent. This is the destructive direction, and the one you must ask for by name.
Same lifecycle as a validate: it waits at waiting_for_upload for the
archive, and nothing is read or written before that lands — so a job
sitting there has changed nothing and costs nothing to abandon.
completed gives result.articles (upserted / deleted),
result.indexing, and result.post_apply_blockers — empty is the
healthy answer there; a non-empty list means the import committed but
left something broken. warnings lists what the import did not do,
without failing.
Two failures need different handling from the rest. preflight_failed
means the target Agent cannot serve the snapshot: every blocker is in
result, and nothing was written. partially_applied means the
opposite — the run died after the configuration phase committed, so the
Agent holds part of the snapshot and a blind retry is unsafe.
Polling
Poll every 5 seconds for the first minute, then every 15. An import can
wait minutes for its upload to be noticed and run for hours on a large
knowledge base; progress moves while it does, and a job that keeps reporting is
never timed out for being slow.
Authentication
Bearer authentication of the form Bearer <token>, where token is your auth token.
Path parameters
The job id returned by the export/import submit endpoint
Response
Current lifecycle status of the job. waiting_for_upload is
import-only: the job exists and the archive has not arrived yet, so no
work has begun and started_at stays null throughout.
When the job started running; null while the job is still queued or
waiting for its upload. The gap from created_at is time spent waiting
— for the archive, then to start — not working.
When the job reached a terminal status; null while it runs
How far through the knowledge the job has got, as a percentage.
null until the size of the work is known, which for both directions is
once the job has started and read the manifest — so a queued or
waiting_for_upload job always reports null.
Knowledge is the only phase whose duration scales with the Agent: an export counts the articles written to the archive, an import the archive’s article files applied.
It answers “how far”, never “is it done” — read status for that.
100 does not mean finished, and appears on three different statuses:
completed— reported outright, since an Agent with no articles finishes with nothing counted.in_progress— the phases either side of the knowledge are not counted, so an export sits at100while it packs and uploads the archive, and an import while it applies the configuration document.failed— an export whose scan completed but whose archive expired before you polled (failure_reason: archive_expired).
A job that fails partway keeps serving the ratio it reached, so an
import that died at 4,000 of 10,000 files reports 40 from then on. One
that fails before the manifest reports null.
Floored while the job runs, so a job one article short of done reports
99. It can go down: an interrupted job is restarted from the
start of the knowledge, and an import resumes where it stopped rather than from what the dead attempt had merely reported.
Null while the job is queued or in progress. Which shape it takes is
settled by type, the submitted mode, and status:
What the import did not do, without failing — see
ConfigImportWarning for the five kinds. An empty array once an
import completes cleanly; null for an export, and null while a job
runs or after it fails.
The kind of failure, as a stable slug — null unless status is
failed. Branch on this rather than on failure_detail, whose
wording is not part of the contract.
One sentence naming what actually went wrong, to show a person. Null
when the job has not failed, and null on the failures whose slug says
everything there is to say — preflight_failed (whose findings are in
result), timeout, stalled, agent_not_found, and
internal_error, whose underlying message is never returned because
driver and storage errors carry internal hostnames.