Open a job (the producer)
const url = 'https://beta-api.plune.ai/v1/jobs';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"kind":"ingest","subjectId":"cairn-run-7","subjectName":"nightly-e2e","progress":{"done":0,"total":800}}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://beta-api.plune.ai/v1/jobs \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "kind": "ingest", "subjectId": "cairn-run-7", "subjectName": "nightly-e2e", "progress": { "done": 0, "total": 800 } }'#1769: a producer outside the server — the CLI for a sync, an ingest from Cairn, the review queue’s auto-analysis — opens its job so the panel shows it, then reports with PATCH /v1/jobs/{id}/progress and /state. The job starts in_progress. One job an operation: with a subjectId, a second call while a job of the same kind and subject is running, waiting (action_needed, blocked) or paused answers that job with 200 and writes nothing — a producer that comes back after a pause finds its job and goes on from its cursor. A finished job (completed, failed, cancelled) is not that operation any more: the next call opens a new one. A report is the heartbeat: a in_progress job nobody reported on for 30 minutes ends failed with a reason (its count and cursor kept), and a person’s Retry goes on from the cursor — but a sync or an ingest has no Retry: its reason says to run plune sync or plune ingest again (#2027, #2085). The producer sends progress at least that often. Only from an API token (a browser session: 403), not for a kind the server runs itself (403), and not for a reader (403 read-only member). Audited as job/create.
Authorizations
Section titled “Authorizations ”Parameters
Section titled “ Parameters ”Header Parameters
Section titled “Header Parameters ”Which of your projects to act in — an id from GET /v1/projects. Absent, your default project. A project you are not a member of answers 404, the same as one that does not exist. Ignored under a bearer token, which is bound to the project it was minted in.
Request Body required
Section titled “Request Body required ”object
ingest, sync, auto-analysis, or a kind of your own. Not one the server runs itself (bin-empty, run-export, runs-delete): 403.
What makes it ONE job for the operation: while a job of the same kind and subjectId is running, waiting or paused, the call answers that one (200) instead of opening another. Absent, every call opens a new job.
Its name as people know it — shown after the kind (a sync carries the one it runs for).
Absent while the size is not known yet — never 0 / 0.
object
Where it resumes from.
Examples
An ingest of 800 cases
{ "kind": "ingest", "subjectId": "cairn-run-7", "subjectName": "nightly-e2e", "progress": { "done": 0, "total": 800 }}A CLI sync, size not known yet
{ "kind": "sync", "subjectName": "plune sync main"}Responses
Section titled “ Responses ”A job of this kind and subject is already unfinished: that job, as it is
One long AI operation. Seven states: in_progress, action_needed (waits for a person — reason says for what), paused (the quota is spent; Resume, or it resumes by itself), blocked (an outside service failed — reason names it; no button), completed, failed (reason is the raw error; a person may Dismiss it), cancelled (a person’s Cancel — reason is «Cancelled by completed). progress is null while the operation does not know its size — never 0 / 0.
object
Which operation: ingest, sync, auto-analysis, and bin-empty — the Bin emptied by the server (#1388) — and runs-delete — runs deleted in the background (#1423) — today; open-ended.
What it works on — a run id, an import id.
Its name as people know it — shown after the kind.
How far it got; null while the size is not known yet.
object
Why it stopped — only on action_needed, paused, blocked, failed, cancelled. A cancel reads «Cancelled by
The producer’s own token for where it resumes; null — a Retry starts from the beginning.
Which try this is: 1, and one more for each Retry. A producer sends it back in its reports (attempt), and a report of an earlier try is 409 stale_attempt.
Set on completed, failed and cancelled, and only there.
Only on a run-export job in GET /v1/jobs and in the answer of GET /v1/runs/{id}/export (202): the run the file is of, so «Export again» works from any browser. A completed export stays in the list 7 days (its file goes after 24 hours).
Example
{ "id": "6f1c2b3a-0000-4000-8000-0000000000a9", "kind": "ingest", "state": "in_progress", "subjectId": "6f1c2b3a-0000-4000-8000-0000000000d1", "subjectName": "nightly-e2e", "progress": { "done": 600, "total": 800 }, "reason": null, "cursor": "result:600", "attempt": 1, "createdAt": "2026-09-16T10:02:00.000Z", "startedAt": "2026-09-16T10:02:00.000Z", "finishedAt": null, "updatedAt": "2026-09-16T10:09:41.000Z"}The job, opened
One long AI operation. Seven states: in_progress, action_needed (waits for a person — reason says for what), paused (the quota is spent; Resume, or it resumes by itself), blocked (an outside service failed — reason names it; no button), completed, failed (reason is the raw error; a person may Dismiss it), cancelled (a person’s Cancel — reason is «Cancelled by completed). progress is null while the operation does not know its size — never 0 / 0.
object
Which operation: ingest, sync, auto-analysis, and bin-empty — the Bin emptied by the server (#1388) — and runs-delete — runs deleted in the background (#1423) — today; open-ended.
What it works on — a run id, an import id.
Its name as people know it — shown after the kind.
How far it got; null while the size is not known yet.
object
Why it stopped — only on action_needed, paused, blocked, failed, cancelled. A cancel reads «Cancelled by
The producer’s own token for where it resumes; null — a Retry starts from the beginning.
Which try this is: 1, and one more for each Retry. A producer sends it back in its reports (attempt), and a report of an earlier try is 409 stale_attempt.
Set on completed, failed and cancelled, and only there.
Only on a run-export job in GET /v1/jobs and in the answer of GET /v1/runs/{id}/export (202): the run the file is of, so «Export again» works from any browser. A completed export stays in the list 7 days (its file goes after 24 hours).
Example
{ "id": "6f1c2b3a-0000-4000-8000-0000000000a9", "kind": "ingest", "state": "in_progress", "subjectId": "6f1c2b3a-0000-4000-8000-0000000000d1", "subjectName": "nightly-e2e", "progress": { "done": 600, "total": 800 }, "reason": null, "cursor": "result:600", "attempt": 1, "createdAt": "2026-09-16T10:02:00.000Z", "startedAt": "2026-09-16T10:02:00.000Z", "finishedAt": null, "updatedAt": "2026-09-16T10:09:41.000Z"}The body is not JSON, or not a job: an empty kind, a count past its total, a name over 200 characters, a key this API does not know
object
Example generated
{ "error": "example"}No / invalid token or session
A reader asked, a browser session sent it, or the kind is one the server runs itself
object
Example generated
{ "error": "example"}The project named in X-Plune-Project is not one of yours
object
Example generated
{ "error": "example"}Rate-limited: the per-route throttle, or the per-minute request limit of the plan an API token counts by (#1442; the body names the plan, #2092). Retry-After carries the seconds until the window rolls over.
object
Example generated
{ "error": "example"}Headers
Section titled “Headers ”Seconds until the window rolls over