ComfyPilotComfyPilot
文档 / 接口 / Job status (the polling workhorse)

Job status (the polling workhorse)

Returns the full job object: current status, the latest progress

接口 job-status-the-polling-workhorse 更新于 2026-11-13

查看官方原文

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.

OpenAPI

````yaml /openapi-v2.yaml get /api/v2/jobs/{id} openapi: 3.0.3 info: title: Comfy API v2 version: 2.0.0

description: |

The official, versioned HTTP API for running ComfyUI workflows from external applications: upload inputs, submit a workflow, observe execution, retrieve results.

Design principles:

  • Poll-first. Every capability is reachable via plain GET polling; the SSE stream is a live enhancement, never the source of truth.
  • Everything is resumable. Submission is idempotent; job state and outputs are retrievable by ID until expires_at.
  • UUID identity, content-addressed dedup. Assets are UUID-identified records over blobs keyed by a server-computed blake3 hash. The hash is nullable and may be computed lazily.
  • Follow links, don't build URLs. Responses embed follow-up URLs.

Additive changes only within v2; breaking changes require v3. servers:

  • url: http://127.0.0.1:8189 description: Self-hosted (comfy-api-proxy)
  • url: https://cloud.comfy.org description: Comfy Cloud
  • url: https://{deployment}.run.comfy.app description: Serverless deployment variables: deployment: description: >- DNS-safe deployment id (subdomain label). Staging uses {deployment}.stg.run.comfy.app. default: dep-1234abcd-56ef-7890-abcd-ef1234567890 security:
  • bearerAuth: []
  • {} tags:
  • name: assets description: UUID-identified records over content-addressed blobs.
  • name: jobs description: One execution of a workflow — durable, pollable, cancelable. paths: /api/v2/jobs/{id}: get: tags:
  • jobs summary: Job status (the polling workhorse) description: | 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. operationId: getJob parameters:
  • $ref: '#/components/parameters/JobId' responses: '200': description: The job. content: application/json: schema: $ref: '#/components/schemas/Job' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '429': $ref: '#/components/responses/RateLimited' '500': $ref: '#/components/responses/UpstreamError' '503': $ref: '#/components/responses/DeploymentLookupUnavailable' components: parameters: JobId: name: id in: path required: true schema: type: string example: 7f3d2c1b-9a8e-4d6f-b012-3c4d5e6f7a8b schemas: Job: type: object description: >- One execution of a workflow. Durable from creation until expires_at; outputs populates incrementally during execution. required:
  • id
  • status
  • created_at
  • started_at
  • completed_at
  • expires_at
  • queue_position
  • progress
  • outputs
  • error
  • urls properties: id: type: string example: 7f3d2c1b-9a8e-4d6f-b012-3c4d5e6f7a8b status: $ref: '#/components/schemas/JobStatus' created_at: type: string format: date-time started_at: type: string format: date-time nullable: true completed_at: type: string format: date-time nullable: true expires_at: type: string format: date-time description: Retention deadline — a platform property, not an API constant. queue_position: type: integer nullable: true progress: allOf:
  • $ref: '#/components/schemas/Progress' nullable: true description: The latest progress snapshot; same data the SSE stream pushes. outputs: type: array items: $ref: '#/components/schemas/Output' error: allOf:
  • $ref: '#/components/schemas/JobError' nullable: true metrics: type: object description: >- 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. additionalProperties: type: integer nullable: true example: queue_ms: 9000 execution_ms: 42000 urls: $ref: '#/components/schemas/JobUrls' deployment_id: type: string description: >- The deployment the job was sent to: the id in the address it was posted at, which stays the same when the deployment moves to another release. Absent on a surface that runs jobs on no deployment. example: dep-0f19a2b3c4d5 release_id: type: string description: >- The release of the deployment's build that ran the job, which can differ from the release the deployment runs now. Absent where the serving surface does not report it. example: 7c1e9a40-3b2d-4f6a-9e81-0c5d2a7b4f13 release_version: type: integer minimum: 1 description: >- The version of the release in release_id, the number its build cut it as. Absent where the serving surface does not report it or could not look it up at the time of the answer. example: 3 metadata: $ref: '#/components/schemas/JobMetadata' JobStatus: type: string enum:
  • queued
  • running
  • succeeded
  • canceling
  • canceled
  • failed
  • expired description: | Lifecycle: queued → running → succeeded | failed | expired; a cancel request, or the deletion of the deployment the job is running on, moves running → canceling → canceled. Terminal states: succeeded, canceled, failed, expired. Progress: type: object description: >- Server-computed progress snapshot (node-count and sampler-step weighted). Complete per snapshot — one fully re-syncs a client. required:
  • value
  • nodes_done
  • nodes_total properties: value: type: number format: double minimum: 0 maximum: 1 description: Overall fraction, server-computed. example: 0.42 nodes_done: type: integer example: 11 nodes_total: type: integer example: 31 current_node: type: string nullable: true example: '12' current_node_class: type: string nullable: true example: KSampler step: type: integer nullable: true example: 21 steps: type: integer nullable: true example: 50 message: type: string nullable: true example: KSampler 21/50 Output: type: object description: >- A committed job output. Outputs are assets: id is the asset UUID, retrievable via GET /api/v2/assets/{id} for as long as the job is retained. hash is lazily computed and may be null on the retrieval hot path. required:
  • node_id
  • name
  • type
  • content_type
  • size_bytes
  • id
  • hash
  • url
  • url_expires_at properties: node_id: type: string description: >- The workflow node that reported this file; empty when the worker named none. example: '9' name: type: string example: ComfyUI_00001_.png type: $ref: '#/components/schemas/OutputType' content_type: type: string example: image/png size_bytes: type: integer format: int64 example: 1848320 id: type: string description: Asset UUID. example: 9f8a1c0d-2b3e-4f56-... hash: type: string nullable: true description: 'blake3:<hex>; null until lazily computed.' url: type: string format: uri url_expires_at: type: string format: date-time job_id: type: string nullable: true description: ID of the job that produced this output. JobError: type: object description: Execution failure detail, carried in job.error (not an HTTP error). required:
  • code
  • message properties: code: type: string example: node_execution_error message: type: string description: >- Why the job failed, written for a person to read. On a serverless deployment, where ComfyUI refused the workflow, it names the rejected nodes it has room for and their reasons, which node_errors carries in full. Its wording may change; read code and node_errors rather than matching this text. node_id: type: string nullable: true class_type: type: string nullable: true traceback: type: string nullable: true node_errors: type: object description: >- Every node ComfyUI rejected when it refused the workflow before running any of it (a value outside its allowed range, a model the deployment does not contain, a graph that loops back on itself), keyed by node id, under the names ComfyUI gave them. Absent when the workflow ran and a node raised: node_id, class_type and traceback describe that failure instead. additionalProperties: $ref: '#/components/schemas/JobNodeError' example: '22': class_type: LoraLoader errors:
  • type: value_not_in_list message: Value not in list details: >- lora_name: 'sdxl\Hyper-SDXL-8steps-lora.safetensors' not in (list of length 40) JobUrls: type: object description: >- 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. required:
  • self
  • events
  • cancel properties: self: type: string format: uri-reference events: type: string format: uri-reference cancel: type: string format: uri-reference logs: type: string format: uri-reference description: >- Where to read what this run printed. Present on any surface that captures execution logs, which is why it is the one link here that is optional: absent means this surface captures none, for any job, so a client can stop looking without spending a request on an answer it already has.

Follow this link rather than building the path from the job id. The two are not interchangeable: a surface may be mounted under a prefix this link already carries and a hand-built path would not, and a surface that does not implement the operation at all answers a routing 404 — indistinguishable, to the client, from the 404 that means the job itself is gone. Present does NOT mean this job has a log, and it is deliberately not a signal about one: a surface that captures logs offers the link on every job, including those it will answer 204 for. Read the log, not the link. JobMetadata: type: object description: >- The labels the job was submitted with, exactly as sent. Absent when it was sent with none. additionalProperties: type: string example: client: acme ErrorEnvelope: type: object

description: |

Shared error envelope with machine-readable codes. Core codes (v1): invalid_workflow (422), workflow_format_ui (422), missing_asset (422), hash_mismatch (409), blob_not_found (404), idempotency_key_reuse (422), upload_timed_out (422: an asset upload stopped sending or was too slow; retry from a faster connection), queue_full (429 + Retry-After), rate_limited (429 + Retry-After: the caller is past a request rate limit; retry), insufficient_credits (402), not_found (404), unauthorized (401), forbidden (403). Deployment-scoped surfaces add: deployment_not_ready (429 + Retry-After — the deployment can still reach ready; retry), deployment_unavailable (429 + Retry-After: the deployment is ready but its GPU provider is not taking work on it yet; retry), deployment_lookup_unavailable (503 + Retry-After, on any operation: the deployment the address names could not be looked up just now; nothing was done, so retry), database_unavailable (503 + Retry-After, only on a job submit: the job could not be saved just now and nothing was started, so retry), auth_unavailable (503 + Retry-After, on any operation: the credential could not be checked just now, which says nothing about the credential, so retry), output_unavailable (503 + Retry-After, only on a job status read: an output of the job could not be prepared just now; the job is unchanged, so retry after Retry-After, and report the job if it keeps answering), deployment_stopped (422 — terminal deployment state; a retry cannot succeed without operator action), invalid_request (422: a malformed asset upload or asset-from-hash field, or a jobs-list limit that is not a positive integer), validation_error (422, Cloud: an asset upload or asset-from-hash field was refused, e.g. a tag over the tag limits), content_blocked (451: content moderation flagged the asset's bytes) and sso_required (403: the key is valid, but the account must sign in through its organization's single sign-on, which does not accept this key). Comfy Cloud's asset upload adds invalid_content_type (422: the request is not multipart/form-data with a boundary), invalid_body (422: a malformed multipart body or upload field), validation_error (422: the platform refused the upload, for example an image over the size cap or a disallowed tag) and upload_busy (503 + Retry-After: the server is busy with other uploads and nothing was stored, so retry). Job labels add: metadata_invalid (422: a submitted metadata breaks a limit; details.key names the key, except for too many pairs, which has no details), metadata_not_supported (422: this surface keeps no job labels yet), invalid_metadata_filter (400) and invalid_cursor (400). A surface that does not list jobs yet answers not_implemented (501). A 429 is disambiguated by error.code alone; clients should treat any 429 + Retry-After as "back off and retry". required:

  • error properties: error: type: object required:
  • code
  • message properties: code: type: string example: invalid_workflow message: type: string example: 'Node 12 (KSampler): required input ''model'' is not connected' organization_id: type: string description: >- On sso_required: the organization whose single sign-on governs this key, the one that holds the account, else the one that holds the key's workspace. It is the organization query parameter of Comfy Cloud's single sign-on start; treat it as opaque. Absent when the organization is unknown, and on every other code. example: org_01HXYZEXAMPLE details: type: object nullable: true additionalProperties: true description: >- Machine-readable detail for the code. When it carries node_errors, that is keyed by node id and each value is a JobNodeError, the same shape as a job's error.node_errors, whether the refusal came at submit (for example unknown_node_class, a node class the deployment's build does not contain) or from ComfyUI after dispatch. With unknown_node_class errors it carries unknown_node_classes, the missing classes; with waits_for_browser errors, browser_wait_node_classes, the classes that wait for a browser. A submit refusal names at most fifty nodes. For invalid_node, invalid_inputs and invalid_class_type, when it found more than fifty, node_errors_truncated is true and malformed_node_count carries the full count; for waits_for_browser, browser_wait_node_count does. For unknown_node_class it names only nodes whose class is one of the at most ten listed in unknown_node_classes; when those nodes number more than fifty, node_errors_truncated is true and unknown_node_count is how many nodes have a listed class. Nodes whose class is past those ten are neither named nor counted. example: node_errors: '12': class_type: SomeCustomNode errors:
  • type: unknown_node_class message: >- this deployment's build does not contain the node class SomeCustomNode. unknown_node_classes:
  • SomeCustomNode OutputType: type: string enum:
  • image
  • video
  • audio
  • text
  • file
  • latent description: Normalized output kind — nothing silently dropped. JobNodeError: type: object description: >- One node that was rejected, and why: by ComfyUI when it refused the workflow after dispatch, or by the gateway when it refused the workflow at submit. required:
  • errors properties: class_type: type: string errors: type: array items: $ref: '#/components/schemas/JobNodeErrorReason' JobNodeErrorReason: type: object description: >- One problem found with a node. type is the code for it: ComfyUI's (for example value_not_in_list, value_bigger_than_max, dependency_cycle), or one of the gateway's, sent where its submit checks are switched on: unknown_node_class (a node class the deployment's build does not contain), waits_for_browser (a node the workflow would run whose class waits for a browser tab to answer while the job runs, which a deployment never has), invalid_node (the node is not a JSON object), invalid_inputs (its inputs is present but not an object, refused only where the deployment's ComfyUI would fail such a node anyway, so no workflow that runs today is refused), or invalid_class_type (its class_type is missing or is not a non-empty string). ComfyUI's missing_node_type means the same as unknown_node_class, found after dispatch rather than at submit. For either, message ends naming the node pack that provides the class where the Comfy node registry knows one. details usually starts with the input it is about. required:
  • type
  • message properties: type: type: string example: value_not_in_list message: type: string example: Value not in list details: type: string responses: Unauthorized: description: >- unauthorized: missing or invalid credentials. A credential that could not be checked answers auth_unavailable (503) instead. content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' Forbidden: description: >- forbidden — authenticated but not allowed. On a serverless deployment, also sso_required — the key is valid, but the account must sign in through its organization's single sign-on, which does not accept this key; it can come back on any operation. content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' NotFound: description: 'not_found.' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' RateLimited: description: >- rate_limited — the caller has exceeded a request rate limit for this account. Account/rate-scoped, not resource-specific — this can be returned even for a job, asset or deployment the caller doesn't own or that doesn't exist. headers: Retry-After: $ref: '#/components/headers/RetryAfter' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' UpstreamError: description: >- upstream_error — an unexpected failure reaching or processing the request in this implementation's backing services. The message is always a generic, safe-to-display string; implementation detail (the specific upstream, its error text, transport failures) is never included here — see each implementation's own error-mapping notes. Every operation in this contract can fail this way. content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' DeploymentLookupUnavailable: description: >- On deployment-scoped surfaces, deployment_lookup_unavailable: the deployment this address names could not be looked up just now. The request was refused before it did anything (nothing was created, read, changed or canceled), so the same request can be sent again after Retry-After. Every operation at a deployment's address can answer this way. A job submit can also answer this 503 with database_unavailable: the job could not be saved just now and nothing was started, so the same request can be sent again after Retry-After. No other operation answers database_unavailable. On Comfy Cloud an asset upload can also answer this 503 with upload_busy: the server is busy with other uploads and nothing was stored, so the same upload can be sent again after Retry-After. Any operation can also answer this 503 with auth_unavailable: the credential could not be checked just now, so the same request can be sent again after Retry-After. A job status read can also answer it with output_unavailable: an output of the job could not be prepared just now, and the job is unchanged. A transient storage failure clears on a later read after Retry-After; an output whose stored address is broken answers it on every read until the platform repairs the output. headers: Retry-After: $ref: '#/components/headers/RetryAfter' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' headers: RetryAfter: schema: type: integer description: Seconds to wait before retrying. securitySchemes: bearerAuth: type: http scheme: bearer description: >- Authorization: Bearer <credential>. The credential is one of: an account-scoped API key (comfyui-…), accepted on Cloud and serverless; a Comfy Cloud session JWT; or an OAuth access token issued for the Comfy Cloud resource. Which kinds a given deployment accepts is deployment configuration — an API key always works on Cloud and serverless, and a deployment that does not accept JWT bearers answers 401 with a message saying so. Self-hosted accepts unauthenticated requests by default and can be configured with a static bearer token.

````