Job status (the polling workhorse)
Returns the full job object: current status, the latest progress
snapshot, and every output committed so far (outputs populates
incrementally while the job runs). This is the authoritative,
resumable view of a job; everything on the SSE stream is derived
from it.
Authorizations
Authorization: Bearer <api-key> — account-scoped API keys on Cloud and serverless. Self-hosted accepts unauthenticated requests by default and can be configured with a static bearer token.
Path Parameters
Response
The job.
One execution of a workflow. Durable from creation until expires_at; outputs populates incrementally during execution.
"7f3d2c1b-9a8e-4d6f-b012-3c4d5e6f7a8b"
Lifecycle: queued → running → succeeded | failed | expired; a cancel request moves running → canceling → canceled. Terminal states: succeeded, canceled, failed, expired.
queued, running, succeeded, canceling, canceled, failed, expired Retention deadline — a platform property, not an API constant.
The latest progress snapshot; same data the SSE stream pushes.
Execution failure detail, carried in job.error (not an HTTP error).
Embedded follow-up links — follow these, don't build URLs. A link is either an absolute URL or a host-relative reference (leading /) that already includes any prefix the serving surface is mounted under (e.g. a serverless gateway's /deployment/{deployment_id}/api/v2). Clients MUST resolve a host-relative link against the request origin (scheme + authority), never against a configured base URL — joining it to a base URL that carries the same mount prefix duplicates the prefix.
What the run printed. Only jobs run on the serverless platform (a {deployment}.run.comfy.app host) carry it. Comfy Cloud and self-hosted callers never receive it, so on those surfaces the field is always absent and a client should not wait for one. Where it is populated it is captured for every job, success and failure alike, since a job that succeeds while producing the wrong thing is exactly what a failure-only log cannot explain. It lives as long as the job it belongs to: nothing ages it out ahead of the job's own expires_at, so a job never outlives its log. Absent, not null, when there is none: the surface does not populate it at all, the job has not finished, the job predates log capture, or the job ran on the public demo deployment, which captures and stores the log like every other serverless deployment but withholds it on read, because that surface takes callers with no credential and a job id would otherwise be the only thing between one anonymous caller and another's run. Those cases are deliberately not distinguished, because a caller's next action is the same in all of them, which is to stop expecting a log. Returned by GET /api/v2/jobs/{id} only. It is deliberately absent from the job object on POST /api/v2/jobs, on POST /api/v2/jobs/{id}/cancel, and on the SSE status event: the last is pushed on every transition to every open stream, and a log on each frame would pay for the whole thing repeatedly to deliver it once. A client that streams to a terminal status and wants the log re-reads the job.
Values are nullable (a metric not yet available — e.g. execution_ms before a job starts running — is null, not omitted); the example below is deliberately all-non-null purely to work around a Spectral/nimma lint-tooling crash on a literal null inside a schema example combined with additionalProperties.nullable: true — the schema itself is unchanged and still allows null values at runtime.