Skip to content

Open a job (the producer)

POST
/v1/jobs
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.

X-Plune-Project
string

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.

Media type application/json
object
kind
required

ingest, sync, auto-analysis, or a kind of your own. Not one the server runs itself (bin-empty, run-export, runs-delete): 403.

string
>= 1 characters <= 64 characters
subjectId

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.

string
>= 1 characters <= 200 characters
subjectName

Its name as people know it — shown after the kind (a sync carries the one it runs for).

string
>= 1 characters <= 200 characters
progress

Absent while the size is not known yet — never 0 / 0.

object
done
required
integer
<= 2147483647
total
required
integer
>= 1 <= 2147483647
cursor

Where it resumes from.

string
>= 1 characters <= 2000 characters
Examples

An ingest of 800 cases

{
"kind": "ingest",
"subjectId": "cairn-run-7",
"subjectName": "nightly-e2e",
"progress": {
"done": 0,
"total": 800
}
}

A job of this kind and subject is already unfinished: that job, as it is

Media type application/json

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 »; not an error, no button, listed for 24 hours like completed). progress is null while the operation does not know its size — never 0 / 0.

object
id
required
string format: uuid
kind
required

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.

string
>= 1 characters <= 64 characters
state
required
string
Allowed values: in_progress action_needed paused blocked completed failed cancelled
subjectId
required

What it works on — a run id, an import id.

string
nullable
subjectName
required

Its name as people know it — shown after the kind.

string
nullable <= 200 characters
progress
required

How far it got; null while the size is not known yet.

object
done
required
integer
total
required
integer
>= 1
reason
required

Why it stopped — only on action_needed, paused, blocked, failed, cancelled. A cancel reads «Cancelled by ».

string
nullable <= 2000 characters
cursor
required

The producer’s own token for where it resumes; null — a Retry starts from the beginning.

string
nullable <= 2000 characters
attempt
required

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.

integer
>= 1
createdAt
required
string format: date-time
startedAt
required
string format: date-time
nullable
finishedAt
required

Set on completed, failed and cancelled, and only there.

string format: date-time
nullable
runId

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).

string
updatedAt
required
string format: date-time
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

Media type application/json

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 »; not an error, no button, listed for 24 hours like completed). progress is null while the operation does not know its size — never 0 / 0.

object
id
required
string format: uuid
kind
required

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.

string
>= 1 characters <= 64 characters
state
required
string
Allowed values: in_progress action_needed paused blocked completed failed cancelled
subjectId
required

What it works on — a run id, an import id.

string
nullable
subjectName
required

Its name as people know it — shown after the kind.

string
nullable <= 200 characters
progress
required

How far it got; null while the size is not known yet.

object
done
required
integer
total
required
integer
>= 1
reason
required

Why it stopped — only on action_needed, paused, blocked, failed, cancelled. A cancel reads «Cancelled by ».

string
nullable <= 2000 characters
cursor
required

The producer’s own token for where it resumes; null — a Retry starts from the beginning.

string
nullable <= 2000 characters
attempt
required

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.

integer
>= 1
createdAt
required
string format: date-time
startedAt
required
string format: date-time
nullable
finishedAt
required

Set on completed, failed and cancelled, and only there.

string format: date-time
nullable
runId

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).

string
updatedAt
required
string format: date-time
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

Media type application/json
object
error
required
string
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

Media type application/json
object
error
required
string
Example generated
{
"error": "example"
}

The project named in X-Plune-Project is not one of yours

Media type application/json
object
error
required
string
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.

Media type application/json
object
error
required
string
Example generated
{
"error": "example"
}
Retry-After
integer

Seconds until the window rolls over