> ## Documentation Index
> Fetch the complete documentation index at: https://docs.comfy.org/llms.txt
> Use this file to discover all available pages before exploring further.

# The workflow behind a job — authoring version if pinned, executed graph otherwise

> Returns the workflow behind a job. The response's `format` field says
which of two different shapes `workflow` is in:

- `format: save` — the original authoring workflow exactly as saved
  in the Comfy Cloud editor at the version the job ran, including
  canvas layout and frontend-only nodes (e.g. Note nodes; Get/Set
  nodes not yet expanded). Returned only when the job is pinned to a
  specific workflow version — see the "when you get which" note
  below.
- `format: api` — the executed API-format prompt graph the job
  actually ran: frontend-only constructs are gone and Get/Set nodes
  are expanded. This is the same shape `POST /api/v2/jobs`'s
  `workflow` request field takes, and never includes the
  submission's `extra_data`, which can carry a live credential.

Always branch on `format`, never assume one or the other — which
shape comes back depends on how the job was submitted, not on
anything the caller controls per-request.

A deliberate sub-resource, not a field on `GET /api/v2/jobs/{id}` —
so the polling workhorse stays cheap and a caller pays for this only
when it actually wants the workflow (for example, to recover what
produced a given output).

Tied to the job's own retention: this 404s under the same conditions
`GET /api/v2/jobs/{id}` does (unknown, not-yours, or past its
retention deadline) — there is no separate lifetime for the
workflow.

**When you get which:** a job only carries a pinned workflow version
when it was submitted with that association. Today that means jobs
submitted from the Comfy Cloud frontend/editor. Jobs submitted
directly through this v2 API (`POST /api/v2/jobs`) do not carry that
association — v2 job submission has no version-linking fields yet —
so they always get `format: api`. This is expected, not a bug: it
will change once v2 submission grows the same version pinning.

A job pinned to a version also falls back to `format: api` if that
version, or the workflow it belongs to, is no longer readable by the
caller — for example the caller deleted the workflow since the job
ran. This is the same fallback as an unpinned job, and for the same
reason: it is preferable to the alternative of erroring the whole
request over data that is genuinely gone.




## OpenAPI

````yaml /openapi-v2.yaml get /api/v2/jobs/{id}/workflow
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}/workflow:
    get:
      tags:
        - jobs
      summary: >-
        The workflow behind a job — authoring version if pinned, executed graph
        otherwise
      description: |
        Returns the workflow behind a job. The response's `format` field says
        which of two different shapes `workflow` is in:

        - `format: save` — the original authoring workflow exactly as saved
          in the Comfy Cloud editor at the version the job ran, including
          canvas layout and frontend-only nodes (e.g. Note nodes; Get/Set
          nodes not yet expanded). Returned only when the job is pinned to a
          specific workflow version — see the "when you get which" note
          below.
        - `format: api` — the executed API-format prompt graph the job
          actually ran: frontend-only constructs are gone and Get/Set nodes
          are expanded. This is the same shape `POST /api/v2/jobs`'s
          `workflow` request field takes, and never includes the
          submission's `extra_data`, which can carry a live credential.

        Always branch on `format`, never assume one or the other — which
        shape comes back depends on how the job was submitted, not on
        anything the caller controls per-request.

        A deliberate sub-resource, not a field on `GET /api/v2/jobs/{id}` —
        so the polling workhorse stays cheap and a caller pays for this only
        when it actually wants the workflow (for example, to recover what
        produced a given output).

        Tied to the job's own retention: this 404s under the same conditions
        `GET /api/v2/jobs/{id}` does (unknown, not-yours, or past its
        retention deadline) — there is no separate lifetime for the
        workflow.

        **When you get which:** a job only carries a pinned workflow version
        when it was submitted with that association. Today that means jobs
        submitted from the Comfy Cloud frontend/editor. Jobs submitted
        directly through this v2 API (`POST /api/v2/jobs`) do not carry that
        association — v2 job submission has no version-linking fields yet —
        so they always get `format: api`. This is expected, not a bug: it
        will change once v2 submission grows the same version pinning.

        A job pinned to a version also falls back to `format: api` if that
        version, or the workflow it belongs to, is no longer readable by the
        caller — for example the caller deleted the workflow since the job
        ran. This is the same fallback as an unpinned job, and for the same
        reason: it is preferable to the alternative of erroring the whole
        request over data that is genuinely gone.
      operationId: getJobWorkflow
      parameters:
        - $ref: '#/components/parameters/JobId'
      responses:
        '200':
          description: The workflow graph.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JobWorkflowResponse'
        '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'
components:
  parameters:
    JobId:
      name: id
      in: path
      required: true
      schema:
        type: string
      example: 7f3d2c1b-9a8e-4d6f-b012-3c4d5e6f7a8b
  schemas:
    JobWorkflowResponse:
      type: object
      description: >-
        The workflow behind a job. See GET /api/v2/jobs/{id}/workflow's
        description for exactly when `format` is `save` vs `api`.
      required:
        - workflow
        - format
      properties:
        workflow:
          type: object
          description: The workflow, verbatim, in the shape `format` says.
          additionalProperties: true
        format:
          type: string
          enum:
            - save
            - api
          description: >-
            Discriminates the `workflow` field's shape. `save`: the original
            authoring workflow JSON, at the version pinned to the job. `api`:
            the executed API-format prompt graph.
    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),
        `queue_full` (429 + Retry-After), `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) and
        `deployment_stopped` (422 — terminal deployment state; a retry
        cannot succeed without operator action). 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'
            details:
              type: object
              nullable: true
              additionalProperties: true
              example:
                node_errors:
                  '12':
                    - field: model
                      reason: missing_input
  responses:
    Unauthorized:
      description: '`unauthorized` — missing or invalid credentials.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    Forbidden:
      description: '`forbidden` — authenticated but not allowed.'
      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 the request rate limit for this
        account. Account/rate-scoped, not job-specific — this can be returned
        even for a job id the caller doesn't own or that doesn't exist, without
        revealing which.
      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'
  headers:
    RetryAfter:
      schema:
        type: integer
      description: Seconds to wait before retrying.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        `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.

````