> ## 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.

# Submit a workflow for execution

> Accepts the API-format workflow graph verbatim. Validation is
synchronous: graph structure, unknown node classes, and asset
references (`core/ASSET` objects — every referenced `id` must exist
and be owned by the caller). A `201` means the job is durably
recorded and queued.

UI-format workflow JSON (the export with `nodes`/`links`) is
rejected with `workflow_format_ui`.

`Idempotency-Key` is single-use (reject-on-duplicate, NOT
record-and-replay): the first request to present a given key is
processed normally; ANY later request presenting the same key — a
retry, a concurrent duplicate, or a same-key request with a different
body — is rejected `422` `idempotency_key_reuse` and is never
re-executed. The key is claimed only for a request that actually
reaches submission and is released if that submission definitively
fails without creating a job (a validation error, or an upstream
reject such as out-of-credits or queue-full), so a legitimate retry
with the same key can proceed. If a submission's outcome is unknown
(an upstream timeout or 5xx where the job may or may not have been
created), the key stays claimed and the retry is rejected: poll or
list your jobs to find the possibly-created job rather than
resubmitting. Keys expire after 24h. There is no response replay and
no `Idempotency-Replayed` header.

Reserved for post-MVP and rejected if present today: `webhook_url`,
`inputs`.




## OpenAPI

````yaml /openapi-v2.yaml post /api/v2/jobs
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:
    post:
      tags:
        - jobs
      summary: Submit a workflow for execution
      description: |
        Accepts the API-format workflow graph verbatim. Validation is
        synchronous: graph structure, unknown node classes, and asset
        references (`core/ASSET` objects — every referenced `id` must exist
        and be owned by the caller). A `201` means the job is durably
        recorded and queued.

        UI-format workflow JSON (the export with `nodes`/`links`) is
        rejected with `workflow_format_ui`.

        `Idempotency-Key` is single-use (reject-on-duplicate, NOT
        record-and-replay): the first request to present a given key is
        processed normally; ANY later request presenting the same key — a
        retry, a concurrent duplicate, or a same-key request with a different
        body — is rejected `422` `idempotency_key_reuse` and is never
        re-executed. The key is claimed only for a request that actually
        reaches submission and is released if that submission definitively
        fails without creating a job (a validation error, or an upstream
        reject such as out-of-credits or queue-full), so a legitimate retry
        with the same key can proceed. If a submission's outcome is unknown
        (an upstream timeout or 5xx where the job may or may not have been
        created), the key stays claimed and the retry is rejected: poll or
        list your jobs to find the possibly-created job rather than
        resubmitting. Keys expire after 24h. There is no response replay and
        no `Idempotency-Replayed` header.

        Reserved for post-MVP and rejected if present today: `webhook_url`,
        `inputs`.
      operationId: postJobs
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - workflow
              properties:
                workflow:
                  type: object
                  description: API-format workflow graph, verbatim.
                  additionalProperties: true
                extra_data:
                  type: object
                  description: >-
                    Per-prompt ComfyUI `extra_data`, same shape as Comfy Cloud
                    and local ComfyUI. Closed object: only the enumerated keys
                    are accepted, keeping the contract fully typed. Forwarded to
                    the worker per-prompt and excluded from idempotency
                    comparison. On a deployment it is dispatch-only and never
                    stored; on Comfy Cloud it is persisted with the prompt,
                    because the worker needs it, and redacted on every path that
                    returns a workflow to a caller.


                    Send the one credential you hold: an API key as
                    `api_key_comfy_org`, or the session token an interactively
                    signed-in client has instead as `auth_token_comfy_org`.
                    Sending both is accepted and both are forwarded, but it is
                    not a supported combination and which one a node uses is not
                    defined here. Note a session token is short-lived and is not
                    re-minted for you, so one submitted long before it executes
                    may expire in the queue.
                  additionalProperties: false
                  properties:
                    api_key_comfy_org:
                      type: string
                      description: API key for partner (API) nodes.
                    auth_token_comfy_org:
                      type: string
                      description: >-
                        Session bearer token for partner (API) nodes — the
                        equivalent of `api_key_comfy_org` for a caller
                        authenticated by session rather than by key.
      responses:
        '201':
          description: Job created and queued.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Job'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          description: '`insufficient_credits` (Cloud / serverless only).'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          description: >-
            `invalid_workflow` (with per-node details), `workflow_format_ui`,
            `missing_asset`, or `idempotency_key_reuse`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '429':
          description: >-
            `queue_full` (bounded queue depth reached) or, on deployment-scoped
            surfaces, `deployment_not_ready` (deployment still
            provisioning/starting) or `deployment_unavailable` (the deployment
            is ready but its GPU provider is not taking work on it yet), or
            `rate_limited` (the caller is past a request rate limit).
            Disambiguate by `error.code`; all four mean back off and retry after
            `Retry-After`.
          headers:
            Retry-After:
              $ref: '#/components/headers/RetryAfter'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '500':
          $ref: '#/components/responses/UpstreamError'
components:
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      schema:
        type: string
      description: >-
        Client-generated UUID (recommended). Single-use: the first request to
        present a key is processed; any later request with the same key is
        rejected `422` `idempotency_key_reuse` (reject-on-duplicate, no response
        replay). Keys expire after 24h.
  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
    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), `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_stopped` (422 — terminal deployment state; a retry

        cannot succeed without operator action), `invalid_request` (422:

        a malformed asset upload or asset-from-hash field), `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). 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
              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.
              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
    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 and those whose log it withholds. Read the
            log, not the link.
    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 the gateway's `unknown_node_class` (a node class
        the deployment's build does not contain). 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.'
      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'
    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 <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.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.