Skip to main content
Work that will not finish inside an HTTP request answers 202 Accepted and hands you a job to poll. Documents, extractions, reports and knowledge ingestions all do this, and they all do it the same way — one helper produces the response, so three subsystems cannot each invent a slightly different answer.

The 202

  • Location points at the job. A caller told to poll should not have to construct the URL.
  • Retry-After is a hint, not a promise — but without one, the first thing every integration writes is a while True with no sleep in it.
  • status: "processing" is the resource’s word, and it is deliberately not one of the job’s five states. The resource and the job are two things, and the job’s own status is one dereference away under job_id.

Five states, and only these moves

completed, failed and cancelled are terminal and have no outgoing edges. A stale retry delivered after a job finished cannot move it back to running. Two distinctions that deliberately are not extra states: running → queued is a real edge and it is the retry. An internal retry does not create a new public job — same job_id, and the attempt count is where the retry lives.

Polling one

Needs jobs:read.
result is populated only when status is completed. error is present for failed and cancelled and absent otherwise — including for a job that is queued again after a retryable failure. Reporting an error on a job that is about to run again would tell you your work had failed when it has not finished.

Cancelling

Needs jobs:cancel, which is separate from jobs:read so a read-only monitoring integration cannot stop work. Idempotent, including on a job that has already finished.
Cancellation is a request, not a guarantee. A provider call already in flight cannot be interrupted, so you may see status: "running" with cancel_requested: true for a while.There is no cancelling state, because a cancelling state promises hard cancellation and the platform does not have it.

POST /jobs creates nothing today

The endpoint is routed and demands jobs:create, and creation is allowlisted twice: a job type must be registered and marked publicly creatable. Nothing is currently marked publicly creatable, so this endpoint accepts no type.
The way to create a job is the domain endpoint — POST /documents, /extractions, /reports, /knowledge/ingestions — each of which returns a job alongside its resource. POST /jobs exists for types that will have no domain resource of their own.
“No such type” and “that type is not yours to create” give the same answer. Telling them apart would let a caller enumerate the platform’s internal job types by trying names.

Listing

Needs jobs:read.
/jobs is the one endpoint on the surface that pages by offset and returns no cursor at all. The response carries has_more rather than next_cursor.Every other paged endpoint is cursor-based. See Overview.

Not found means not found

A guessed job_id, a job in another workspace, and an id that was never issued all answer 404. A 403 would confirm the guess.

When the outcome is genuinely unknown

This is the part worth reading before you build a retry loop. A job step can call one of your own tools — a booking, an order, a charge. If that call times out, the platform does not know whether it landed. There are two tempting answers and both are wrong: retrying might place a second order, and recording a failure might tell a customer their order did not go through when it did. So the platform does a third thing.
1

The ledger row stays pending, and stays there

Not failed. A failed row is one a retry may re-run, and re-running a charge is the outcome this whole mechanism exists to prevent.Nothing expires a pending row on a timer. A row disappearing is the platform forgetting the one fact that stops it acting twice.
2

The next identical call is refused without reaching your system

Not retryable. The same call with the same arguments does not go out again.
3

The customer is told it could not be confirmed

The model is instructed, in these words:
“This did not come back in time and it may or may not have gone through. Do NOT try it again. Do NOT tell them it worked and do NOT tell them it failed. Say plainly that you could not confirm it and that someone will check and come back to them.”
An ambiguous outcome has to forbid three things at once: a retry, a claim of success, and a claim of failure. A model handed “it failed” will apologise for an order that exists; handed “it worked” it will confirm one that does not.
4

A human or a reconciler settles it

The only way out of pending is somebody finding out what actually happened — asking the far system what it has. Once settled as it happened, a later identical call replays the recorded result rather than acting again. Settled as it did not, the key is freed and the call may be retried.
Do not build a client-side retry that treats a timeout as a failure. For a safe, read-only call that is fine. For anything that writes to your systems, a timeout is not a failure — it is an unknown, and the platform is deliberately refusing to guess on your behalf.

Idempotency

POST /jobs sets Idempotent-Replay: true on a replay. A replayed 202 is the same body and the same status code — a replay is not a different outcome. The key may also be sent in the body as idempotency_key, because a caller using a generated client that cannot set headers is not a caller who should have to give up idempotency. See Idempotency.