Skip to main content
GET
Job status (the polling workhorse)

Authorizations

Authorization
string
header
required

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

id
string
required

Response

The job.

One execution of a workflow. Durable from creation until expires_at; outputs populates incrementally during execution.

id
string
required
Example:

"7f3d2c1b-9a8e-4d6f-b012-3c4d5e6f7a8b"

status
enum<string>
required

Lifecycle: queued → running → succeeded | failed | expired; a cancel request moves running → canceling → canceled. Terminal states: succeeded, canceled, failed, expired.

Available options:
queued,
running,
succeeded,
canceling,
canceled,
failed,
expired
created_at
string<date-time>
required
started_at
string<date-time> | null
required
completed_at
string<date-time> | null
required
expires_at
string<date-time>
required

Retention deadline — a platform property, not an API constant.

queue_position
integer | null
required
progress
object | null
required

The latest progress snapshot; same data the SSE stream pushes.

outputs
object[]
required
error
object | null
required

Execution failure detail, carried in job.error (not an HTTP error).

urls
object
required

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.

logs
object

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.

metrics
object

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.

Example: