> ## Documentation Index
> Fetch the complete documentation index at: https://dripart-chore-sync-comfy-api-v2-spec-463e218.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Comfy SDKs

> Official Python and TypeScript SDKs for running ComfyUI workflows from your own application

<Warning>
  **Beta.** The SDKs and the Comfy API v2 they call are at `0.1.x`. The shape of the API can still change before we lock it down. Now is the cheapest time to tell us it is wrong. See [Feedback](#feedback).
</Warning>

The Comfy SDKs let your application run ComfyUI workflows and get the results back. You submit a workflow, ComfyUI executes it, and you download the outputs. The same code runs against Comfy Cloud or against a ComfyUI instance you host yourself. Only the base URL changes.

The SDKs are clients for the [Comfy API v2](/api-reference/v2/overview), a versioned HTTP API that we intend to support long term. New releases of ComfyUI will not break integrations built on it.

Things people build this way:

* Plugins that generate content inside another application, such as Blender or Krita
* Consumer apps that run generation on behalf of their users
* Batch pipelines, for example running one workflow over every frame of a video
* Backend services that need many workflows in flight at once

<Note>
  These SDKs drive ComfyUI **from the outside**. If you are writing custom nodes or frontend extensions that run **inside** ComfyUI, you want [Develop Custom Nodes](/custom-nodes/overview) instead. Those are a separate set of APIs.
</Note>

## Install

<CodeGroup>
  ```bash Python theme={null}
  pip install comfy-sdk
  ```

  ```bash TypeScript theme={null}
  npm i @comfyorg/sdk
  ```
</CodeGroup>

Python 3.10 or newer. Node 22 or newer.

## Quickstart

Upload an input image, run a workflow, and write the results to disk.

<CodeGroup>
  ```python Python theme={null}
  from comfy_sdk import Comfy

  # Comfy Cloud
  client = Comfy(api_key="comfyui-...")

  wf = client.workflows.from_file("workflow_api.json")

  asset = client.assets.from_file("photo.png")
  wf.set_input("10", "image", asset)

  job = client.run(wf)
  for output in job.get_outputs("9"):
      output.to_file(output.name)
  ```

  ```typescript TypeScript theme={null}
  import { Comfy } from "@comfyorg/sdk";

  // Comfy Cloud
  const client = new Comfy({ apiKey: "comfyui-..." });

  const wf = await client.workflows.fromFile("workflow_api.json");

  const asset = client.assets.fromFile("photo.png");
  wf.setInput("10", "image", asset);

  const job = await client.run(wf);
  await job.getOutputs("9")[0].toFile("out.png");
  ```
</CodeGroup>

`workflow_api.json` is a workflow saved in [API format](/development/api-development/workflow-api-format). `"10"` and `"9"` are node IDs from that file: the node the input image feeds into, and the output node whose results you want.

Asset handles are lazy. `photo.png` is hashed locally and only uploaded if the server does not already have those bytes, so re-running with the same input costs nothing.

`run()` submits the job and waits for it to reach a terminal state. To do work while it executes, use `submit()` instead and watch the [event stream](#watching-a-job-run).

To run against your own ComfyUI instead, set `COMFY_BASE_URL` and drop the key. See below.

## Choosing a base URL

| Surface                   | Base URL                                  | API key                                       |
| ------------------------- | ----------------------------------------- | --------------------------------------------- |
| **Comfy Cloud**           | `https://cloud.comfy.org` (the default)   | Required                                      |
| **Serverless deployment** | `https://<deployment>.run.comfy.app`      | Required                                      |
| **Your own ComfyUI**      | `http://127.0.0.1:8189` (the local proxy) | None by default. Optional static bearer token |

The base URL comes from the `COMFY_BASE_URL` environment variable, not a constructor argument:

```bash theme={null}
export COMFY_BASE_URL="https://<deployment>.run.comfy.app"  # serverless
export COMFY_BASE_URL="http://127.0.0.1:8189"               # self-hosted proxy
```

It is read each time a client is constructed, must be an `http(s)` URL, and unset or blank means Comfy Cloud. So the client itself is the same everywhere:

<CodeGroup>
  ```python Python theme={null}
  client = Comfy(api_key="comfyui-...")
  ```

  ```typescript TypeScript theme={null}
  const client = new Comfy({ apiKey: "comfyui-..." });
  ```
</CodeGroup>

<Note>
  Upgrading from an early build? `Comfy("<url>", "<key>")` is now `Comfy(api_key="<key>")` with `COMFY_BASE_URL` set. `api_key` is keyword-only, so the old positional call raises `TypeError` rather than quietly reading a URL as a key.
</Note>

### Comfy Cloud

Works out of the box. Create an [API key](/development/api-development/getting-an-api-key) and pass it to the client.

<Note>
  API access requires a paid Comfy Cloud subscription. The free tier does not include it. How many jobs you can run at once depends on your tier. See [Cloud API Overview](/development/cloud/overview#parallel-execution-concurrent-jobs).
</Note>

### Serverless deployment

A workflow you deployed through the [developer platform](https://platform.comfy.org) gets its own endpoint. Point `COMFY_BASE_URL` at it and use your API key, exactly as with Comfy Cloud. Everything in this guide works the same way.

Serverless deployments run one pinned workflow, so `get_workflow()` returns the executed graph (`format: "api"`).

### Your own ComfyUI

During the beta, the v2 API is served by [comfy-api-proxy](https://github.com/Comfy-Org/comfy-api-proxy), a small open-source service that runs alongside your ComfyUI:

```bash theme={null}
pip install comfy-api-proxy
comfy-api-proxy
```

By default it proxies the ComfyUI on `127.0.0.1:8188` and serves the v2 API on `127.0.0.1:8189`. Use `--comfyui` and `--port` to change either.

Then set `COMFY_BASE_URL="http://127.0.0.1:8189"`. Authentication is not required by default. If the proxy is configured with a static bearer token, pass that token as the SDK API key: `Comfy(api_key="...")`. The proxy binds to loopback only by default. Run it with `--comfyui-base-dir /path/to/ComfyUI` if you also want to upload model files into your install.

The proxy is a stopgap. Once the v2 API stabilizes it moves into ComfyUI core and the proxy is no longer needed.

## Watching a job run

`job.events()` gives you a live stream of the job's state: node and step progress, preview frames, and each output the moment it is committed. It reconnects on its own if the connection drops.

<CodeGroup>
  ```python Python theme={null}
  from comfy_sdk import Progress, Preview, OutputReady, StatusChange

  job = client.submit(wf)

  for event in job.events():
      match event:
          case Progress() as p:
              print(f"{p.value:.0%} {p.message}")
          case Preview() as pv:
              image = pv.to_pil()
          case OutputReady() as o:
              o.output.to_file(f"partial/{o.output.name}")
          case StatusChange(status="succeeded"):
              break

  result = job.result()
  ```

  ```typescript TypeScript theme={null}
  const job = await client.submit(wf);

  // The label is required: a bare `break` inside a switch leaves the switch,
  // not the loop.
  eventLoop: for await (const event of job.events()) {
    switch (event.kind) {
      case "progress":
        console.log(event.value);
        break;
      case "outputReady":
        await event.output.toFile(`${event.output.name}`);
        break;
      case "statusChange":
        if (event.status === "succeeded") break eventLoop;
    }
  }
  ```
</CodeGroup>

`Preview.to_pil()` requires the optional Pillow extra: `pip install "comfy-sdk[pil]"`.

`result()` returns the finished job, or raises `JobFailed` with the node-level detail if execution failed. See the [SDK README](#reference) for your language for the full event catalog.

The stream is a live feed, not a replayable log. It exists so you can render progress, not so you can rely on it for results. Polling the job is what is authoritative, and `run()`, `wait()`, and `result()` fall back to polling automatically. See [Design Notes](/development/api-development/sdks-design#poll-first-stream-for-progress) for why.

## Tracing an output back to its workflow

Outputs carry the id of the job that produced them, so you can start from a file and work backwards without keeping a side table.

<CodeGroup>
  ```python Python theme={null}
  output = job.outputs[0]
  output.job_id          # the job that produced this file
  ```

  ```typescript TypeScript theme={null}
  const output = job.outputs[0];
  output.jobId; // the job that produced this file
  ```
</CodeGroup>

The same id is on an asset fetched on its own, so a file you found later still leads back to its job. It is `None` (TypeScript: `undefined`) for an asset you uploaded, which has no producing job.

From the job you can ask for the workflow behind it. This works for a job you did not submit in this process, rehydrated by id:

<CodeGroup>
  ```python Python theme={null}
  wf = job.get_workflow()

  if wf.format == "save":
      ...  # the workflow as authored, canvas layout and Note nodes intact
  else:
      ...  # the executed API-format graph
  ```

  ```typescript TypeScript theme={null}
  const wf = await job.getWorkflow();

  if (wf.format === "save") {
    // the workflow as authored, canvas layout and Note nodes intact
  } else {
    // the executed API-format graph
  }
  ```
</CodeGroup>

**Always branch on `format`.** Which shape comes back depends on how the job was submitted, not on anything you control per request:

| `format` | What you get                                                                                             | When                                                                      |
| -------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| `save`   | The authoring workflow at the version the job ran, with canvas layout and editor-only nodes such as Note | Jobs submitted from the Comfy Cloud editor, which pins a workflow version |
| `api`    | The executed graph. Editor-only constructs are gone and Get/Set nodes are expanded                       | Everything else, including every job submitted through these SDKs today   |

Jobs you submit through the SDK always return `api`, because v2 submission has no version-pinning fields yet. That will change; the discriminator is there so your code does not have to.

## What the SDKs cover today

The first version does one thing properly: run a workflow and get the results back.

* **Assets.** Create input handles from a file, bytes, a stream, or a URL. Handles are lazy and content addressed, so re-running with the same input does not re-upload it.
* **Submission.** Submit an API-format graph. Submission is idempotent, and a full queue is retried for you within a bounded budget.
* **Execution.** Poll with `wait()`, or follow `events()` for live progress.
* **Outputs.** Write to disk, buffer into memory, fetch a byte range, or get a short-lived download URL.
* **Traceability.** Every output carries the id of the job that produced it, and a job can hand back the workflow behind it.
* **Deleting assets.** Remove an asset you uploaded, by handle or by id.
* **Errors.** Typed exceptions such as `JobFailed`, `Unauthorized`, `InsufficientCredits`, and `QueueFull`, rather than raw status codes.
* **Cancellation.** Jobs can be canceled while running. TypeScript additionally accepts an `AbortSignal` on any call.

Python ships both a synchronous `Comfy` client and an `AsyncComfy` client with the same surface. TypeScript is async only.

Not in this version: managing saved workflows, the model library, node introspection, and named workflow parameters. [Design Notes](/development/api-development/sdks-design#scope-of-the-first-version) explains why the surface starts this small.

## Reference

The SDK READMEs are the full reference for each language, including auth, assets, errors, and the low-level escape hatches.

<CardGroup cols={2}>
  <Card title="Python SDK" icon="python" href="https://github.com/Comfy-Org/comfy-python-sdk">
    <code>comfy-sdk</code> on PyPI. Sync and async clients.
  </Card>

  <Card title="TypeScript SDK" icon="js" href="https://github.com/Comfy-Org/comfy-typescript-sdk">
    <code>@comfyorg/sdk</code> on npm. Typed, async, with a low-level client.
  </Card>

  <Card title="Comfy API v2 Reference" icon="code" href="/api-reference/v2/overview">
    The HTTP API underneath both SDKs. Use it directly from any language.
  </Card>

  <Card title="Design Notes" icon="compass" href="/development/api-development/sdks-design">
    Why this API exists, how it relates to the existing ComfyUI APIs, and what comes next.
  </Card>
</CardGroup>

## Feedback

This is `0.1.x` on purpose. Method names, the client shape, the event catalog, the error taxonomy, and how asset handling feels in practice are all still cheap to change, and we plan to lock the surface down over the next few weeks. After that, "we support this long term" starts to mean we cannot fix it for you anymore.

So tell us what is awkward, what you expected to find and did not, and what you ended up working around. The `#developer-platform` channel in [our Discord](https://discord.com/invite/comfyorg) is the place for it.

If you want a first-party SDK in another language, say so there. Both SDKs sit on the same documented HTTP contract, so any language can talk to the API today, but we would rather know where the demand is.
