> ## Documentation Index
> Fetch the complete documentation index at: https://dripart-docs-partner-nodes-consolidate.mintlify.site/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, never persisted, and excluded from
                    idempotency comparison.
                  additionalProperties: false
                  properties:
                    api_key_comfy_org:
                      type: string
                      description: API key for partner (API) nodes.
      responses:
        '201':
          description: Job created and queued.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Job'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          description: '`insufficient_credits` (Comfy Cloud / serverless deployments 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). Disambiguate by `error.code`; both 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
        logs:
          allOf:
            - $ref: '#/components/schemas/JobLogs'
          description: >-
            What the run printed. **Only jobs run on a Developer Platform
            serverless deployment** (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:
          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'
    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
    JobStatus:
      type: string
      enum:
        - queued
        - running
        - succeeded
        - canceling
        - canceled
        - failed
        - expired
      description: |
        Lifecycle: queued → running → succeeded | failed | expired;
        a cancel request 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
          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
        node_id:
          type: string
          nullable: true
        class_type:
          type: string
          nullable: true
        traceback:
          type: string
          nullable: true
    JobLogs:
      type: object
      description: >-
        A job's captured execution log. Diagnostics, not a contract on content:
        this is whatever the workflow's own code and nodes wrote to standard
        output, in the order they wrote it, so nothing about its shape is stable
        between runs or between releases of a build. It is **untrusted text** —
        a workflow chooses what goes in it — and must be rendered as plain text
        rather than interpreted.
      required:
        - text
        - truncated
        - captured_at
      properties:
        text:
          type: string
          description: The captured output.
        truncated:
          type: boolean
          description: >-
            `text` is the TAIL of a longer run. Implementations bound what they
            capture and store, so a workflow that prints megabytes keeps its
            last lines — where a failure normally is — instead of being dropped
            whole. True with an empty `text` means the log was captured and then
            shed entirely to fit.
        captured_at:
          type: string
          format: date-time
          description: When the run's output was read back off the worker.
    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
    OutputType:
      type: string
      enum:
        - image
        - video
        - audio
        - text
        - file
        - latent
      description: Normalized output kind — nothing silently dropped.
  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'
    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 Comfy
        Cloud and serverless deployments. Self-hosted accepts unauthenticated
        requests by default and can be configured with a static bearer token.

````