# Balance Source: https://docs.runcomfy.com/account/balance Your RunComfy account has **one balance**. The same funds pay for Serverless deployments, Model API requests, and Trainer jobs, and every product checks it before starting work. This endpoint reports what is left. *** ## Endpoint **Base URL**: `https://api.runcomfy.net/prod` | Endpoint | Method | Purpose | | ------------- | ------ | ----------------------------------- | | `/v2/balance` | `GET` | Get the account's remaining balance | The balance lives on the core API host rather than being mirrored on `model-api` and `trainer-api`, because there is only one wallet to report. Authenticate with the same Bearer token used everywhere else — see **[Authentication](/serverless/authentication)**. *** ## Get the balance ``` GET /v2/balance ``` ### Request example ```bash theme={null} curl --request GET \ --url "https://api.runcomfy.net/prod/v2/balance" \ --header "Authorization: Bearer " ``` ### Response example ```json theme={null} { "balance_microdollars": 4830000, "balance_usd": 4.83, "currency": "USD" } ``` * `balance_microdollars` (integer): Remaining balance in **millionths of a US dollar**. This is the exact stored value — use it for arithmetic and threshold checks. * `balance_usd` (number): The same figure in dollars, rounded to cents for display. * `currency` (string): Always `USD`. RunComfy stores money as microdollars: **1 USD = 1,000,000 microdollars**. Comparing against `balance_usd` loses sub-cent precision, so gate automation on `balance_microdollars`. *** ## What the balance covers The reported figure is your spendable balance across all products. It excludes retired legacy playground packs, promotional credit that is restricted to specific tools, and any pack that has expired — so it matches what the platform will actually let you spend. A zero balance is a normal response, not an error. It means work will be refused until you top up in your [Profile](https://www.runcomfy.com/profile). *** ## Minimum balances Products refuse to start work below a floor, so a positive balance is not by itself enough to guarantee a job starts: | Product | Minimum to start | | ------------------------------- | ------------------------------- | | Serverless API (ComfyUI / LoRA) | \$1.00 (`1000000` microdollars) | | Trainer API | \$0.50 (`500000` microdollars) | Running out **mid-run** also has consequences: a Serverless deployment that exhausts the balance is disabled and torn down, and a training job stops with a `FAILED` status. Polling this endpoint before and during long jobs is the cheapest way to avoid both. *** ## Checking cost alongside balance Balance tells you what is left; it does not tell you what a run cost. For that, ask the request itself: ```bash theme={null} curl --request GET \ --url "https://model-api.runcomfy.net/v1/requests/{request_id}/result?include_cost=true" \ --header "Authorization: Bearer " ``` See **[Retrieve Request Results](/model-apis/async-queue-endpoints#retrieve-request-results)**. For how each product prices work, see [Model API billing](/model-apis/about-billing), [Serverless billing](/serverless/about-billing), and [Trainer billing](/trainer-apis/about-billing). *** ## From an AI assistant The MCP server exposes this as the `get_balance` tool, so an assistant can answer "how much credit do I have left?" directly. See **[Tool Reference](/mcp/tool-reference#account)**. # Authentication Source: https://docs.runcomfy.com/cli/auth The CLI supports two authentication paths: an interactive **device-code OAuth flow** for humans, and a **token env var** for CI / containers. Both produce the same kind of token: a row in the `user_tokens` table tied to your RunComfy account, sent as `Authorization: Bearer ` to the [Model API](/model-apis/quickstart) and other RunComfy services. *** ## Device-code flow (`runcomfy login`) The everyday path for human users. Standard [OAuth 2.0 device authorization grant](https://datatracker.ietf.org/doc/html/rfc8628) — same shape as `gh auth login` or `stripe login`. ```bash theme={null} runcomfy login ``` The CLI: 1. Calls `POST https://www.runcomfy.com/api/cli-auth/start` and receives a short `user_code` (e.g. `ABCD-1234`). 2. Prints the code prominently in your terminal. 3. Opens `https://www.runcomfy.com/cli-auth` in your browser. 4. Polls `POST .../cli-auth/poll` every two seconds. In the browser: 1. Sign in (magic link or any other configured provider). 2. **Type or paste the code from the terminal** into the form. The page deliberately doesn't accept a `?code=…` query string, so a stranger can't send you a pre-filled link to phish your token. 3. Click **Authorize**. The CLI saves the resulting token to `~/.config/runcomfy/token.json` (Unix `mode 0600`). ```bash theme={null} runcomfy whoami # confirms it worked runcomfy logout # remove the local token ``` Tokens are minted fresh on every `runcomfy login` (`token_type='cli'` in `user_tokens`) and can be revoked independently of any other API tokens you have on your Profile page. *** ## CI / container env var (`RUNCOMFY_TOKEN`) In a non-interactive environment, set `RUNCOMFY_TOKEN` to bypass the device-code flow entirely: ```bash theme={null} export RUNCOMFY_TOKEN= runcomfy whoami runcomfy run openai/gpt-image-2/text-to-image --input '{"prompt": "..."}' ``` The env var **takes precedence** over `~/.config/runcomfy/token.json`. Get the token from your [Profile](https://www.runcomfy.com/profile) page (the "API Token" section) — that token is interchangeable with one minted by `runcomfy login`, just with a different `token_type`. For GitHub Actions: ```yaml theme={null} - run: runcomfy run ${{ inputs.model_id }} --input '${{ inputs.body }}' env: RUNCOMFY_TOKEN: ${{ secrets.RUNCOMFY_TOKEN }} ``` *** ## Where the token lives | Source | Location | When used | | ------------------------ | ------------------------------------------------------------------------- | --------------------------------------- | | `RUNCOMFY_TOKEN` env var | n/a | Always wins if set | | `runcomfy login` | `$XDG_CONFIG_HOME/runcomfy/token.json` or `~/.config/runcomfy/token.json` | Default location on Linux/macOS/Windows | | Override | `RUNCOMFY_CONFIG_DIR=` | Useful for tests / sandboxes | | Legacy macOS | `~/Library/Application Support/runcomfy/token.json` | Read-only fallback for older builds | The file is `mode 0600` (only your user can read), and `runcomfy login` writes it atomically (temp file + `rename(2)`) so a crash mid-write can't corrupt it. *** ## Revoking a token * **Local logout**: `runcomfy logout` removes the token file. The token is still valid server-side until you also revoke it. * **Server-side revoke**: rotate or delete the row from your [Profile](https://www.runcomfy.com/profile) page. *** ## Security notes * The token is **plaintext** in `user_tokens.token` server-side. (Token-at-rest hashing is on the roadmap but does not change CLI behavior.) * Don't commit `token.json` or echo `$RUNCOMFY_TOKEN` in CI logs. * The CLI never logs the token: `runcomfy -v ...` and `RUST_LOG=reqwest=trace ...` redact the `Authorization` header. * If you suspect a token leak: rotate immediately on the Profile page, then `runcomfy logout && runcomfy login`. # Commands Source: https://docs.runcomfy.com/cli/commands The CLI covers three RunComfy products plus your account balance. All commands accept the global flags `--output {pretty,json}`, `-q/--quiet`, `-v/--verbose`, and all except `login` / `logout` require authentication ([Authentication](/cli/auth)). | Group | Commands | API | | -------------------- | ------------------------------------------------ | ---------------------------------------------------------------- | | Auth | `login`, `logout`, `whoami` | Web auth | | Account | `balance` | Serverless API | | Model catalog | `models list`, `models categories`, `models get` | [Model API](/model-apis/quickstart) | | Model requests | `run`, `status`, `result`, `cancel` | [Model API](/model-apis/async-queue-endpoints) | | Deployments | `deployments list/get/create/update/delete` | [Serverless API](/serverless/deployment-endpoints) | | Deployment inference | `deployments run/status/result/cancel/proxy` | [Serverless API](/serverless/async-queue-endpoints) | | Datasets | `datasets create/list/status/delete/upload` | [Trainer API](/trainer-apis/async-queue-endpoints-datasets) | | Training jobs | `train submit/status/result/cancel/resume/edit` | [Trainer API](/trainer-apis/async-queue-endpoints-training-jobs) | Aliases: `runcomfy requests get` ≡ `runcomfy status`, `runcomfy requests result` ≡ `runcomfy result`, `runcomfy requests cancel` ≡ `runcomfy cancel`. For exit-code semantics across all commands, see [Troubleshooting → Exit codes](/cli/troubleshooting#exit-codes). *** ## `login` Authenticate the CLI with RunComfy via the device-code OAuth flow. ```bash theme={null} runcomfy login [--web-base ] ``` 1. Calls `POST {web_base}/api/cli-auth/start`, receives a short user code and a verification URL. 2. Prints the code in the terminal: ``` 🔑 Opening the authorization page in your browser If your browser doesn't open, visit: https://www.runcomfy.com/cli-auth YOUR CODE: ABCD-1234 Type or paste this code into the page, then click Authorize. ``` 3. Opens the verification URL in your default browser. 4. Polls `POST {web_base}/api/cli-auth/poll` every 2 seconds. 5. On `Authorize`, saves the access token to `~/.config/runcomfy/token.json` (`mode 0600`). 6. `Ctrl-C` aborts the wait cleanly. | Flag | Description | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | | `--web-base ` | Override the auth host (default `https://www.runcomfy.com`). Useful for staging / preview environments. Also reads `RUNCOMFY_WEB_BASE`. | In CI or any non-interactive environment, set `RUNCOMFY_TOKEN=` and skip `runcomfy login` entirely. *** ## `logout` ```bash theme={null} runcomfy logout ``` Removes the local token file. Does **not** revoke the token server-side — for that, rotate it on your [Profile](https://www.runcomfy.com/profile) page. *** ## `whoami` Show the currently authenticated user. ```bash theme={null} runcomfy whoami # 📛 you@example.com # token type: cli # user id: 36312e13-1b55-45c5-80f7-5382bd18c98b ``` In `--output json` mode the same data goes to stdout as a single line, useful for scripts: ```bash theme={null} USER_ID=$(runcomfy --output json whoami | jq -r .id) ``` Backs `GET https://www.runcomfy.com/api/auth/me`. *** ## `balance` Show the account's remaining balance. One wallet funds every product — Model API requests, Serverless deployments, and training jobs all draw it down. ```bash theme={null} runcomfy balance # balance: $64.11 USD ``` `--output json` returns the raw record, including `balance_microdollars` for exact threshold checks: ```bash theme={null} runcomfy --output json balance # {"balance_microdollars":64106410,"balance_usd":64.11,"currency":"USD"} ``` Backs `GET /prod/v2/balance`. *** ## `models` Browse the hosted catalog that [`run`](#run) executes — find a `model_id`, then read its Input schema before building a request. ### `models list` ```bash theme={null} runcomfy models list [--search ] [--category ] [--kind ] [--include-schema] [--limit ] [--offset ] ``` | Flag | Default | Description | | ----------------------- | ------- | ----------------------------------------------------------------- | | `--search ` | — | Case-insensitive match on model\_id, display name, or description | | `--category ` | — | Capability filter, e.g. `text-to-image`, `image-to-video` | | `--kind ` | — | How the model runs: `model`, `workflow`, or `inference` | | `--include-schema` | false | Include every model's full `input_schema` (large; prints JSON) | | `--limit ` | 100 | Page size, 1–500 | | `--offset ` | 0 | Rows to skip | ```bash theme={null} runcomfy models list --search kontext --limit 5 # MODEL_ID NAME CATEGORY PRICE # blackforestlabs/flux-1-kontext/dev/image-to-image Flux Kontext Dev image-to-image $0.06/output # blackforestlabs/flux-1-kontext/pro/edit FLUX Kontext image-to-image $0.044/output # ... # Showing 1-5 of 5 (page with --offset/--limit; filter with --search/--category) ``` In `--output json` mode the raw catalog payload comes back, including `inputs`, `required_inputs`, `base_price_usd`, and `model_url`. Backs `GET /v1/models`. ### `models categories` ```bash theme={null} runcomfy models categories # audio-to-audio # image-to-image # image-to-video # text-to-image # ... ``` Pass any of these to `models list --category`. Backs `GET /v1/models/categories`. ### `models get` ```bash theme={null} runcomfy models get blackforestlabs/flux-1-kontext/pro/edit ``` Prints the model's description, categories, price, and full `input_schema` — property types, defaults, enums, and ranges. Read this instead of guessing parameter names for [`run --input`](#run). Properties whose `format` is `image_uri` / `video_uri` / `audio_uri` take a public HTTPS URL. Backs `GET /v1/models/{model_id}`. *** ## `run` Run a [Model API](/model-apis/quickstart) model end-to-end: submit, poll, fetch the result, download generated files. ```bash theme={null} runcomfy run [--input | --input-file ] [--no-wait] [--poll-secs ] [--output-dir ] [--no-download] ``` `` is the slash-separated identifier from the [Models catalog](https://www.runcomfy.com/models) (e.g. `blackforestlabs/flux-1-kontext/pro/edit`, `openai/gpt-image-2/text-to-image`). | Flag | Default | Description | | --------------------- | ------- | ---------------------------------------------------------------------------------- | | `--input ''` | — | Inline JSON payload matching the model's Input schema | | `--input-file ` | — | Read JSON input from a file. Use `-` for stdin. Mutually exclusive with `--input`. | | `--no-wait` | false | Submit and return the request\_id immediately; don't poll | | `--poll-secs ` | 2 | Polling interval in seconds while waiting | | `--output-dir ` | `.` | Where to download generated files | | `--no-download` | false | Skip file download; only print the result JSON | ### Examples ```bash theme={null} # Inline input runcomfy run blackforestlabs/flux-1-kontext/pro/edit \ --input '{"prompt": "a small purple cat", "aspect_ratio": "16:9"}' # Input from a file runcomfy run openai/gpt-image-2/text-to-image --input-file ./req.json # Input from stdin echo '{"prompt": "piped"}' | \ runcomfy run openai/gpt-image-2/text-to-image --input-file - # Submit and return immediately runcomfy run openai/gpt-image-2/text-to-image \ --input '{"prompt":"..."}' --no-wait # {"request_id":"8a3f...","wait":false} # JSON-only output for scripts runcomfy --output json run openai/gpt-image-2/text-to-image \ --input '{"prompt":"..."}' --no-wait | jq -r .request_id # Save outputs to a specific directory runcomfy run openai/gpt-image-2/text-to-image \ --input '{"prompt":"..."}' --output-dir ./out # Print the result URL but don't download runcomfy run openai/gpt-image-2/text-to-image \ --input '{"prompt":"..."}' --no-download ``` ### End-to-end output ``` ⏳ Submitting request to openai/gpt-image-2/text-to-image request_id: 8a3f... ⏳ Polling status (every 2s)... in_queue in_progress completed ✅ completed { "images": [ "https://playgrounds-storage-public.runcomfy.net/.../result.png" ] } 📥 Downloading 1 file(s) to . ./result.png ``` Stdout receives the result JSON; stderr receives the progress lines (or `[tag]` text if not a TTY / `NO_COLOR` is set). ### Behavior details * **Submit**: `POST https://model-api.runcomfy.net/v1/models/` with the JSON body **as-is** (Model API expects flat input, not `{"input":{...}}`). * **Poll**: `GET .../requests//status` every `--poll-secs` seconds. * **Fetch**: on terminal status (`completed` / `succeeded` / `failed` / `cancelled`), `GET .../requests//result`. * **Download whitelist**: the CLI scans the result JSON recursively and downloads every URL whose host ends with **`.runcomfy.net`** or **`.runcomfy.com`**. URLs outside that whitelist are listed but not fetched — preventing a compromised upstream model from coercing the CLI into pulling arbitrary internet content. Downloads stream to disk and abort with `unlink(2)` if the response exceeds 2 GiB. * **Ctrl-C**: while polling, sends `POST .../requests//cancel` to RunComfy before exiting. If the cancel call itself fails, the CLI prints the request\_id and tells you to retry with `runcomfy cancel ` — so you don't get billed for GPU you thought was stopped. *** ## `status` Poll the status of a Model API request submitted via `runcomfy run --no-wait`. ```bash theme={null} runcomfy status # request_id: 8a3fbf12-... # status: in_progress # status_url: https://model-api.runcomfy.net/v1/requests/.../status # result_url: https://model-api.runcomfy.net/v1/requests/.../result ``` If still queued, the output also includes `queue: position N`. | status | Meaning | | ------------------------- | ---------------------------------------------------------------------------------- | | `in_queue` | Waiting for a runner; `queue_position` shows how many ahead | | `in_progress` | Running on GPU | | `completed` / `succeeded` | Done; fetch the output via `runcomfy run` (same `request_id`) or curl `result_url` | | `failed` | Run errored; fetch `result_url` for the failure reason | | `cancelled` | Cancelled (by you or by the platform) | The CLI doesn't loop — it returns the current snapshot and exits. Wrap in your own `while` loop for repeated checks. Backs `GET .../requests//status`. *** ## `result` Fetch the result record of a request submitted with [`run --no-wait`](#run), and download its files. ```bash theme={null} runcomfy result [--output-dir ] [--no-download] ``` ```bash theme={null} runcomfy result 8a3fbf12-... --output-dir ./out # ✅ completed # { # "request_id": "8a3fbf12-...", # "status": "completed", # "output": { "image": "https://playgrounds-storage-public.runcomfy.net/.../result.jpeg" }, # "created_at": "...", # "completed_at": "..." # } # 📥 Downloading 1 file(s) to ./out # ./out/result.jpeg ``` Unlike [`run`](#run), this never submits anything — it reads an existing request. Calling it before the request finishes prints the current record and exits `0`; a `failed` or `cancelled` request exits non-zero. Alias: `runcomfy requests result`. Backs `GET /v1/requests/{id}/result`. *** ## `cancel` Cancel a queued or running Model API request. ```bash theme={null} runcomfy cancel # ✅ Cancelled 8a3fbf12-... (final status: cancelled) ``` If already terminal: ``` ℹ 8a3fbf12-... is already terminal (succeeded); cancel is a no-op ``` In `--output json` mode, the response includes an `outcome` field (`cancelled` or `not_cancellable`). `runcomfy run` (without `--no-wait`) auto-cancels on `Ctrl-C`, so you only need `cancel` directly when you submitted with `--no-wait`, the auto-cancel failed, or you're cancelling someone else's `request_id`. Backs `POST .../requests//cancel` (returns `202 Accepted`). *** ## `deployments` Manage [Serverless API (ComfyUI)](/serverless/introduction) deployments and run inference on them. Unlike `run`, which uses hosted catalog models, these run **your own** cloud-saved ComfyUI workflows on hardware you choose. ### `deployments list` and `deployments get` ```bash theme={null} runcomfy deployments list [--ids ,] [--include-payload] [--include-readme] runcomfy deployments get [--include-payload] [--include-readme] ``` ```bash theme={null} runcomfy deployments list # ID NAME STATUS ENABLED HARDWARE INSTANCES VERSION # a1b2c3d4-... text-to-image standby true AMPERE_48 0-1 v1 ``` `--include-payload` adds `workflow_api_json` — use it to discover the node IDs and input names that [`deployments run --overrides`](#deployments-run) targets. Backs `GET /prod/v2/deployments[/{id}]`. ### `deployments create` ```bash theme={null} runcomfy deployments create --name --workflow-id --workflow-version [--hardware ] [--min-instances ] [--max-instances ] [--queue-size ] [--keep-warm-secs ] ``` | Flag | Default | Description | | ------------------------- | ----------- | ---------------------------------------------------------------------------------------------- | | `--name` | required | Human-readable name | | `--workflow-id` | required | UUID of the cloud-saved ComfyUI workflow | | `--workflow-version` | required | Version label, e.g. `v1` | | `--hardware ` | `AMPERE_48` | `TURING_16`, `AMPERE_24`, `AMPERE_48`, `ADA_48_PLUS`, `AMPERE_80`, `ADA_80_PLUS`, `HOPPER_141` | | `--min-instances ` | 0 | Warm instance floor (0–30); **billable when > 0** | | `--max-instances ` | 1 | Concurrency ceiling (1–60) | | `--queue-size ` | 1 | Pending requests per instance before scaling out | | `--keep-warm-secs ` | 60 | Idle seconds before an instance scales down | For LoRA deployments, create via the RunComfy UI (**Trainer → LoRA Assets → Deploy**), then use `deployments list` to get the id. Backs `POST /prod/v2/deployments`. ### `deployments update` Only the flags you pass are changed. ```bash theme={null} runcomfy deployments update [--name ] [--workflow-version ] [--hardware ] [--min-instances ] [--max-instances ] [--queue-size ] [--keep-warm-secs ] [--enable | --disable] ``` `--disable` pauses the deployment (instances shut down, billing stops, configuration is preserved); `--enable` resumes it. Backs `PATCH /prod/v2/deployments/{id}`. ### `deployments delete` ```bash theme={null} runcomfy deployments delete [-y/--yes] ``` Permanent. Prompts for confirmation on a terminal; in a non-interactive shell it refuses with exit `64` unless `--yes` is given. Consider `deployments update --disable` to pause instead. Backs `DELETE /prod/v2/deployments/{id}`. ### `deployments run` Submit an inference request and, by default, wait for it. ```bash theme={null} runcomfy deployments run [--overrides | --overrides-file ] [--workflow-file ] [--extra-data ] [--webhook-url ] [--webhook-intermediate] [--no-wait] [--poll-secs ] [--timeout ] [--output-dir ] [--no-download] ``` | Flag | Description | | ------------------------- | ------------------------------------------------------------------------------- | | `--overrides ` | Partial workflow graph keyed by node ID | | `--overrides-file ` | Same, read from a file (`-` for stdin) | | `--workflow-file ` | Advanced: run a full `workflow_api.json` inline without updating the deployment | | `--extra-data ` | e.g. `{"api_key_comfy_org": "comfyui-..."}` for ComfyUI Core API nodes | | `--webhook-url ` | Push-based status updates instead of polling | | `--webhook-intermediate` | Fire the webhook on every status change, not just terminal ones | | `--timeout ` | Stop waiting after this long (the request keeps running) | One of `--overrides`, `--overrides-file` or `--workflow-file` is required — the API rejects an empty body. ```bash theme={null} runcomfy deployments run a1b2c3d4-... \ --overrides '{"6": {"inputs": {"text": "a futuristic cityscape at sunset"}}, "189": {"inputs": {"image": "https://example.com/input.jpg"}}}' ``` File inputs take a public HTTPS URL or a Base64 `data:` URI directly in the override value. `Ctrl-C` while waiting cancels the remote request. Backs `POST /prod/v2/deployments/{id}/inference`. ### `deployments status`, `result` and `cancel` ```bash theme={null} runcomfy deployments status runcomfy deployments result [--output-dir ] [--no-download] runcomfy deployments cancel ``` `status` also prints `instance_id` once an instance is running your job — that's what `deployments proxy` needs. Output URLs from `result` are hosted for **7 days**. A cancel that the API has only accepted reports `Cancellation requested`; poll `status` to confirm it reaches `canceled`. ### `deployments proxy` Call a ComfyUI backend endpoint on a live instance. ```bash theme={null} runcomfy deployments proxy [--body | --body-file ] ``` ```bash theme={null} # Unload models to free GPU memory runcomfy deployments proxy a1b2c3d4-... 1697cb1a-... api/free \ --body '{"unload_models": true, "free_memory": true}' ``` Instance IDs are ephemeral — valid only while that instance is running. Backs `POST /prod/v2/deployments/{id}/instances/{instance_id}/proxy/{path}`. *** ## `datasets` Manage [LoRA training datasets](/trainer-apis/async-queue-endpoints-datasets). A dataset must reach `READY` before a training job can mount it. ### `datasets create`, `list`, `status` and `delete` ```bash theme={null} runcomfy datasets create [--name ] runcomfy datasets list runcomfy datasets status runcomfy datasets delete [-y/--yes] ``` The dataset's **name** (not its id) is what an AI Toolkit config references in `folder_path`. `status` shows the lifecycle — `DRAFT` → `UPLOADING` → `READY` (or `FAILED`) — plus the files that uploaded successfully. ```bash theme={null} runcomfy datasets status 9a2efbab-... # id: 9a2efbab-... # name: my-dataset # status: READY # # FILENAME SIZE_BYTES # img_0001.png 215290 # img_0001.txt 24 ``` ### `datasets upload` ```bash theme={null} runcomfy datasets upload [PATH]... [--from-url [--filename ]] [--wait] [--poll-secs ] ``` Each path is a file, or a directory whose top-level files are uploaded. Files up to 150 MB go through the direct upload endpoint; larger ones automatically fetch a signed URL and stream the bytes to storage. Re-uploading a filename overwrites the previous copy. Every image or video needs a caption `.txt` with the **same base name** — `img_0001.jpg` pairs with `img_0001.txt`. ```bash theme={null} # A whole folder, then wait until the dataset is READY runcomfy datasets upload 9a2efbab-... ./my-dataset/ --wait # A file the server should fetch from a public URL runcomfy datasets upload 9a2efbab-... --from-url https://example.com/a.jpg --filename img_0002.jpg ``` `--wait` polls until `READY` (or `FAILED`, which exits non-zero). `Ctrl-C` while waiting stops watching; the uploads are already done. *** ## `train` Submit and manage [AI Toolkit training jobs](/trainer-apis/async-queue-endpoints-training-jobs) — typically LoRA training. ### `train submit` ```bash theme={null} runcomfy train submit --config [--gpu-type ] [--gpu-count ] [--gpu-id ] [--wait] [--poll-secs ] [--timeout ] ``` | Flag | Default | Description | | ------------------- | ------------- | -------------------------------------------------- | | `--config ` | required | Complete AI Toolkit YAML config (`-` reads stdin) | | `--gpu-type ` | `ADA_80_PLUS` | `ADA_80_PLUS` (H100) or `HOPPER_141` (H200) | | `--gpu-count ` | 1 | `1`, or `8` for multi-GPU (`ADA_80_PLUS` only) | | `--wait` | false | Poll until the job finishes, then print its result | Two paths in the config are fixed by the platform: ```yaml theme={null} training_folder: "/app/ai-toolkit/output" datasets: - folder_path: "/app/ai-toolkit/datasets/{dataset_name}" ``` where `{dataset_name}` is the dataset's `name` from [`datasets list`](#datasets-create--list--status--delete). Training runs for hours, so the command returns as soon as the job is queued unless you pass `--wait`. With `--wait`, `Ctrl-C` stops watching but **does not** cancel the job. ### `train status` and `train result` ```bash theme={null} runcomfy train status runcomfy train result [--download] [--output-dir ] ``` Lifecycle: `IN_QUEUE` → `RUNNING` → `STOPPED` (finished or preempted), `FAILED`, or `CANCELED`. `status` prints step progress: ```bash theme={null} runcomfy train status 7f2c... # id: 7f2c... # name: my_lora # status: RUNNING # progress: 16% (step 320/2000) ``` `result` lists checkpoints, the resolved config, and samples as hosted URLs. It is safe to call while the job is still `RUNNING` — the artifact list grows over time — and after a `FAILED` or `CANCELED` job to recover whatever was produced. Downloads are opt-in via `--download` because checkpoints are large. A checkpoint URL can be fed straight back to [`run`](#run) without deploying anything: ```bash theme={null} runcomfy run \ --input '{"prompt": "...", "lora": {"path": "my_lora_3000.safetensors"}}' ``` The `path` is either a name from your [LoRA Assets](https://www.runcomfy.com/trainer/lora-assets) or a public URL. ### `train cancel`, `resume` and `edit` ```bash theme={null} runcomfy train cancel runcomfy train resume runcomfy train edit --config ``` `cancel` stops progress; artifacts produced so far stay available through `train result`. `resume` restarts from the highest-step checkpoint under the **same** `job_id` (from step 0 if none exists) — useful after a preemption. `edit` replaces the config of a `STOPPED` / `CANCELED` / `FAILED` job, after which `resume` re-queues it; `config.name` must still match the original job's name. # Install Source: https://docs.runcomfy.com/cli/install The runcomfy CLI is a single statically-linked Rust binary. Pick whichever install matches your environment. *** ## Option 1: npx (no install) For a one-off run or to try the CLI before committing to an install: ```bash theme={null} npx -y @runcomfy/cli --version npx -y @runcomfy/cli run openai/gpt-image-2/text-to-image \ --input '{"prompt": "test"}' ``` `npx` downloads `@runcomfy/cli`, runs its `postinstall` hook (which fetches the per-platform binary from GitHub Releases), and invokes it. Subsequent `npx` runs reuse the cached package. No `PATH` changes, no admin privileges. *** ## Option 2: npm global ```bash theme={null} npm install -g @runcomfy/cli runcomfy --version ``` Same package as `npx`, but `runcomfy` is on your `PATH` permanently. Update with `npm i -g @runcomfy/cli@latest`. *** ## Option 3: curl install script For developers who don't want Node on the install path, or for quick provisioning into containers / VMs: ```bash theme={null} curl -fsSL https://runcomfy.com/install.sh | sh ``` The script: * Detects your OS and CPU architecture * Downloads the matching binary from the [latest GitHub Release](https://github.com/runcomfy-com/runcomfy-cli/releases) * Verifies the SHA-256 checksum * Installs to `/usr/local/bin/runcomfy` if that's writable, else `~/.local/bin/runcomfy` (override with `RUNCOMFY_PREFIX`) * Adds the install dir to your PATH if needed (with a one-line printed instruction) To install a specific version: ```bash theme={null} curl -fsSL https://runcomfy.com/install.sh | RUNCOMFY_VERSION=v0.2.0 sh ``` *** ## Option 4: Build from source ```bash theme={null} git clone https://github.com/runcomfy-com/runcomfy-cli cd runcomfy-cli cargo build --release ./target/release/runcomfy --version ``` Requires Rust 1.75+ (`rustup install stable`). *** ## Supported platforms | OS | Architecture | npm | curl install.sh | Notes | | ------- | ------------------- | --- | --------------- | ------------------------------------------------------------------- | | macOS | Apple Silicon (M1+) | ✅ | ✅ | | | macOS | Intel | ✅ | ✅ | | | Linux | x86\_64 (glibc) | ✅ | ✅ | Works on Ubuntu, Debian, Fedora, RHEL, Alpine via `apk add gcompat` | | Linux | ARM64 (aarch64) | ✅ | ✅ | Raspberry Pi 4 / 5, ARM cloud VMs | | Windows | x86\_64 | — | — | Use WSL2 for now | *** ## Shell completion After install, generate a completion script for your shell: ```bash theme={null} runcomfy completion bash > /usr/local/etc/bash_completion.d/runcomfy # bash runcomfy completion zsh > "${fpath[1]}/_runcomfy" # zsh runcomfy completion fish > ~/.config/fish/completions/runcomfy.fish # fish ``` Re-source your shell rc file (or open a new terminal) to pick it up. `runcomfy ` will then list subcommands; `runcomfy run ` will offer flags. *** ## Verify the install ```bash theme={null} runcomfy --version # runcomfy 0.1.1 (16cc8b8, 2026-04-29) ``` The output includes the git short SHA and commit date so you can pin a specific build. # Introduction Source: https://docs.runcomfy.com/cli/introduction The **runcomfy CLI** drives RunComfy from your terminal or any AI agent. It covers the [Model API](/model-apis/quickstart), [Serverless API (ComfyUI)](/serverless/introduction) and [Trainer API](/trainer-apis/introduction) — so you can find a model, run it, manage your own workflow deployments, and train a LoRA without leaving the shell. A single command submits a request, waits for completion, and downloads the result. ```bash theme={null} runcomfy run openai/gpt-image-2/text-to-image \ --input '{"prompt": "a small purple cat at sunset"}' ``` **Repository**: [runcomfy-com/runcomfy-cli](https://github.com/runcomfy-com/runcomfy-cli) *** ## What it gives you * **One command end-to-end** — submit, poll, fetch the result, and download generated files into your working directory. * **Pipe-friendly** — `--output json` emits a single line of JSON to stdout (stderr stays empty in JSON mode), so `runcomfy ... | jq ...` works in scripts. * **AI-agent-friendly** — every progress line goes to stderr; final payload to stdout; `sysexits`-style exit codes (`64` usage / `65` data / `69` upstream / `75` retryable / `77` auth) so callers branch on failure type without parsing strings. * **Browser OAuth** — `runcomfy login` opens a device-code flow in your browser. No long-lived API key copy-paste, and no token in your shell history. * **Streaming downloads, with safety** — multi-hundred-MB video outputs stream straight to disk; downloads are restricted to RunComfy CDN hosts so a compromised model can't trick the CLI into pulling arbitrary content. *** ## Commands at a glance | Command | Purpose | | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | | [`login`](/cli/commands#login) / [`logout`](/cli/commands#logout) / [`whoami`](/cli/commands#whoami) | Browser OAuth, clear the token, show the current user | | [`balance`](/cli/commands#balance) | Remaining account balance — one wallet funds every product | | [`models`](/cli/commands#models) | Browse the hosted catalog: find a `model_id`, read its Input schema | | [`run`](/cli/commands#run) | Run a Model API model end-to-end | | [`status`](/cli/commands#status) / [`result`](/cli/commands#result) / [`cancel`](/cli/commands#cancel) | Poll, fetch and cancel a Model API request | | [`deployments`](/cli/commands#deployments) | Create, scale and delete Serverless (ComfyUI) deployments, and run inference on them | | [`datasets`](/cli/commands#datasets) | Create training datasets and upload media plus captions | | [`train`](/cli/commands#train) | Submit, track, resume and edit AI Toolkit LoRA training jobs | | [`completion`](/cli/install#shell-completion) | Generate a shell completion script | Aliases: `runcomfy requests get` / `result` / `cancel` are equivalent to `runcomfy status` / `result` / `cancel`. ### Which one runs my model? * **A model from the [catalog](https://www.runcomfy.com/models)** — [`run`](/cli/commands#run). No setup, per-request billing. * **My own ComfyUI workflow** — [`deployments run`](/cli/commands#deployments-run), against a deployment you create once and choose hardware for. * **A LoRA I trained** — either: pass it to [`run`](/cli/commands#run) as an input on its base model, or deploy it for a dedicated endpoint. *** ## Next steps * [**Quickstart**](/cli/quickstart) — install, log in, generate your first image. * [**Install**](/cli/install) — npx, `npm i -g`, `curl install.sh`, build from source. * [**Authentication**](/cli/auth) — device-code flow, `RUNCOMFY_TOKEN` for CI, where the token is stored. * [**Troubleshooting**](/cli/troubleshooting) — exit codes, common errors, proxy issues. # Quickstart Source: https://docs.runcomfy.com/cli/quickstart Install the CLI, sign in, and generate your first image — three commands. *** ## 1. Install The fastest path is `npx`, which downloads and runs the CLI without a global install: ```bash theme={null} npx -y @runcomfy/cli --version ``` For repeat use, install globally or use the curl installer — see [Install](/cli/install) for all four options. *** ## 2. Sign in ```bash theme={null} runcomfy login ``` This prints a verification code in your terminal and opens `https://www.runcomfy.com/cli-auth` in your browser. **Type or paste the code from the terminal into the page**, then click **Authorize**. The CLI saves a token to `~/.config/runcomfy/token.json` (`mode 0600`). If you already have an API token from your [Profile](https://www.runcomfy.com/profile), set `RUNCOMFY_TOKEN=` and skip `runcomfy login` entirely. See [Authentication](/cli/auth) for the full flow. Verify: ```bash theme={null} runcomfy whoami # 📛 you@example.com # token type: cli # user id: ... ``` *** ## 3. Pick a model Browse the catalog from the shell, then read the model's Input schema so you know what `--input` takes: ```bash theme={null} runcomfy models list --search "gpt image" runcomfy models get openai/gpt-image-2/text-to-image ``` `models list` also filters by capability — `--category text-to-video`, `--category image-to-image` — and `runcomfy models categories` prints the full set. *** ## 4. Generate an image ```bash theme={null} runcomfy run openai/gpt-image-2/text-to-image \ --input '{"prompt": "a small purple cat at sunset, photorealistic"}' ``` What you'll see: ``` ⏳ Submitting request to openai/gpt-image-2/text-to-image request_id: 8a3f... ⏳ Polling status (every 2s)... in_queue in_progress completed ✅ completed { "images": [ "https://playgrounds-storage-public.runcomfy.net/.../result.png" ] } 📥 Downloading 1 file(s) to . ./result.png ``` The result file is in your current directory. Override with `--output-dir ./out` or skip downloading with `--no-download`. *** ## What's next * See [`runcomfy run`](/cli/commands#run) for `--input-file`, `--no-wait`, and `--output-dir`. * Submit now, collect later: `runcomfy --output json run ... --no-wait | jq -r .request_id`, then [`runcomfy result `](/cli/commands#result). * Check what a run costs you against [`runcomfy balance`](/cli/commands#balance); `models list` shows each model's price per unit. * Running your **own** ComfyUI workflow instead of a catalog model? See [`deployments`](/cli/commands#deployments). * Training a LoRA? [`datasets`](/cli/commands#datasets) then [`train`](/cli/commands#train). # Troubleshooting Source: https://docs.runcomfy.com/cli/troubleshooting Common errors, why they happen, and how to fix them. *** ## Exit codes The CLI uses [`sysexits.h`](https://man.openbsd.org/sysexits.3)-style exit codes so scripts can branch on failure type without parsing strings. | Code | Name | When | | ---- | ---------------- | ------------------------------------------------------------------------------------------ | | `0` | success | | | `2` | clap parse error | bad CLI args (missing required, unknown flag) | | `64` | `EX_USAGE` | usage error (e.g. `model_id` without a `/`) | | `65` | `EX_DATAERR` | bad input data — JSON parse error, schema mismatch (API 422/400) | | `66` | `EX_NOINPUT` | a local input file doesn't exist (`--input-file`, `train submit --config`, an upload path) | | `69` | `EX_UNAVAILABLE` | upstream 5xx | | `75` | `EX_TEMPFAIL` | retryable: 408, 429, network timeout, a training job that stopped before finishing | | `77` | `EX_NOPERM` | auth: 401, 403, not signed in, token rejected | | `1` | unclassified | anything else | ```bash theme={null} runcomfy run ... || case $? in 77) echo "auth failed; refresh login" ;; 75) echo "transient; retry" ;; *) echo "permanent failure" ;; esac ``` *** ## Common errors ### `not signed in — run \`runcomfy login\` first\` **Exit code 77**. No token in `RUNCOMFY_TOKEN` env, no token file at `/token.json`, no legacy macOS file. Run [`runcomfy login`](/cli/commands#login) or set [`RUNCOMFY_TOKEN`](/cli/auth#ci--container-env-var-runcomfy_token). ### `authentication failed — token rejected by server` **Exit code 77**. Server returned 401 — the token was once valid but isn't anymore (revoked, rotated, or wrong). Run `runcomfy login` to mint a fresh one, or rotate `RUNCOMFY_TOKEN` to a current value from your [Profile](https://www.runcomfy.com/profile). ### `model `xxx` not found. Verify the model_id at https://www.runcomfy.com/models` **Exit code 65**. The Model API doesn't have a model at that path. Common causes: * Typo (e.g. `flux-1-kontext-pro/edit` vs `flux-1-kontext/pro/edit` — note the slash placement) * Stale model\_id from a tutorial; the model was renamed or removed Find the canonical id on the model's page: it's printed prominently above the playground. ### `input did not match the model's schema` **Exit code 65**. API returned 422 / 400. The CLI sends the JSON body verbatim to the Model API; if a required field is missing or a value is the wrong type, the server rejects it. The error message includes the server's response body (capped at 300 chars). Open the model's API tab — it lists the Input schema with required fields, types, and defaults. Adjust your `--input` JSON to match. ### `rate limited; retry in a moment` **Exit code 75**. API returned 429. Retry after a short sleep. RunComfy free-tier accounts share rate limits; pro plans have higher caps. ### `is not a valid model_id (expected slash-separated)` **Exit code 65**. Caught client-side before the request goes out. `model_id` must contain at least one `/` — that's how Model API paths work. Example: `openai/gpt-image-2/text-to-image`. ### `prompt too long` / unexpected truncation Most models have a token limit (often 512 or "a few thousand"). The CLI doesn't enforce this — the model server does. Trim your prompt or check the model page for the exact limit. ### `pass --overrides / --overrides-file ... or --workflow-file` **Exit code 65**. Caught client-side: the Serverless inference endpoint rejects an empty body, so [`deployments run`](/cli/commands#deployments-run) needs the inputs to send. Run `runcomfy deployments get --include-payload` to see the workflow's node IDs, input names, and default overrides, then key your `--overrides` by node ID. ### `training job ... did not finish` **Exit code 75**. A training job reported `STOPPED`, but its step progress stopped short of the total or its result carried an error — typically a spot preemption or a reclaimed server. The checkpoints produced so far are still available via [`train result`](/cli/commands#train); continue from the latest one with [`train resume `](/cli/commands#train), which reuses the same job id. ### `refusing to delete ... without confirmation` **Exit code 64**. `deployments delete` and `datasets delete` are permanent, so in a non-interactive shell (CI, a pipe, an agent) they refuse unless you pass `--yes`. On a terminal they prompt instead. ### `Skipped N URL(s) outside trusted hosts` Not an error — the run succeeded. The CLI only downloads from RunComfy CDN hosts (`*.runcomfy.net`, `*.runcomfy.com`), including across redirects, so that a compromised or adversarial model output can't make it fetch arbitrary content. Any other URL in the result is printed but not fetched; copy it yourself if you trust it. ### `request cancelled` (after `Ctrl-C`) **Exit code 1**. Expected when you Ctrl-C during `runcomfy run` or `deployments run`. The CLI also tries to cancel the remote request before exiting — if the cancel call itself failed, or the request was already running (the Model API only cancels queued requests), you'll see a line saying so, with the id to retry or to fetch later with [`runcomfy result `](/cli/commands#result). `train submit --wait` and `datasets upload --wait` behave differently on purpose: Ctrl-C there stops watching but leaves the remote work running, so an accidental keystroke can't throw away hours of training. *** ## Behind a corporate proxy `reqwest` (the HTTP client) honors the standard `HTTP_PROXY` / `HTTPS_PROXY` env vars automatically. If your proxy intercepts traffic with a custom CA, set `SSL_CERT_FILE=/path/to/ca-bundle.pem`. To force-bypass the proxy (e.g. for direct LAN access): ```bash theme={null} NO_PROXY=* no_proxy=* runcomfy run ... ``` Sporadic 403 from `model-api.runcomfy.net` while behind a local proxy (ClashX, Surge, etc.) usually means the proxy is interfering — retry, or temporarily set `NO_PROXY=*` for the call. *** ## Pipe and CI gotchas * Use `--output json` for any script. Pretty mode emits emoji that mangle `jq` parsing. * In non-TTY (piped, CI logs), the CLI auto-replaces emoji with `[tag]` — but progress lines still go to stderr. `runcomfy ... 2>/dev/null` is fine. * `NO_COLOR=1` and `TERM=dumb` both turn off all color and emoji. * `RUNCOMFY_TOKEN` env wins over the token file. Forgetting to unset it locally after testing CI flows is a common foot-gun. *** ## Verbose / trace logging ```bash theme={null} runcomfy -v run ... # one line per outbound HTTP call runcomfy -vv run ... # add timing RUST_LOG=reqwest=trace runcomfy ... # raw reqwest trace (auth header is redacted) ``` *** ## Checking the version ```bash theme={null} runcomfy --version # runcomfy 0.1.1 (16cc8b8, 2026-04-29) ``` The git short SHA in the parens makes it easy to pin a specific build when filing a bug. If you see `unknown` instead of a sha, you installed from a tarball or `cargo install` — that's fine, just include `--version` output verbatim when reporting issues. *** ## Reporting bugs * Repo: [runcomfy-com/runcomfy-cli](https://github.com/runcomfy-com/runcomfy-cli) * Include: `runcomfy --version` output, full command, full stderr, OS/arch (`uname -a`). # RunComfy APIs Source: https://docs.runcomfy.com/index Choose the right RunComfy API for your use case: on-demand model inference, serverless ComfyUI workflows, serverless LoRA deployments, or AI Toolkit LoRA training. RunComfy provides **four API products**, an **MCP server** for AI assistants, and a **CLI** for the terminal. The APIs share the same high-level flow (submit > get a `request_id` > fetch status/results), but they solve different problems. Deploy ComfyUI workflows as serverless endpoints. Run AI Toolkit LoRA training jobs on GPUs — bring your dataset + YAML config. Deploy LoRAs as serverless endpoints. Run hosted models on-demand with no deployment — pay per request. Connect AI assistants (Claude, Cursor, Windsurf) to your deployments via MCP. Run RunComfy models from your terminal or any AI agent. One command to submit, poll, and download. Import ready-to-use requests for ComfyUI workflows, image and video models, and LoRA training. *** ## Which API should I use? Use this as a quick decision guide: | What you are trying to do | Recommended API | What you call with | Deployment required? | | -------------------------------------------------------------------------------------------------------- | ---------------------------- | ------------------------ | :------------------: | | Train/fine‑tune an **AI Toolkit LoRA** (upload dataset, run training, download artifacts) | **Trainer API** | `dataset_id` + `job_id` | No | | Run a model from the RunComfy **Models catalog** (or a hosted pipeline) | **Model API** | `model_id` | No | | Run inference with a **LoRA** *without deploying anything* | **Model API** | `model_id` + LoRA inputs | No | | Turn a **ComfyUI workflow** into a production endpoint (versions, autoscaling, webhooks, instance proxy) | **Serverless API (ComfyUI)** | `deployment_id` | Yes | | Serve a **LoRA** behind a dedicated, scalable endpoint | **Serverless API (LoRA)** | `deployment_id` | Yes | **One important mental model:**\ Both **Serverless API (LoRA)** and **Serverless API (ComfyUI)** are built on the same serverless deployment system. The difference is *what you deploy* and therefore *what the request schema looks like*. *** ## Getting started * **Model API**: start with **[Quickstart](/model-apis/quickstart)**, then see **[Async Queue Endpoints](/model-apis/async-queue-endpoints)**. * **Serverless API (ComfyUI)**: start with **[Quickstart](/serverless/quickstart)**, then learn about **[Overrides and workflow files](/serverless/workflow-files)**. * **Serverless API (LoRA)**: start with **[Choose a LoRA inference API](/serverless-lora/api-types)**, then follow the **[Quickstart](/serverless-lora/quickstart)**. * **Trainer API**: start with **[Quickstart](/trainer-apis/quickstart)**, then see **[Async Queue Endpoints (Datasets)](/trainer-apis/async-queue-endpoints-datasets)** and **[Async Queue Endpoints (Training Jobs)](/trainer-apis/async-queue-endpoints-training-jobs)**. *** ## Common request pattern Most RunComfy endpoints are **asynchronous**: 1. Submit a job (`POST …`) > get an ID (`request_id`, `job_id`, etc.) 2. Poll status (`GET …/status`) until it completes 3. Fetch outputs (`GET …/result`) or use **webhooks** for push-based updates If you are deploying workflows (Serverless API), you can also manage the deployment lifecycle (create/update/delete) and interact with live instances through the **Instance Proxy**. # Postman collection Source: https://docs.runcomfy.com/integrations/postman Explore RunComfy APIs in Postman: ComfyUI workflows, AI image and video models, and GPU LoRA training. Use the RunComfy Postman collection to discover models, run ComfyUI workflows, generate images and videos, and manage AI Toolkit LoRA training. It includes 20 requests across account and model discovery, Model API, ComfyUI workflow API, and Trainer API. Fork the collection into your own Postman workspace. Browse request examples and their configuration instructions. ## Download and import Download the [collection JSON](/postman/RunComfy.postman_collection.json) and [environment JSON](/postman/RunComfy.postman_environment.json). In Postman, select **Import**, choose both files, and select **RunComfy — local values only** as the active environment. Get an API token from your [RunComfy Profile](https://www.runcomfy.com/profile). Set `runcomfy_api_token` in your private/local environment values. The download contains no API token or account-specific request, deployment, dataset, or training job IDs. Keep shared credential values empty. ## Start with a read-only request Run **Search models**, **Get model input schema**, or **List ComfyUI deployments** to check your setup. Use `model_search` to find a supported family such as Seedance, Wan, FLUX, LTX, or Seedream. Inspect the exact model ID, price, and input schema before configuring a request. The default model example edits an image with FLUX.1 Kontext Pro. Other AI image models and AI video models have their own inputs; select a current model ID and build `model_input_json` from its schema. See the [Model API quickstart](/model-apis/quickstart) and [current models](https://www.runcomfy.com/models). ## Submit once, then check status and results Paid inference and training requests are disabled by default. Review the selected model or deployment, inputs, and price, then set `allow_paid_requests` to `true` locally when you are ready to send one request. After a successful submission, the response script saves its returned ID in the selected environment for the matching status and result requests. Poll status until the request completes, then retrieve its results. Re-sending a submission starts another job and can incur another charge. Dataset creation and uploads are separate requests and do not use the paid-request switch. For ComfyUI, choose an existing deployment and inspect its stored payload before setting workflow overrides. These examples use the current [v2 workflow endpoints](/serverless/async-queue-endpoints). For LoRA training, upload matching media and captions, wait for the dataset to be `READY`, and supply a complete reviewed AI Toolkit YAML configuration and supported GPU type. A `STOPPED` status alone does not prove training completed; inspect its progress and result artifacts. See the [Trainer API quickstart](/trainer-apis/quickstart). # FAQ Source: https://docs.runcomfy.com/mcp/faq Common questions about the RunComfy MCP server. *** ## What deployments can I access? The MCP server uses your API token to call the RunComfy Serverless API. You see exactly the same deployments as on your [Deployments dashboard](https://www.runcomfy.com/comfyui-api/deployments) — both ComfyUI workflow deployments and LoRA deployments. *** ## Which sign-in method should I use? Two ways to authenticate, both tied to an API token from your [Profile](https://www.runcomfy.com/profile): | | API token header | Browser sign-in (OAuth) | | ------------------------------------- | --------------------------------------- | ---------------------------------------------------------------------------------------------------- | | **Works with** | Every client that allows custom headers | Claude.ai, Claude Code, ChatGPT, Hermes Agent, OpenClaw, and other clients using a loopback callback | | **Setup** | Paste the token into your client config | Paste the token once on a RunComfy page | | **Token stored in your config file?** | Yes | No | | **Best for** | Quickest setup, CI, any client | Shared machines, or keeping tokens out of dotfiles | If you're unsure, use the **API token header** — it's one command and works with every client that allows custom headers. ChatGPT is the exception: it allows no custom headers, so it must use browser sign-in. *** ## How is my API token handled? It depends on which sign-in method you use. **API token header** — your token is sent on each request, verified against `api.runcomfy.net`, and forwarded upstream for that request only. It is never written to disk by the MCP server and never logged. **Browser sign-in** — your token is verified, then stored **encrypted** in the OAuth grant so the server can act on your behalf later. Your MCP client never receives it; it gets a separate, short-lived access token instead. Either way, every tool call is re-checked against RunComfy before it runs, so **regenerating the token in your Profile revokes access immediately** — for the header and for any browser sign-in built on it. *** ## What does it cost? The MCP server itself is **free**. You pay only for the RunComfy resources you use: * **Inference requests** are billed the same as calling the Serverless API directly * **Deployments with `min_instances > 0`** incur GPU uptime charges even when idle * **Deployments with `min_instances = 0`** cost nothing when idle (scale-to-zero) See **[Serverless API Billing](/serverless/about-billing)** for pricing details. *** ## How do I pass images or videos as inputs? When a workflow node requires an image, video, or audio file, pass the input directly in the `overrides` object of `submit_request`: * **Public HTTPS URL** (recommended): `"image": "https://example.com/photo.jpg"` — use a URL that returns the raw file without authentication * **Base64 data URI**: `"image": "data:image/jpeg;base64,/9j/4AAQ..."` — for inline content No separate upload step is needed. See **[Async Queue Endpoints](/serverless/async-queue-endpoints)** for more details and examples. *** ## Are there rate limits? Your requests are subject to the same rate limits as the RunComfy Serverless API. The MCP server adds one limit of its own: **600 token-authenticated requests per minute per IP address**. This exists to stop token guessing and is far above normal assistant use — if you hit it you'll get a `429`, and waiting a minute clears it. *** ## How do I find node IDs for my workflow? Call `get_deployment` with `include_payload=true`. The response includes a `payload_summary` with every node's ID, class type, and input names. Use these to build the `overrides` object for `submit_request`. For example, if the summary shows `node_id: "6"` with `class_type: "CLIPTextEncode"` and `input_names: ["text", "clip"]`, your override would be: ```json theme={null} { "6": { "inputs": { "text": "your prompt here" } } } ``` For more context on workflow files and node schemas, see **[Workflow Files](/serverless/workflow-files)**. *** ## Can I use a different workflow without redeploying? Yes. The `submit_request` tool accepts an optional `workflow_api_json` parameter that lets you send a full ComfyUI workflow inline. The deployment's stored workflow is bypassed for that request. This is useful for testing workflow changes before updating the deployment. See **[Async Queue Endpoints — Send dynamic workflow](/serverless/async-queue-endpoints#send-dynamic-workflow-or-any-workflow_apijson)** for details. *** ## Does it work with ChatGPT? Yes, using browser sign-in. ChatGPT does not support custom headers on MCP connectors, so the API token header is not an option there — OAuth is the only way in. At **chatgpt.com** (not the desktop app), turn on **Developer mode** under **Settings → Security and login**, then open **Plugins**, select **+**, and create a developer-mode app pointing at `https://mcp.runcomfy.com/mcp` with **OAuth** authentication. A RunComfy page opens where you paste an API token from your [Profile](https://www.runcomfy.com/profile) once. **Not available in the Mac app or on iPhone.** Adding a custom MCP server needs **Developer mode**, which only exists at chatgpt.com and in the Windows app. A connector added on the web syncs to the Mac app and shows up in settings, but cannot be switched on there. This is an OpenAI platform limit, not a RunComfy one. **No "Connectors" in your settings?** OpenAI renamed it in July 2026 — **Connectors** is now **Plugins**, and Developer mode moved out of *Connectors → Advanced* into *Settings → Security and login*. RunComfy's tools are for managing deployments and running inference. They are not the `search` and `fetch` pair that ChatGPT's **deep research** feature specifically requires, so use the connector from normal chat or developer mode rather than deep research. *** ## Why does my client need a "loopback" redirect URI? Browser sign-in is open to any local client that receives its callback on `http://localhost`, `http://127.0.0.1`, or `http://[::1]` — that covers Claude Code, Hermes Agent, OpenClaw, and most desktop MCP clients. Hosted clients that receive the callback on their own servers must be allowlisted individually — currently Claude.ai and ChatGPT. This keeps someone from registering a lookalike client, sending you a link to a genuine-looking RunComfy consent page, and collecting your token. A loopback callback can only ever deliver to your own machine. If a client can't do either, use the API token header instead — it works with every Streamable HTTP client. *** ## Where can I learn more about the Serverless API? The MCP tools map directly to the Serverless API endpoints: * **[Serverless API Introduction](/serverless/introduction)** — Key concepts: workflows, deployments, requests, instances * **[Core Concepts](/serverless/core-concepts)** — Overrides, workflow files, scaling * **[Async Queue Endpoints](/serverless/async-queue-endpoints)** — The inference API the MCP wraps * **[Deployment Endpoints](/serverless/deployment-endpoints)** — The deployment management API # Introduction Source: https://docs.runcomfy.com/mcp/introduction **RunComfy MCP** gives AI assistants direct access to your Serverless API (ComfyUI) deployments. Connect Claude Code, Claude.ai, ChatGPT, Cursor, VS Code, Windsurf, Hermes Agent, OpenClaw, or any MCP-compatible client and manage deployments, run inference, and retrieve results — all from natural language. **MCP endpoint**: `https://mcp.runcomfy.com/mcp` **Transport**: Streamable HTTP **Authentication**: an `Authorization: Bearer` API token, or browser sign-in Already have your API token? Grab it from your **[RunComfy Profile](https://www.runcomfy.com/profile)** — then the whole setup is one command: ```bash theme={null} claude mcp add --transport http runcomfy https://mcp.runcomfy.com/mcp --header "Authorization: Bearer YOUR_RUNCOMFY_TOKEN" ``` Then try it straight away — ask your assistant: > "List my RunComfy deployments" If you get back deployment names and IDs, you're connected. See the **[Quickstart](/mcp/quickstart)** for every other client. *** ## What you get With the RunComfy MCP server, your AI assistant can: * **List and inspect deployments** in your account, including workflow graphs and node schemas * **Create, update, and delete deployments** with full control over hardware and autoscaling * **Run inference** on any deployment using the async queue (submit, poll, fetch results) * **Cancel queued or running requests** to stop unnecessary GPU usage * **Call ComfyUI backend endpoints** on live instances via the instance proxy (e.g., free memory, unload models) *** ## Available tools The MCP server exposes **31 tools** spanning three RunComfy products, plus your account balance. ### Serverless API (ComfyUI) — deployment management | Tool | Description | | ------------------- | ------------------------------------------------------------------------- | | `list_deployments` | List all deployments in your account | | `get_deployment` | Get a deployment's details, including its workflow graph and node schemas | | `create_deployment` | Create a new deployment from a cloud-saved ComfyUI workflow | | `update_deployment` | Update a deployment's hardware, scaling, or enabled status | | `delete_deployment` | Permanently delete a deployment | ### Serverless API (ComfyUI) — inference | Tool | Description | | -------------------- | ------------------------------------------------------------------------ | | `submit_request` | Submit an async inference request to a deployment | | `get_request_status` | Poll a request's current status (`in_queue`, `in_progress`, `completed`) | | `get_request_result` | Fetch the final outputs (hosted URLs) of a completed request | | `cancel_request` | Cancel a queued or running request | ### Serverless API (ComfyUI) — advanced | Tool | Description | | --------------------- | -------------------------------------------------------------------------------------- | | `call_instance_proxy` | Call a ComfyUI backend endpoint on a live instance (e.g., `api/free` to unload models) | ### [Model API](/model-apis/quickstart) — hosted models, no deployment | Tool | Description | | -------------------------- | ----------------------------------------------------------------- | | `list_models` | Browse hosted models by keyword, capability, or price | | `get_model` | Get one model's input schema — types, defaults, enums, and ranges | | `list_model_categories` | List the capability categories to filter by | | `run_model` | Run a hosted model on demand by `model_id` | | `get_model_request_status` | Poll a Model API request's status | | `get_model_request_result` | Fetch a completed Model API request's outputs | | `cancel_model_request` | Cancel a queued Model API request | ### [Trainer API](/trainer-apis/introduction) — datasets | Tool | Description | | ------------------------------ | ------------------------------------------- | | `create_dataset` | Create an empty training dataset | | `list_datasets` | List datasets in your account | | `get_dataset_status` | Check a dataset's status and uploaded files | | `delete_dataset` | Permanently delete a dataset | | `upload_dataset_file_from_url` | Add a file to a dataset from a public URL | | `upload_dataset_text_file` | Write a caption straight into a dataset | | `get_dataset_upload_urls` | Get signed URLs for local or >150 MB files | ### [Trainer API](/trainer-apis/introduction) — training jobs | Tool | Description | | ------------------------- | ----------------------------------------------- | | `submit_training_job` | Submit an AI Toolkit LoRA training job | | `get_training_job_status` | Poll a job's status and step progress | | `get_training_job_result` | Fetch checkpoints, resolved config, and samples | | `cancel_training_job` | Cancel a queued or running job | | `resume_training_job` | Resume a stopped job from its latest checkpoint | | `edit_training_job` | Replace a non-running job's config | ### Account | Tool | Description | | ------------- | ------------------------------------------------------------ | | `get_balance` | Get the account's remaining balance, in USD and microdollars | These compose: `list_models` finds a model, `get_model` shows what it takes, `run_model` runs it — and a LoRA from `get_training_job_result` can be passed straight to `run_model` without deploying anything. *** ## Examples Here are typical workflows an AI assistant performs with the RunComfy MCP: ### Generate an image > "Generate an image of a mountain landscape using my Flux deployment" 1. The assistant calls `list_deployments` to find your deployments 2. It calls `get_deployment` with `include_payload=true` to inspect the workflow's node IDs and input names 3. It calls `submit_request` with the appropriate `overrides` (e.g., `{"6": {"inputs": {"text": "a mountain landscape at sunset"}}}`) 4. It calls `get_request_result` to fetch the output image URL ### Create and run a new deployment > "Deploy my upscaler workflow and run it on this image" 1. The assistant calls `create_deployment` with your `workflow_id`, `workflow_version`, and hardware choice 2. It calls `submit_request` on the new deployment with image input as a public URL in overrides 3. It polls `get_request_status` until the job completes 4. It calls `get_request_result` to return the upscaled image URL ### Check and cancel a running job > "What's the status of my last request? Cancel it if it's still queued." 1. The assistant calls `get_request_status` with the `deployment_id` and `request_id` 2. If the status is `in_queue`, it calls `cancel_request` 3. The cancel response confirms `cancelled` or `not_cancellable` (if already running) *** ## How it works 1. **Your AI assistant** sends MCP tool calls to `https://mcp.runcomfy.com/mcp`, authenticating either with your API token in the `Authorization: Bearer` header or with a token from browser sign-in. 2. **The MCP server** verifies the credential against RunComfy on every request, then translates the tool call into a RunComfy Serverless API request (`api.runcomfy.net`) as you — so you see only your deployments and billing is attributed to your account. 3. **Results** flow back to the assistant as structured JSON with output URLs, status fields, and metadata. Because every call is re-checked upstream, regenerating your token in your [Profile](https://www.runcomfy.com/profile) revokes access immediately. See **[How is my API token handled?](/mcp/faq#how-is-my-api-token-handled)** for what is and isn't stored. ### File inputs When a workflow requires image, video, or audio inputs, pass them directly in the `overrides`: * **Public URL**: `"image": "https://example.com/photo.jpg"` * **Base64 data URI**: `"image": "data:image/jpeg;base64,/9j/4AAQ..."` No separate file upload step is needed. *** ## Next steps * **[Quickstart](/mcp/quickstart)** — Set up the MCP server in your AI assistant, plus troubleshooting * **[Tool Reference](/mcp/tool-reference)** — Detailed parameters and examples for all 31 tools * **[FAQ](/mcp/faq)** — Common questions about the MCP server # Quickstart Source: https://docs.runcomfy.com/mcp/quickstart Connect the RunComfy MCP server to your AI assistant and make your first tool call. *** ## 1. Get your API token Copy your API token from your [Profile](https://www.runcomfy.com/profile) page. Copy the **whole** token. A truncated token is the most common cause of a `401` — the server can't tell a partial token from a wrong one. *** ## 2. Connect your client Run this in your terminal, replacing `YOUR_RUNCOMFY_TOKEN`: ```bash theme={null} claude mcp add --transport http runcomfy https://mcp.runcomfy.com/mcp --header "Authorization: Bearer YOUR_RUNCOMFY_TOKEN" ``` Then confirm it worked: ```bash theme={null} claude mcp list ``` You should see `runcomfy: ... - ✓ Connected`. The transport is `http`, not `streamable-http`. Claude Code names the Streamable HTTP transport `http`, and rejects any other value before it ever contacts the server. Flags also need two dashes — `-transport` and `-header` are not valid. **Want it in every project?** By default the server is added only to the current directory. Add `--scope user` to make it available everywhere: ```bash theme={null} claude mcp add --scope user --transport http runcomfy https://mcp.runcomfy.com/mcp --header "Authorization: Bearer YOUR_RUNCOMFY_TOKEN" ``` **Prefer not to keep a token in your config?** Omit the header and sign in through your browser instead: ```bash theme={null} claude mcp add --transport http runcomfy https://mcp.runcomfy.com/mcp ``` Then run `/mcp` inside Claude Code, choose **runcomfy**, and select **Authenticate**. A RunComfy page opens where you paste your token once. No token in a config file — Claude.ai signs in through your browser. 1. Go to **Settings → Connectors → Add custom connector**. 2. Enter the URL `https://mcp.runcomfy.com/mcp`. 3. Select **Connect**. 4. A RunComfy consent page opens. Paste an API token from your [Profile](https://www.runcomfy.com/profile) and select **Authorize**. Your token is verified by RunComfy and stored encrypted in the authorization grant. Claude never receives it. To disconnect, remove the connector in Claude, or regenerate the token in your RunComfy Profile. **Using the Mac app or iPhone?** You can't add a connector there. RunComfy will appear in your settings once added, but there's no way to switch it on — Developer mode doesn't exist on those platforms. Add it at **chatgpt.com** or in the **Windows app** instead. ChatGPT allows no custom headers, so it signs in through your browser rather than taking an API token in a config file. Adding any custom MCP server requires **Developer mode**, on a Plus, Pro, Business, Enterprise or Edu plan. At **chatgpt.com** or in the **Windows app**: 1. **Settings → Security and login** → turn on **Developer mode** 2. **Plugins → +** → create an app for `https://mcp.runcomfy.com/mcp`, authentication **OAuth** 3. Paste your [Profile](https://www.runcomfy.com/profile) token on the RunComfy page that opens Where it works once added: | | Add it | Use it | | ----------- | :----: | :----: | | chatgpt.com | Yes | Yes | | Windows app | Yes | Yes | | Mac app | No | No | | iPhone | No | No | Following an older guide? OpenAI renamed **Connectors** to **Plugins** in July 2026 and moved Developer mode out of *Connectors → Advanced* into *Settings → Security and login*. RunComfy's tools manage deployments and run inference — they aren't the `search` and `fetch` pair ChatGPT's **deep research** feature requires, so use RunComfy from a normal conversation. If these limits are a problem, every other client on this page takes an API token directly and works on any platform. Add this to `.cursor/mcp.json` (create the file if it doesn't exist): ```json theme={null} { "mcpServers": { "runcomfy": { "url": "https://mcp.runcomfy.com/mcp", "headers": { "Authorization": "Bearer YOUR_RUNCOMFY_TOKEN" } } } } ``` For a global setup, use `~/.cursor/mcp.json` instead of the project file. Add this to `.vscode/mcp.json`: ```json theme={null} { "servers": { "runcomfy": { "type": "http", "url": "https://mcp.runcomfy.com/mcp", "headers": { "Authorization": "Bearer YOUR_RUNCOMFY_TOKEN" } } } } ``` Go to **Windsurf Settings → MCP** and add: ```json theme={null} { "mcpServers": { "runcomfy": { "serverUrl": "https://mcp.runcomfy.com/mcp", "headers": { "Authorization": "Bearer YOUR_RUNCOMFY_TOKEN" } } } } ``` Hermes reads its MCP servers from `~/.hermes/config.yaml`. Add RunComfy under `mcp_servers`: ```yaml theme={null} mcp_servers: runcomfy: url: "https://mcp.runcomfy.com/mcp" headers: Authorization: "Bearer YOUR_RUNCOMFY_TOKEN" enabled: true ``` A `url` with no `command` is a remote server, so Hermes uses Streamable HTTP — there is no separate transport field to set. Then check it connects: ```bash theme={null} hermes mcp test runcomfy ``` You should see `✓ Connected` and `✓ Tools discovered: 10`. Expect the connect to take 5–8 seconds — the server enumerates tools, resources and prompts on first contact. Inside a running session, reload the config without restarting: ``` /reload-mcp ``` **Keep the token out of the config file.** Put it in `~/.hermes/.env` and reference it instead: ```yaml theme={null} mcp_servers: runcomfy: url: "https://mcp.runcomfy.com/mcp" headers: Authorization: "Bearer ${env:RUNCOMFY_API_TOKEN}" ``` Hermes prints `RUNCOMFY_API_TOKEN is not set (check ~/.hermes/.env); keeping the literal placeholder` on every command even when the variable **is** set and resolving correctly. It's a cosmetic bug — if `hermes mcp test runcomfy` reports `✓ Connected`, the token resolved. To confirm for yourself, put a wrong value in `.env` and watch the same command fail. **Prefer browser sign-in?** Hermes registers a loopback callback, so it can use OAuth instead of a token in the file: ```yaml theme={null} mcp_servers: runcomfy: url: "https://mcp.runcomfy.com/mcp" auth: oauth ``` Then run `hermes mcp login runcomfy` and paste your token once on the RunComfy page that opens. Hermes stores the resulting grant in `~/.hermes/mcp-tokens/runcomfy.json` and refreshes it for you. If you'd rather Hermes only sees a few of the tools, narrow the list with an allowlist rather than an allow-everything default: ```yaml theme={null} mcp_servers: runcomfy: url: "https://mcp.runcomfy.com/mcp" headers: Authorization: "Bearer ${env:RUNCOMFY_API_TOKEN}" tools: include: [list_deployments, get_deployment, submit_request, get_request_status, get_request_result] ``` `hermes mcp list` then shows `5 selected` instead of `all`. Note that `hermes mcp test` still lists everything the server offers — it reports what's discoverable, not what's exposed to the agent. Add RunComfy in one command: ```bash theme={null} openclaw mcp set runcomfy '{"url":"https://mcp.runcomfy.com/mcp","transport":"streamable-http","headers":{"Authorization":"Bearer YOUR_RUNCOMFY_TOKEN"}}' ``` `mcp set` writes the entry without connecting, so confirm it works: ```bash theme={null} openclaw mcp probe runcomfy ``` You should see `runcomfy: 10 tools, resources, prompts`. The entry is written to `mcp.servers.runcomfy` in `~/.openclaw/openclaw.json`, so you can also edit it by hand: ```json theme={null} { "mcp": { "servers": { "runcomfy": { "url": "https://mcp.runcomfy.com/mcp", "transport": "streamable-http", "headers": { "Authorization": "Bearer YOUR_RUNCOMFY_TOKEN" } } } } } ``` **Prefer browser sign-in?** Drop the header and let OpenClaw run the OAuth flow — it registers a loopback callback, so no allowlisting is needed: ```bash theme={null} openclaw mcp set runcomfy '{"url":"https://mcp.runcomfy.com/mcp","transport":"streamable-http","auth":"oauth"}' ``` Paste your [Profile](https://www.runcomfy.com/profile) token once on the RunComfy page that opens. **Prefer flags to JSON?** `openclaw mcp add` takes the same settings, but note the header is `key=value` — not the `Key: value` form other clients use — and that `add` probes the server before saving: ```bash theme={null} openclaw mcp add runcomfy --url https://mcp.runcomfy.com/mcp --transport streamable-http --header "Authorization=Bearer YOUR_RUNCOMFY_TOKEN" --timeout 10 ``` The `--timeout 10` is required in practice. `add`'s built-in probe gives up after about 5 seconds, and RunComfy's first handshake takes longer than that, so the command fails with `MCP error -32001: Request timed out` and saves nothing. `mcp set` doesn't probe, so it never hits this. Timeouts can be set two ways, and both are stored as written: the flags above take **seconds** (`connectTimeout`, `timeout`), while `mcp set` JSON takes **milliseconds** (`connectionTimeoutMs`, `requestTimeoutMs`). `openclaw mcp status --verbose` reports the resolved values in ms, so use it to check what actually applied. Defaults are `connect=30000ms request=60000ms`. Useful checks: `openclaw mcp list` for configured servers, `openclaw mcp status --verbose` for transport and timeouts without connecting, and `openclaw mcp doctor runcomfy --probe` when something's wrong. `openclaw mcp show runcomfy --json` prints your token **in full** — it is not redacted. Don't paste that output into a bug report. `mcp list` and `mcp status` don't print headers at all and are safe to share. Any client that speaks **Streamable HTTP** can connect: | Setting | Value | | -------------- | ------------------------------------------------------------ | | Server URL | `https://mcp.runcomfy.com/mcp` | | Transport | Streamable HTTP | | Authentication | `Authorization: Bearer YOUR_RUNCOMFY_TOKEN` on every request | Clients that run a browser OAuth flow can instead connect with no token in the config, as long as they register a **loopback** redirect URI (`http://localhost:PORT/...`). Hosted clients need their callback allowlisted — currently Claude.ai and ChatGPT. See [Which sign-in method should I use?](/mcp/faq#which-sign-in-method-should-i-use). You can check the endpoint is reachable before configuring anything: ```bash theme={null} curl -s -o /dev/null -w "%{http_code}\n" \ -X POST https://mcp.runcomfy.com/mcp \ -H "Authorization: Bearer YOUR_RUNCOMFY_TOKEN" \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}' ``` `200` means your token works. `401` means it was rejected — the response body says why. *** ## 3. Verify the connection Ask your assistant: > "List my RunComfy deployments" It will call `list_deployments`. If you get back deployment names and IDs, you're connected. *** ## 4. Your first inference With a deployment available, try: > "Run my \[deployment name] with the prompt: a futuristic cityscape at sunset" The assistant will: 1. Call `get_deployment` to inspect the workflow's node IDs 2. Call `submit_request` with the appropriate overrides 3. Call `get_request_status` to poll progress 4. Call `get_request_result` to return the output URL *** ## Troubleshooting The token reached the server and RunComfy did not recognize it. Almost always one of: * **Truncated on copy.** Re-copy the whole token from your [Profile](https://www.runcomfy.com/profile). * **Regenerated.** Generating a new token invalidates the old one everywhere. Update every client that used it. * **Extra characters.** Quotes, a trailing space, or a line break pasted along with the token. Check the token on its own before blaming the client: ```bash theme={null} curl -s -o /dev/null -w "%{http_code}\n" \ -H "Authorization: Bearer YOUR_RUNCOMFY_TOKEN" \ https://api.runcomfy.net/prod/v2/deployments ``` `200` means the token is good and the problem is in your client config. `401` means the token itself is bad. No `Authorization` header arrived. The header has to be a single argument, so keep it quoted: ```bash theme={null} --header "Authorization: Bearer YOUR_RUNCOMFY_TOKEN" ``` Without the quotes the shell splits it at the space and the header is dropped. If you see an error about the transport, or the flags aren't recognized, check the command shape: * Transport is `http` — **not** `streamable-http`. * Flags take two dashes — `--transport`, `--header`, not `-transport`, `-header`. * The order is `claude mcp add [flags] NAME URL`. The working command is: ```bash theme={null} claude mcp add --transport http runcomfy https://mcp.runcomfy.com/mcp --header "Authorization: Bearer YOUR_RUNCOMFY_TOKEN" ``` `claude mcp add` defaults to **local** scope — the current directory only. If you moved to another project, re-add it with `--scope user` so it follows you everywhere. ```bash theme={null} claude mcp add --scope user --transport http runcomfy https://mcp.runcomfy.com/mcp --header "Authorization: Bearer YOUR_RUNCOMFY_TOKEN" ``` Hermes loads `~/.hermes/config.yaml` at startup. After editing it, run `/reload-mcp` in the session, or restart the gateway. If it reloads but the tools still aren't there, check for a `tools.include` allowlist on the entry — anything not listed is hidden, including tools added since you wrote the list. `hermes mcp list` shows `N selected` when a filter is active and `all` when it isn't. Ignore the `RUNCOMFY_API_TOKEN is not set` warning if you're using `${env:...}` — it prints even when the variable resolves fine. Probe it — this connects for real and reports what came back: ```bash theme={null} openclaw mcp doctor runcomfy --probe ``` If the failure is `MCP error -32001: Request timed out` within a few seconds of running `openclaw mcp add`, it's the probe deadline, not the network — re-run with `--timeout 10`, or use `openclaw mcp set`, which saves without probing. Otherwise check two things in the entry: * `transport` is `streamable-http`. RunComfy does not serve the older SSE transport. * `url` is exactly `https://mcp.runcomfy.com/mcp`, with no trailing slash. ```bash theme={null} openclaw mcp show runcomfy --json ``` That output contains your token in the clear — redact it before sharing. To test the token on its own, use the `curl` check in **401 — RunComfy rejected this API token** above. The URL must be exactly `https://mcp.runcomfy.com/mcp`, with no trailing slash. The access token is bound to that exact address, so `.../mcp/` is treated as a different resource. Your client registered a redirect URI that isn't a loopback address and isn't on the allowlist of hosted clients. Use the API token header instead — it works with every Streamable HTTP client. If you maintain a hosted MCP client and want its callback allowlisted, email [hi@runcomfy.com](mailto:hi@runcomfy.com). RunComfy's API couldn't be reached to verify your token. This is temporary — retry after the number of seconds in the `Retry-After` header. More than 600 token-authenticated requests in a minute from one IP address. Normal assistant use never reaches this. Wait a minute and retry. Still stuck? Email [hi@runcomfy.com](mailto:hi@runcomfy.com) with the exact error message and the client you're using. *** ## Next steps * **[Tool Reference](/mcp/tool-reference)** — Detailed parameters and examples for all 31 tools * **[FAQ](/mcp/faq)** — Common questions * **[Serverless API docs](/serverless/introduction)** — Understand deployments, workflows, and the async queue # Tool Reference Source: https://docs.runcomfy.com/mcp/tool-reference Detailed reference for the 31 tools exposed by the RunComfy MCP server. Each maps directly to a RunComfy API endpoint. | Group | Tools | | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Deployment management](#deployment-management) | `list_deployments`, `get_deployment`, `create_deployment`, `update_deployment`, `delete_deployment` | | [Inference](#inference) | `submit_request`, `get_request_status`, `get_request_result`, `cancel_request` | | [Advanced](#advanced) | `call_instance_proxy` | | [Model catalog](#model-catalog) | `list_models`, `get_model`, `list_model_categories` | | [Model inference](#model-inference) | `run_model`, `get_model_request_status`, `get_model_request_result`, `cancel_model_request` | | [Training datasets](#training-datasets) | `create_dataset`, `list_datasets`, `get_dataset_status`, `delete_dataset`, `upload_dataset_file_from_url`, `upload_dataset_text_file`, `get_dataset_upload_urls` | | [Training jobs](#training-jobs) | `submit_training_job`, `get_training_job_status`, `get_training_job_result`, `cancel_training_job`, `resume_training_job`, `edit_training_job` | | [Account](#account) | `get_balance` | The first three groups back the **[Serverless API (ComfyUI)](/serverless/introduction)**, the next two the **[Model API](/model-apis/quickstart)**, and the training groups the **[Trainer API](/trainer-apis/introduction)**. Serverless and Model API requests have similarly named tools. The Serverless ones (`get_request_status`, `get_request_result`, `cancel_request`) always take a `deployment_id`; the Model API ones (`get_model_request_status`, and so on) take only a `request_id`. Mixing them up yields a 404. *** ## Deployment management ### `list_deployments` List all Serverless API deployments in your account. **Backs**: `GET /prod/v2/deployments` | Parameter | Type | Required | Description | | ----------------- | --------- | :------: | --------------------------------------------------------------- | | `ids` | string\[] | No | Filter to specific deployment IDs | | `include_payload` | boolean | No | Include `workflow_api_json`, `overrides`, and `object_info_url` | | `include_readme` | boolean | No | Include the deployment's README markdown | **Example arguments:** ```json theme={null} {} ``` **Example response (structuredContent):** ```json theme={null} { "ok": true, "deployments": [ { "id": "a1b2c3d4-...", "name": "text-to-image", "workflow_id": "00000000-...", "workflow_version": "v1", "hardware": ["AMPERE_48"], "min_instances": 0, "max_instances": 1, "status": "standby", "is_enabled": true } ] } ``` *** ### `get_deployment` Get one deployment by ID, optionally including its full workflow graph. **Backs**: `GET /prod/v2/deployments/{deployment_id}` | Parameter | Type | Required | Description | | ----------------- | ------- | :------: | ------------------------------------------------------------------------------------------------- | | `deployment_id` | string | Yes | The deployment's UUID | | `include_payload` | boolean | No | Include `workflow_api_json` and node schemas — use this to discover node IDs for `submit_request` | | `include_readme` | boolean | No | Include the deployment's README | > Tip: Call with `include_payload=true` to see every node's ID and input names. Use those to build the `overrides` object for `submit_request`. **Example arguments:** ```json theme={null} { "deployment_id": "a1b2c3d4-...", "include_payload": true } ``` *** ### `create_deployment` Create a new Serverless API (ComfyUI) deployment from a cloud-saved workflow. **Backs**: `POST /prod/v2/deployments` | Parameter | Type | Required | Default | Description | | ------------------------------- | ------- | :------: | ------------- | -------------------------------------------- | | `name` | string | Yes | — | Human-readable name | | `workflow_id` | string | Yes | — | UUID of the ComfyUI workflow | | `workflow_version` | string | Yes | — | Version label (e.g., `"v1"`) | | `hardware` | string | No | `"AMPERE_48"` | GPU SKU (see hardware table below) | | `min_instances` | integer | No | `0` | Warm instance floor (0–30). Billable if > 0. | | `max_instances` | integer | No | `1` | Concurrency ceiling (1–60) | | `queue_size` | integer | No | `1` | Pending requests per instance before scaling | | `keep_warm_duration_in_seconds` | integer | No | `60` | Idle timeout before scale-down | **Hardware SKUs:** ``` TURING_16 | AMPERE_24 | AMPERE_48 | ADA_48_PLUS AMPERE_80 | ADA_80_PLUS | HOPPER_141 ``` **Example arguments:** ```json theme={null} { "name": "my-flux-deployment", "workflow_id": "00000000-0000-0000-0000-000000001234", "workflow_version": "v1", "hardware": "AMPERE_48", "min_instances": 0, "max_instances": 2 } ``` > For LoRA deployments, create via the RunComfy UI (Trainer > LoRA Assets > Deploy), then use `list_deployments` to get the `deployment_id`. *** ### `update_deployment` Partially update a deployment. Only pass the fields you want to change. **Backs**: `PATCH /prod/v2/deployments/{deployment_id}` | Parameter | Type | Required | Description | | ------------------------------- | ------- | :------: | ---------------------------------- | | `deployment_id` | string | Yes | The deployment's UUID | | `name` | string | No | New name | | `workflow_version` | string | No | New version label | | `hardware` | string | No | New GPU SKU | | `min_instances` | integer | No | New warm floor | | `max_instances` | integer | No | New concurrency ceiling | | `queue_size` | integer | No | New queue threshold | | `keep_warm_duration_in_seconds` | integer | No | New idle timeout | | `is_enabled` | boolean | No | `false` to pause, `true` to resume | **Example — pause a deployment:** ```json theme={null} { "deployment_id": "a1b2c3d4-...", "is_enabled": false } ``` *** ### `delete_deployment` Permanently delete a deployment. This cannot be undone. **Backs**: `DELETE /prod/v2/deployments/{deployment_id}` | Parameter | Type | Required | Description | | --------------- | ------ | :------: | --------------------- | | `deployment_id` | string | Yes | The deployment's UUID | > Consider `update_deployment` with `is_enabled=false` to pause instead of deleting. *** ## Inference ### `submit_request` Submit an async inference request to a deployment. **Backs**: `POST /prod/v2/deployments/{deployment_id}/inference` | Parameter | Type | Required | Description | | ----------------------------- | ------- | :------: | ------------------------------------------------------------------------- | | `deployment_id` | string | Yes | Target deployment | | `overrides` | object | No | Partial workflow graph keyed by node ID (see example below) | | `workflow_api_json` | object | No | Advanced: run a different workflow inline without updating the deployment | | `extra_data` | object | No | E.g., `{"api_key_comfy_org": "comfyui-..."}` for ComfyUI Core API nodes | | `webhook_url` | string | No | URL for push-based status updates | | `webhook_intermediate_status` | boolean | No | Fire webhooks on every status change, not just terminal | | `wait_for_completion` | boolean | No | If `true`, poll until done and return the result inline | | `timeout_seconds` | integer | No | Max wait (default 300) when `wait_for_completion=true` | **Example — text-to-image with overrides:** ```json theme={null} { "deployment_id": "a1b2c3d4-...", "overrides": { "6": { "inputs": { "text": "a futuristic cityscape at sunset" } }, "31": { "inputs": { "seed": 42 } } } } ``` **File inputs** — pass a public URL or Base64 data URI directly in the overrides value: ```json theme={null} { "deployment_id": "a1b2c3d4-...", "overrides": { "189": { "inputs": { "image": "https://example.com/input-photo.jpg" } } } } ``` Or using Base64: ```json theme={null} { "deployment_id": "a1b2c3d4-...", "overrides": { "189": { "inputs": { "image": "data:image/jpeg;base64,/9j/4AAQ..." } } } } ``` > Use `get_deployment` with `include_payload=true` to discover the node IDs and input names for your workflow. *** ### `get_request_status` Poll a request's current status. **Backs**: `GET /prod/v2/deployments/{deployment_id}/requests/{request_id}/status` | Parameter | Type | Required | Description | | --------------- | ------ | :------: | ------------------------------------------- | | `deployment_id` | string | Yes | The deployment that owns this request | | `request_id` | string | Yes | The request ID returned by `submit_request` | Status lifecycle: `in_queue` > `in_progress` > `completed` (or `cancelled`). **Example response:** ```json theme={null} { "ok": true, "status": { "request_id": "5f1ba692-...", "status": "in_progress", "queue_position": null, "instance_id": "1697cb1a-..." } } ``` *** ### `get_request_result` Fetch the final outputs of a completed request. **Backs**: `GET /prod/v2/deployments/{deployment_id}/requests/{request_id}/result` | Parameter | Type | Required | Description | | --------------- | ------ | :------: | ------------------------------------- | | `deployment_id` | string | Yes | The deployment that owns this request | | `request_id` | string | Yes | The request ID | Output URLs are hosted for **7 days** after success. Download or copy them to your own storage for longer retention. **Example response:** ```json theme={null} { "ok": true, "result": { "request_id": "5f1ba692-...", "status": "succeeded", "outputs": { "9": { "images": [ { "url": "https://serverless-api-storage.runcomfy.net/.../ComfyUI_00001_.png", "filename": "ComfyUI_00001_.png" } ] } }, "created_at": "2026-04-16T03:40:52.093Z", "finished_at": "2026-04-16T03:44:18.401Z" }, "output_urls": [ { "node_id": "9", "channel": "images", "url": "https://serverless-api-storage.runcomfy.net/.../ComfyUI_00001_.png", "filename": "ComfyUI_00001_.png" } ] } ``` *** ### `cancel_request` Cancel a queued or running request. **Backs**: `POST /prod/v2/deployments/{deployment_id}/requests/{request_id}/cancel` | Parameter | Type | Required | Description | | --------------- | ------ | :------: | ------------------------------------- | | `deployment_id` | string | Yes | The deployment that owns this request | | `request_id` | string | Yes | The request ID | Returns `cancelled` if accepted, or `not_cancellable` if the request has already completed. **Example response:** ```json theme={null} { "ok": true, "cancel": { "request_id": "5f1ba692-...", "status": "completed", "outcome": "cancelled" } } ``` *** ## Advanced ### `call_instance_proxy` Call a ComfyUI backend endpoint on a live instance. **Backs**: `POST /prod/v2/deployments/{deployment_id}/instances/{instance_id}/proxy/{comfy_backend_path}` | Parameter | Type | Required | Description | | -------------------- | ------ | :------: | ----------------------------------------------------------------------------- | | `deployment_id` | string | Yes | The deployment | | `instance_id` | string | Yes | The running instance (from `get_request_status` when status is `in_progress`) | | `comfy_backend_path` | string | Yes | The ComfyUI backend route, e.g., `api/free` | | `request_body` | object | No | JSON body to send to the ComfyUI endpoint | **Example — unload models to free GPU memory:** ```json theme={null} { "deployment_id": "a1b2c3d4-...", "instance_id": "1697cb1a-...", "comfy_backend_path": "api/free", "request_body": { "unload_models": true, "free_memory": true } } ``` > Instance IDs are ephemeral — they are only valid while the instance is running. If the instance shuts down, submit a new request to get a fresh instance. *** ## Model catalog Discovery for the **[Model API](/model-apis/quickstart)** — hosted models you run on demand, with no deployment. Backs the **[Model Catalog Endpoints](/model-apis/model-catalog-endpoints)**. ### `list_models` Browse the hosted models `run_model` can run. **Backs**: `GET /v1/models` | Parameter | Type | Required | Description | | ---------------- | ------- | :------: | ------------------------------------------------------------------------------------------------- | | `search` | string | No | Case-insensitive match on id, display name, or description, e.g. `kontext`, `upscale`, `lip sync` | | `category` | string | No | Capability filter, e.g. `text-to-image`, `image-to-video` | | `kind` | string | No | Execution filter: `model`, `workflow`, or `inference` | | `include_schema` | boolean | No | Return each model's full `input_schema` inline. Much larger response. | | `limit` | integer | No | Page size, 1–500 (default 100) | | `offset` | integer | No | Rows to skip (default 0) | > `category` is what the model does; `kind` is how it runs. Filter on `category` unless you specifically care about the execution path. For a single model, prefer `get_model` over `include_schema`. **Example arguments:** ```json theme={null} { "search": "kontext", "limit": 20 } ``` **Example response (structuredContent):** ```json theme={null} { "ok": true, "models": [ { "model_id": "blackforestlabs/flux-1-kontext/dev/image-to-image", "display_name": "Flux Kontext Dev", "description": "Edit visuals via text with multi-layer control and style memory.", "publisher": "blackforestlabs", "categories": ["image-to-image"], "kind": "model", "model_url": "https://www.runcomfy.com/models/blackforestlabs/flux-1-kontext-dev", "base_price_usd": 0.06, "price_unit": "output", "inputs": ["aspect_ratio", "image_url", "prompt", "seed"], "required_inputs": ["prompt", "image_url"] } ], "total": 371, "limit": 20, "offset": 0 } ``` `base_price_usd` is a base rate per `price_unit` (`second` for duration-billed models, otherwise `output`); most models multiply it by inputs like resolution or duration. `total` is the count **before** paging — use it to decide whether to fetch another page. *** ### `list_model_categories` List the capability categories models are grouped into. **Backs**: `GET /v1/models/categories` Takes no parameters. Returns values such as `text-to-image` and `image-to-video` — pass one to `list_models(category=...)`. Derived from the catalog, so it grows as new kinds of model are published. *** ### `get_model` Get one model's full input schema before building a request. **Backs**: `GET /v1/models/{model_id}` | Parameter | Type | Required | Description | | ---------- | ------ | :------: | -------------------------------------------------------------------------------- | | `model_id` | string | Yes | The identifier, slashes included, e.g. `blackforestlabs/flux-1-kontext/pro/edit` | `input_schema` is the JSON Schema for `run_model`'s `inputs`: property types, defaults, enums, and min/max ranges. Read it rather than guessing parameter names. **Example response:** ```json theme={null} { "ok": true, "model": { "model_id": "blackforestlabs/flux-1-kontext/pro/edit", "display_name": "Flux Kontext Pro", "categories": ["image-to-image"], "base_price_usd": 0.044, "price_unit": "output", "input_schema": { "type": "object", "required": ["prompt", "image_url"], "properties": { "prompt": { "type": "string" }, "image_url": { "type": "string", "format": "image_uri" }, "aspect_ratio": { "type": "string", "default": "16:9", "enum": ["21:9", "16:9", "1:1", "9:16"] }, "num_inference_steps": { "type": "integer", "default": 28, "minimum": 10, "maximum": 50 } } } } } ``` The schema describes the request body only — playground rendering hints (`title`, `x-order`, `x-rc-group-id`, widget-style `format` values) are stripped, so `format` appears only where a field takes a public file URL. *** ## Model inference ### `run_model` Run a hosted model on demand — no deployment needed. **Backs**: `POST /v1/models/{model_id}` | Parameter | Type | Required | Description | | --------------------- | ------- | :------: | ---------------------------------------------------------------- | | `model_id` | string | Yes | The model's identifier, slashes included | | `inputs` | object | No | Request body matching the model's Input schema (see `get_model`) | | `wait_for_completion` | boolean | No | If `true`, poll until done and return the result inline | | `timeout_seconds` | integer | No | Max wait (default 300) when `wait_for_completion=true` | File inputs must be **publicly accessible HTTPS URLs** that a plain unauthenticated GET can fetch. Unlike `submit_request`, the Model API does not take Base64 data URIs. **Example arguments:** ```json theme={null} { "model_id": "blackforestlabs/flux-1-kontext/pro/edit", "inputs": { "prompt": "She is now holding an orange umbrella and smiling", "image_url": "https://example.com/photo.webp", "aspect_ratio": "16:9" } } ``` **Running a trained LoRA without deploying it** — call the LoRA's *base model* and pass the LoRA as an input: ```json theme={null} { "model_id": "", "inputs": { "prompt": "a portrait in the style", "lora": { "path": "my_first_lora_3000.safetensors" } } } ``` `path` accepts either a name from your [LoRA Assets](https://www.runcomfy.com/trainer/lora-assets) or a public URL — including a checkpoint URL from `get_training_job_result`. *** ### `get_model_request_status` Poll a Model API request's status. **Backs**: `GET /v1/requests/{request_id}/status` | Parameter | Type | Required | Description | | ------------ | ------ | :------: | -------------------------------------- | | `request_id` | string | Yes | The request ID returned by `run_model` | Lifecycle: `in_queue` → `in_progress` → `completed` / `cancelled`. While `in_queue` the payload also carries `queue_position`. *** ### `get_model_request_result` Fetch a completed Model API request's outputs. **Backs**: `GET /v1/requests/{request_id}/result` | Parameter | Type | Required | Description | | ------------ | ------ | :------: | -------------- | | `request_id` | string | Yes | The request ID | The `output` shape is defined by the model's Output schema. Any hosted asset URLs found inside it are also flattened into `output_urls` for convenience. **Example response:** ```json theme={null} { "ok": true, "result": { "request_id": "5f1ba692-...", "status": "succeeded", "output": { "image": "https://playgrounds-storage-public.runcomfy.net/a.png" } }, "output_urls": [ { "field": "image", "json_path": "image", "url": "https://playgrounds-storage-public.runcomfy.net/a.png" } ] } ``` *** ### `cancel_model_request` Cancel a queued Model API request. **Backs**: `POST /v1/requests/{request_id}/cancel` | Parameter | Type | Required | Description | | ------------ | ------ | :------: | -------------- | | `request_id` | string | Yes | The request ID | Returns `cancelled` if accepted, or `not_cancellable` if the request is already in progress or finished. *** ## Training datasets Datasets for the **[Trainer API](/trainer-apis/introduction)**. Create one, upload media plus matching captions, then poll until it is `READY` — only `READY` datasets can be mounted by a training job. Backs the **[Dataset endpoints](/trainer-apis/async-queue-endpoints-datasets)**. ### `create_dataset` Create an empty dataset. **Backs**: `POST /prod/v1/trainers/datasets` | Parameter | Type | Required | Description | | --------- | ------ | :------: | ------------------------------------------------------------------------ | | `name` | string | No | Human-readable name, unique in your account. Omit to have one generated. | The `name` — not the id — is what an AI Toolkit config references as `/app/ai-toolkit/datasets/{dataset_name}`. A new dataset starts in `DRAFT`. *** ### `list_datasets` List datasets in your account. **Backs**: `GET /prod/v1/trainers/datasets` | Parameter | Type | Required | Description | | ------------- | ------- | :------: | ----------------------------------------------------------------------- | | `include_raw` | boolean | No | Return each dataset's unabridged payload instead of the compact summary | The listing carries no per-file detail — use `get_dataset_status` for a dataset's files. *** ### `get_dataset_status` Get a dataset's status and its successfully uploaded files. **Backs**: `GET /prod/v1/trainers/datasets/{dataset_id}/status` | Parameter | Type | Required | Description | | ------------ | ------ | :------: | ------------------ | | `dataset_id` | string | Yes | The dataset's UUID | Lifecycle: `DRAFT` → `UPLOADING` → `READY`, or `FAILED` (which sets `error`). Files still uploading or that failed do not appear in `files`. *** ### `delete_dataset` Permanently delete a dataset. **Backs**: `DELETE /prod/v1/trainers/datasets/{dataset_id}` | Parameter | Type | Required | Description | | ------------ | ------ | :------: | ------------------ | | `dataset_id` | string | Yes | The dataset's UUID | This cannot be undone. *** ### `upload_dataset_file_from_url` Add one file to a dataset by fetching it from a public URL. **Backs**: `POST /prod/v1/trainers/datasets/{dataset_id}/upload` | Parameter | Type | Required | Description | | ------------ | ------ | :------: | ---------------------------------------------------------------------------------- | | `dataset_id` | string | Yes | Target dataset | | `source_url` | string | Yes | Publicly reachable HTTPS URL for an image, video, or `.txt` caption. Under 150 MB. | | `filename` | string | No | Name to store it under. Defaults to the URL's basename. | The MCP server runs remotely and cannot read files on your machine. A local path passed as `source_url` is rejected — use `get_dataset_upload_urls` instead. Each image or video needs a caption `.txt` with the **same base name**: `img_0001.jpg` pairs with `img_0001.txt`. Re-uploading the same filename overwrites the previous copy. *** ### `upload_dataset_text_file` Write a caption straight into the dataset, with no file hosting needed. **Backs**: `POST /prod/v1/trainers/datasets/{dataset_id}/upload` | Parameter | Type | Required | Description | | ------------ | ------ | :------: | ---------------------------------------------------------------------- | | `dataset_id` | string | Yes | Target dataset | | `filename` | string | Yes | Must share the base name of the media it captions, e.g. `img_0001.txt` | | `text` | string | Yes | Caption body | **Example arguments:** ```json theme={null} { "dataset_id": "ds_123", "filename": "img_0001.txt", "text": "a photo of a golden retriever in a park" } ``` *** ### `get_dataset_upload_urls` Get signed upload URLs for files the server cannot fetch itself. **Backs**: `POST /prod/v1/trainers/datasets/{dataset_id}/get-upload-endpoint` | Parameter | Type | Required | Description | | ----------------------- | ------ | :------: | ------------------------------------- | | `dataset_id` | string | Yes | Target dataset | | `filename_to_byte_size` | object | Yes | Map of filename → exact size in bytes | Use this for local files and anything over 150 MB, then `PUT` each file's bytes to the returned `upload_url` with the returned `method` and `headers`. **Example arguments:** ```json theme={null} { "dataset_id": "ds_123", "filename_to_byte_size": { "img_0001.jpg": 2000000, "img_0001.txt": 12000 } } ``` The signature is derived from the byte size, so a wrong value is rejected by storage at `PUT` time. Signed URLs are short-lived; call again for a fresh one. After every `PUT` succeeds, poll `get_dataset_status` until `READY`. *** ## Training jobs AI Toolkit LoRA training. Backs the **[Training job endpoints](/trainer-apis/async-queue-endpoints-training-jobs)**. ### `submit_training_job` Submit an AI Toolkit training job. **Backs**: `POST /prod/v1/trainers/ai-toolkit/jobs` | Parameter | Type | Required | Description | | ------------- | ------- | :------: | ---------------------------------------------------- | | `config_file` | string | Yes | The complete AI Toolkit YAML config, as a string | | `gpu_type` | string | No | `ADA_80_PLUS` (H100, default) or `HOPPER_141` (H200) | | `gpu_count` | integer | No | `1` for single-GPU (default) or `8` for multi-GPU | | `gpu_id` | string | No | Specific GPU selector, e.g. `"#1"` | Multi-GPU (`gpu_count: 8`) is only supported on `ADA_80_PLUS`; the tool rejects other combinations before calling the API. Two paths in the config are fixed by the platform: * `training_folder` must be `/app/ai-toolkit/output` * the dataset's `folder_path` must be `/app/ai-toolkit/datasets/{dataset_name}`, where `dataset_name` is the dataset's **name**, not its id Training runs for hours, so this tool returns as soon as the job is queued rather than waiting. Track it with `get_training_job_status`. *** ### `get_training_job_status` Poll a training job's status and step progress. **Backs**: `GET /prod/v1/trainers/ai-toolkit/jobs/{job_id}/status` | Parameter | Type | Required | Description | | --------- | ------ | :------: | -------------- | | `job_id` | string | Yes | The job's UUID | Lifecycle: `IN_QUEUE` → `RUNNING` → `STOPPED` (finished or preempted), `FAILED` (with an `error`), or `CANCELED`. **Example response:** ```json theme={null} { "ok": true, "status": { "id": "job_123", "status": "RUNNING", "progress": { "current_step": 320, "total_steps": 2000, "percent": 16 } } } ``` *** ### `get_training_job_result` Fetch a training job's artifacts as hosted URLs. **Backs**: `GET /prod/v1/trainers/ai-toolkit/jobs/{job_id}/result` | Parameter | Type | Required | Description | | --------- | ------ | :------: | -------------- | | `job_id` | string | Yes | The job's UUID | Returns checkpoints (`.safetensors`), the resolved config, and sample outputs, flattened into `artifact_urls`. Safe to call while the job is still `RUNNING` — the list grows over time — and after a `FAILED` or `CANCELED` job to recover whatever was produced. Feed a checkpoint URL to `run_model` as `{"lora": {"path": ""}}` to run inference on it. *** ### `cancel_training_job` Cancel a queued or running training job. **Backs**: `POST /prod/v1/trainers/ai-toolkit/jobs/{job_id}/cancel` | Parameter | Type | Required | Description | | --------- | ------ | :------: | -------------- | | `job_id` | string | Yes | The job's UUID | Progress stops, but `get_training_job_result` still returns any checkpoints produced so far. *** ### `resume_training_job` Resume a stopped job from its latest checkpoint. **Backs**: `POST /prod/v1/trainers/ai-toolkit/jobs/{job_id}/resume` | Parameter | Type | Required | Description | | --------- | ------ | :------: | -------------- | | `job_id` | string | Yes | The job's UUID | Reuses the same `job_id` rather than creating a new job, restarting from the highest-step checkpoint (or step 0 if none exists). Useful after a preemption. For a `FAILED` job, read `error` from the status first and fix the cause — often via `edit_training_job` — before resuming. *** ### `edit_training_job` Replace the config of a non-running job. **Backs**: `POST /prod/v1/trainers/ai-toolkit/jobs/{job_id}/edit` | Parameter | Type | Required | Description | | ------------- | ------ | :------: | --------------------------- | | `job_id` | string | Yes | The job's UUID | | `config_file` | string | Yes | The updated AI Toolkit YAML | Only works while the job is `STOPPED`, `CANCELED`, or `FAILED`, and `config.name` must still match the original job's name. GPU type and count are chosen at resume time, so call `resume_training_job` afterwards to re-queue. *** ## Account ### `get_balance` Get the account's remaining RunComfy balance. **Backs**: `GET /prod/v2/balance` Takes no parameters. One wallet funds every product, so this is the figure Serverless deployments, `run_model` requests, and training jobs all draw down — and the one checked before work is allowed to start. **Example response:** ```json theme={null} { "ok": true, "balance": { "balance_microdollars": 4830000, "balance_usd": 4.83, "currency": "USD" } } ``` Use `balance_usd` for reading and `balance_microdollars` (millionths of a dollar) for exact arithmetic and threshold checks. A zero balance is a normal response, not an error. See **[Balance](/account/balance)** for minimums and what the figure covers. # Pricing Source: https://docs.runcomfy.com/model-apis/about-billing Model API is billed **per request** (on-demand inference). You do **not** pay for idle GPUs and you do **not** create deployments. For up-to-date pricing for a specific model or pipeline, check its model page in the [Models catalog](https://www.runcomfy.com/models) — the UI shows the current rate and billing unit. For LoRAs (Trainer > [Run LoRA](https://www.runcomfy.com/trainer/inference)), pricing is shown on the corresponding base model’s page. *** ## What affects cost Cost depends on the model/pipeline and the work it performs. Common drivers include: * model family (some pipelines are heavier than others) * output size (resolution / frames) * video length / FPS *** ## How to estimate * Look up the model in the [Models catalog](https://www.runcomfy.com/models) * Use the model’s pricing unit as your baseline * Multiply by expected runtime or output count (depending on the model) *** ## Support If you believe you’ve been incorrectly billed, contact [hi@runcomfy.com](mailto:hi@runcomfy.com) with your request ID and the approximate time of the issue. # Async Queue Endpoints Source: https://docs.runcomfy.com/model-apis/async-queue-endpoints These endpoints power the **asynchronous** (request-id based) Model API flow. You submit a job, get back a `request_id` immediately, then poll status and fetch results when the run completes. You can use the same endpoints for two sources: * **Models catalog**: run any prebuilt model from [Models](https://www.runcomfy.com/models) by calling its `model_id`. * **Trainer LoRA inference (no deployment)**: run inference for a LoRA trained (or imported) in **[RunComfy Trainer](https://www.runcomfy.com/trainer/ai-toolkit/app)** by calling the **same Model API endpoints** with the `model_id` of the LoRA’s **base model** (copy it from the base model’s model page), and pass the LoRA as an **input parameter** (see [LoRA Inputs (Trainer)](#lora-inputs-trainer)). ## Endpoints **Base URL**: `https://model-api.runcomfy.net` | Endpoint | Method | Purpose | | ---------------------------------- | ------ | ----------------------------------------------------------------------------- | | `/v1/models` | `GET` | [List available models](/model-apis/model-catalog-endpoints) | | `/v1/models/{model_id}` | `GET` | [Get a model's input schema](/model-apis/model-catalog-endpoints#get-a-model) | | `/v1/models/{model_id}` | `POST` | Submit an asynchronous request | | `/v1/requests/{request_id}/status` | `GET` | Check request status | | `/v1/requests/{request_id}/result` | `GET` | Retrieve request result | | `/v1/requests/{request_id}/cancel` | `POST` | Cancel a queued request | ## Common Path Parameters `model_id` string (required). The identifier of the model/pipeline you want to run (e.g., `blackforestlabs/flux-1-kontext/pro/edit`). * For **Models catalog** usage, pick a model from [Models](https://www.runcomfy.com/models). Each model page shows its `model_id`. To discover models from code instead, call `GET /v1/models` — see **[Model Catalog Endpoints](/model-apis/model-catalog-endpoints)**. * For **Trainer** usage, in **Trainer > [Run LoRA](https://www.runcomfy.com/trainer/inference)** select your LoRA’s **base model**, then open that base model page and copy its `model_id` (this `model_id` represents the inference pipeline you’ll run via the Model API). Alt RunComfy model id `request_id` string (required for non‑submit endpoints). Returned by `POST /v1/models/{model_id}`; use it to check status, fetch result, or cancel. *** ## Submit a Request Submit an asynchronous request to a model. Returns a `request_id` and convenience URLs to poll and fetch results. ``` POST /v1/models/{model_id} ``` ### **Request Example** Example using the [blackforestlabs/flux-1-kontext/pro/edit](https://www.runcomfy.com/models/blackforestlabs/flux-1-kontext-pro/image-to-image) model: ```bash theme={null} curl --request POST \ --url https://model-api.runcomfy.net/v1/models/blackforestlabs/flux-1-kontext/pro/edit \ --header "Content-Type: application/json" \ --header "Authorization: Bearer " \ --data '{ "prompt": "She is now holding an orange umbrella and smiling", "image_url": "https://playgrounds-storage-public.runcomfy.net/tools/7063/media-files/usecase1-1-input.webp", "seed": 81030369, "aspect_ratio": "16:9" }' ``` Request body keys (e.g., `prompt`, `image_url`, `seed`, `aspect_ratio`) map 1:1 to this model’s Input schema. See the model’s API page for required fields, types, enums, and defaults. For reference, see the Input schema on the [blackforestlabs/flux-1-kontext/pro/edit API page](https://www.runcomfy.com/models/blackforestlabs/flux-1-kontext-pro/image-to-image/api#input-schema). Alt RunComfy model input schema ### **Response Example** ```json theme={null} { "request_id": "{request_id}", "status_url": "https://model-api.runcomfy.net/v1/requests/{request_id}/status", "result_url": "https://model-api.runcomfy.net/v1/requests/{request_id}/result", "cancel_url": "https://model-api.runcomfy.net/v1/requests/{request_id}/cancel" } ``` Successful requests return 200 OK with a JSON object providing request tracking details. * `request_id` (string): Unique identifier for the request. * `status_url` (string): URL to poll for request progress. * `result_url` (string): URL to fetch outputs once the request completes. * `cancel_url` (string): URL to cancel the request if still queued. *** ## Monitor Request Status Poll the current state for a `request_id`. Typical states are: `in_queue` > `in_progress` > `completed` (or `cancelled`). ``` GET /v1/requests/{request_id}/status ``` ### Request Example ```bash theme={null} curl --request GET \ --url https://model-api.runcomfy.net/v1/requests/{request_id}/status \ --header "Authorization: Bearer " ``` ### Response Example ```json theme={null} { "request_id": "{request_id}", "status": "in_queue", "queue_position": 3, "status_url": "https://model-api.runcomfy.net/v1/requests/{request_id}/status", "result_url": "https://model-api.runcomfy.net/v1/requests/{request_id}/result" } ``` Successful requests return a 200 OK status with a JSON object describing the request’s state. * `status` (string): States while polling: `in_queue`, `in_progress`, `completed`, `cancelled`. * `status_url` (string): URL to poll for request progress. * `result_url` (string): URL to fetch outputs once the request completes. * For `in_queue`, `queue_position` (integer): Your position in the queue. *** ## Retrieve Request Results When `status` is `completed`, fetch the final outputs. The shape of result (single URI vs. object/array) is defined by the model’s Output schema on its API page. ``` GET /v1/requests/{request_id}/result ``` ### Query Parameters | Parameter | Type | Required | Description | | -------------- | ------- | :------: | -------------------------------------------------------------------------------------- | | `include_cost` | boolean | No | Add a `cost` field with what this request charged, in US dollars. Defaults to `false`. | ### Request Example ```bash theme={null} curl --request GET \ --url https://model-api.runcomfy.net/v1/requests/{request_id}/result \ --header "Authorization: Bearer " ``` ### Response Example ```json theme={null} { "request_id": "{request_id}", "status": "succeeded", "output": { "image": "https://playgrounds-storage-public.runcomfy.net/a.png", "videos": [ "https://playgrounds-storage-public.runcomfy.net/a.mp4", "https://playgrounds-storage-public.runcomfy.net/b.mp4", "https://playgrounds-storage-public.runcomfy.net/c.mp4" ] } } ``` Successful requests return **200 OK** with a JSON object containing the request’s final details. * `status` (string): One of `succeeded`, `failed`, `in_queue`, `in_progress`, or `cancelled`. * `output` (varies by model): the response body defined by the model’s Output schema (often URLs to generated assets). * `created_at` (string): When the request was created. * `finished_at` (string): When the request completed. * `cost` (number): Present only when `include_cost=true`. What this request charged, in US dollars. For funds remaining across your whole account, see **[Balance](/account/balance)**. *** ## Cancel a Request Cancel a request that is still queued. Already completed or terminated requests will be no‑ops. ``` POST /v1/requests/{request_id}/cancel ``` ### Request Example ```bash theme={null} curl --request POST \ --url https://model-api.runcomfy.net/v1/requests/{request_id}/cancel \ --header "Authorization: Bearer " ``` ### Response Example ```json theme={null} { "request_id": "{request_id}", "status": "completed", "outcome": "cancelled" } ``` Successful requests return a 202 Accepted status with a JSON object containing the cancellation outcome. * `outcome` (string): `cancelled` if the cancellation is accepted; `not_cancellable` if the request is already in progress or completed. *** ## Image/Video/Audio Inputs Use **a publicly accessible HTTPS URL** that returns the file with an unauthenticated GET request (no cookies or auth headers). Prefer stable or pre‑signed URLs for private assets. ```json theme={null} { "image_url": "https://playgrounds-storage-public.runcomfy.net/tools/7063/media-files/usecase1-1-input.webp" } ``` *** ## LoRA Inputs (Trainer) This section applies to **LoRA inference**. If you’re not deploying your LoRA as a dedicated endpoint, you can run it directly via the **Model API**. In this flow, the Model API works the same way as for the Models catalog (same base URL and endpoints). The key differences are: * Your `model_id` represents the inference **pipeline** you’ll run via the Model API (in **Trainer > Run LoRA**, select your LoRA’s **base model**, then open that base model page and copy its `model_id`). * The LoRA is provided via **input parameters** in the request body (your payload must match that pipeline’s Input schema). LoRA `path` can be set in two ways in *Model API*: * **LoRA name** from your [LoRA Assets](https://www.runcomfy.com/trainer/lora-assets), e.g. `"path": "my_first_lora_3000.safetensors"` * **Public URL**, e.g. `"path": "https://example.com/my_first_lora_3000.safetensors"` Example payload fragment: ```json theme={null} { "lora": { "path": "my_first_lora_3000.safetensors" } } ``` If you need a **dedicated endpoint** (stable `deployment_id`), or want to **choose hardware / autoscale**, deploy it and use **[Serverless API (LoRA)](/serverless-lora/introduction)** instead of the Model API. # Authentication Source: https://docs.runcomfy.com/model-apis/authentication Model API uses Bearer token authentication. Send your API token in the `Authorization` header: `Authorization: Bearer ` ## Get an API token Get your token from your [Profile](https://www.runcomfy.com/profile) page (avatar menu > **API Token**). Alt RunComfy Profile Button Alt RunComfy API Token If you regenerate your token, the old token stops working immediately — update any integrations. ## Keep your token secret Do not ship your token in client-side apps (browsers, mobile apps). Route requests through a server-side service you control. # Error Codes Source: https://docs.runcomfy.com/model-apis/error-codes ### 400001 InvalidResourceIdentifier The request contains an invalid or malformed resource ID (for example, a wrong `model_id`). Use the exact identifier as shown on the model’s detail page. Alt RunComfy model id ### 403001 PermissionDeniedError The resource exists but is not accessible to the authenticated user (e.g., the `request_id` belongs to another user). Use the owner’s credentials or obtain access, then retry. ### 404001 ResourceNotFound The specified resource cannot be found (e.g., a nonexistent, deleted, or expired `request_id`). Confirm the identifier and that it belongs to your account before retrying. ### 400002 InsufficientResources Your account has no remaining balance to run this operation. ### 400004 UserAccountError Authentication or account state is invalid, missing/expired token, revoked credentials, or an inactive account. Send a valid `Authorization: Bearer ` and ensure the account is active. # Model Catalog Endpoints Source: https://docs.runcomfy.com/model-apis/model-catalog-endpoints These endpoints let you **discover** which models you can run and **inspect** the request body each one expects, without leaving the API. They are the programmatic equivalent of browsing [Models](https://www.runcomfy.com/models) and reading a model's Input schema on its API page. Use them when you know what you want to generate but not which `model_id` provides it, or when you are generating request bodies from code and need the parameter names, types, and defaults. *** ## Endpoints **Base URL**: `https://model-api.runcomfy.net` | Endpoint | Method | Purpose | | ----------------------- | ------ | ------------------------------------------------------ | | `/v1/models` | `GET` | List the models your account can run | | `/v1/models/categories` | `GET` | List the capability categories models are grouped into | | `/v1/models/{model_id}` | `GET` | Get one model's full input schema | Both require the same Bearer token as the rest of the Model API. See **[Authentication](/model-apis/authentication)**. *** ## List models Returns every `model_id` that `POST /v1/models/{model_id}` accepts, with a summary of each model's inputs. ``` GET /v1/models ``` ### Query parameters | Parameter | Type | Required | Description | | ---------------- | ------- | :------: | ------------------------------------------------------------------------------------------------------------------------------ | | `search` | string | No | Case-insensitive substring match against `model_id`, `display_name`, and `description`. E.g. `kontext`, `upscale`, `lip sync`. | | `category` | string | No | Filter by capability, e.g. `text-to-image`, `image-to-video`. See [List categories](#list-categories). | | `kind` | string | No | Filter by how the model runs: `model`, `workflow`, or `inference`. | | `include_schema` | boolean | No | Include each model's full `input_schema` inline. Much larger response. Default `false`. | | `limit` | integer | No | Page size, `1`–`500`. Default `100`. | | `offset` | integer | No | Rows to skip. Default `0`. | `category` is **what the model does**; `kind` is **how it runs**. They are independent — filter on `category` unless you specifically care about the execution path. ### Request example ```bash theme={null} curl --request GET \ --url "https://model-api.runcomfy.net/v1/models?search=kontext&limit=20" \ --header "Authorization: Bearer " ``` ### Response example ```json theme={null} { "models": [ { "model_id": "blackforestlabs/flux-1-kontext/dev/image-to-image", "display_name": "Flux Kontext Dev", "description": "Edit visuals via text with multi-layer control and style memory.", "publisher": "blackforestlabs", "categories": ["image-to-image"], "kind": "model", "tags": ["By Function/IMAGE/Generate Image"], "model_url": "https://www.runcomfy.com/models/blackforestlabs/flux-1-kontext-dev", "base_price_usd": 0.06, "price_unit": "output", "pricing_note": "The rate is $0.06 per image.", "supported_batch_size": [1, 2, 3, 4], "inputs": ["aspect_ratio", "image_url", "prompt", "seed"], "required_inputs": ["prompt", "image_url"] } ], "total": 371, "limit": 20, "offset": 0 } ``` * `model_id` (string): Pass this to `POST /v1/models/{model_id}` to run the model. * `display_name` (string): Human-readable name, e.g. `Flux Kontext Dev`. * `description` (string): One-line summary of what the model does. * `publisher` (string): First segment of the `model_id`, e.g. `blackforestlabs`. * `categories` (string\[]): What the model does, e.g. `image-to-image`. A few models declare several. * `kind` (string): How it runs — `model`, `workflow`, or `inference`. * `model_url` (string): The model's page on runcomfy.com. Absent for models without a public page. * `base_price_usd` (number): Base rate in US dollars, per `price_unit`. * `price_unit` (string): `second` for duration-billed models, otherwise `output`. * `pricing_note` (string): Human-readable rate, where the model publishes one. * `supported_batch_size` (integer\[]): Batch sizes the model accepts. * `inputs` (string\[]): Every parameter name the model accepts. * `required_inputs` (string\[]): The subset you must supply. * `total` (integer): Matches **before** paging — use it to drive `offset`. `inputs` and `required_inputs` tell you whether a model fits your use case. For types, defaults, enums, and ranges, either fetch the model or pass `include_schema=true`. `base_price_usd` is a **base rate**, not a final price. Most models multiply it by inputs such as resolution or duration. For what a run actually cost, call `GET /v1/requests/{request_id}/result?include_cost=true`. *** ## List categories Returns every capability category present in the catalog — the valid values for the `category` filter. ``` GET /v1/models/categories ``` ### Response example ```json theme={null} { "categories": [ "audio-to-audio", "audio-to-video", "edit-video", "image-to-image", "image-to-video", "reference-to-video", "speech-to-video", "text-to-audio", "text-to-image", "text-to-video", "video-to-video" ] } ``` This list is derived from the catalog itself, so it grows as new kinds of model are published. *** ## Get a model Returns the same fields as a list entry plus `input_schema`, the JSON Schema for the request body. ``` GET /v1/models/{model_id} ``` ### Path parameters `model_id` string (required). The identifier exactly as listed, **slashes included** — they are path segments, not something to escape. E.g. `blackforestlabs/flux-1-kontext/pro/edit`. ### Request example ```bash theme={null} curl --request GET \ --url "https://model-api.runcomfy.net/v1/models/blackforestlabs/flux-1-kontext/pro/edit" \ --header "Authorization: Bearer " ``` ### Response example ```json theme={null} { "model_id": "blackforestlabs/flux-1-kontext/pro/edit", "display_name": "Flux Kontext Pro", "description": "Edit visuals via text with multi-layer control and style memory.", "publisher": "blackforestlabs", "categories": ["image-to-image"], "kind": "model", "base_price_usd": 0.044, "price_unit": "output", "inputs": ["aspect_ratio", "image_url", "prompt", "seed"], "required_inputs": ["prompt", "image_url"], "input_schema": { "type": "object", "required": ["prompt", "image_url"], "properties": { "prompt": { "type": "string", "description": "", "default": "Convert the scene to blue hour with soft drizzle" }, "aspect_ratio": { "type": "string", "default": "16:9", "enum": ["21:9", "16:9", "4:3", "1:1", "3:4", "9:16", "9:21"] }, "image_url": { "type": "string", "format": "image_uri" }, "num_inference_steps": { "type": "integer", "description": "The number of inference steps to perform.", "default": 28, "minimum": 10, "maximum": 50 } } } } ``` `input_schema` is the contract for the body you send to `POST /v1/models/{model_id}`. Each property carries its `type`, and where the model defines them: `default`, `description`, `enum` (the allowed values), `minimum`/`maximum`, `maxLength`, `minItems`/`maxItems`, and `multipleOf`. Properties marked `"format": "image_uri"` (or `video_uri` / `audio_uri`, and their plural `_uris` forms) take a **publicly accessible HTTPS URL** — see [Image/Video/Audio Inputs](/model-apis/async-queue-endpoints#imagevideoaudio-inputs). A few properties also carry `validations`, an array of API-enforced limits such as maximum upload size or image count. The schema describes the **request body**, not the RunComfy web UI. Presentation-only hints that the playground uses to render its controls — `title`, `x-order`, `x-rc-group-id`, and widget-style `format` values like `int_slider_with_range` — are stripped, so `format` appears only when it marks a file input. An unknown `model_id` returns `404001 ResourceNotFound`. See **[Error Codes](/model-apis/error-codes)**. *** ## Discover, then run The two endpoints compose into the normal flow: ```bash theme={null} # 1. Find a model by capability curl -s --url "https://model-api.runcomfy.net/v1/models?category=image-to-image&search=kontext" \ --header "Authorization: Bearer " # 2. Read the schema it expects curl -s --url "https://model-api.runcomfy.net/v1/models/blackforestlabs/flux-1-kontext/pro/edit" \ --header "Authorization: Bearer " # 3. Run it curl --request POST \ --url "https://model-api.runcomfy.net/v1/models/blackforestlabs/flux-1-kontext/pro/edit" \ --header "Content-Type: application/json" \ --header "Authorization: Bearer " \ --data '{"prompt": "make it snow", "image_url": "https://example.com/photo.webp"}' ``` `base_price_usd` gives you the base rate up front, but most models multiply it by inputs such as resolution or duration, so treat it as an estimate. For what a run actually cost, call `GET /v1/requests/{request_id}/result?include_cost=true` — see **[Retrieve Request Results](/model-apis/async-queue-endpoints#retrieve-request-results)**. For remaining funds, see **[Balance](/account/balance)**. # Quickstart Source: https://docs.runcomfy.com/model-apis/quickstart ## What is the Model API? The **Model API** lets you run hosted models from RunComfy with a single, consistent REST interface: * **No deployment** (on-demand inference) * **Per-request billing** * **Async queue**: submit > get `request_id` > poll status/result * **Hosted outputs** returned as URLs when the run completes *** ## Choose a model The Model API can run models/pipelines from two sources: ### Option A: Models catalog (hosted models) Pick a model from [Models](https://www.runcomfy.com/models). Each model page shows its `model_id`. In this quickstart we’ll use: [blackforestlabs/flux-1-kontext/pro/edit](https://www.runcomfy.com/models/blackforestlabs/flux-1-kontext-pro/image-to-image) Its `model_id` is: `blackforestlabs/flux-1-kontext/pro/edit` Alt RunComfy model id ### Option B: Trainer LoRA inference (on-demand) If you trained (or imported) a LoRA in **[RunComfy Trainer](https://www.runcomfy.com/trainer/ai-toolkit/app)** and want to run inference **without deploying**, you can still use the **Model API**. Important detail: * you call a **base model pipeline** by `model_id` * you pass the LoRA as **input parameters** in the request body See **[LoRA Inputs (Trainer)](/model-apis/async-queue-endpoints#lora-inputs-trainer)** for the exact request fields. Alt RunComfy model id > If you want a dedicated endpoint (choose hardware, autoscale, stable `deployment_id`), use **[Serverless API (LoRA)](/serverless-lora/introduction)**. > Want to train a LoRA model yourself? Start with **[Trainer APIs Quickstart](/trainer-apis/quickstart)**. *** ## Authentication All requests require a Bearer token: `Authorization: Bearer ` Get your token from your [Profile](https://www.runcomfy.com/profile) page. *** ## Submit a request Send a JSON body that matches the model’s input schema. For file inputs, provide publicly accessible HTTPS URLs. ```bash theme={null} curl --request POST \ --url https://model-api.runcomfy.net/v1/models/blackforestlabs/flux-1-kontext/pro/edit \ --header "Content-Type: application/json" \ --header "Authorization: Bearer " \ --data '{ "prompt": "She is now holding an orange umbrella and smiling", "image_url": "https://playgrounds-storage-public.runcomfy.net/tools/7063/media-files/usecase1-1-input.webp", "seed": 81030369, "aspect_ratio": "16:9" }' ``` Tip: use the model’s **Input schema** as the source of truth (model page > API/Schema > properties). Alt RunComfy model input schema *** ## Poll status ```bash theme={null} curl --request GET \ --url https://model-api.runcomfy.net/v1/requests/{request_id}/status \ --header "Authorization: Bearer " ``` Typical lifecycle: `in_queue` > `in_progress` > `completed` *** ## Fetch results ```bash theme={null} curl --request GET --url https://model-api.runcomfy.net/v1/requests/{request_id}/result --header "Authorization: Bearer " ``` # Pricing Source: https://docs.runcomfy.com/serverless-lora/about-billing Serverless API (LoRA) is billed based on **GPU instance uptime** (per-second billing). This is different from the **Model API**, which is billed **per request**. ## Pricing Overview Serverless API (LoRA) supports two billing plans: * **Pay as You Go**: standard hourly rates by machine tier. * **Pro (subscription)**: **20%–30%** discount on Pay as You Go rates. Prices and machine availability may change. Refer to [RunComfy Pricing](https://www.runcomfy.com/pricing) for the latest machine rates, plan benefits, and extras. | Machine Type | GPU Options | VRAM | RAM | vCPUs | Pay as You Go Price | Pro Price | | ------------- | ----------- | ----- | ----- | ----- | ------------------- | ----------- | | Medium | T4, A4000 | 16GB | 16GB | 8 | \$0.99/hour | \$0.79/hour | | Large | A10G, A5000 | 24GB | 32GB | 8 | \$1.75/hour | \$1.39/hour | | X-Large | A6000 | 48GB | 48GB | 28 | \$2.50/hour | \$1.99/hour | | X-Large Plus | L40S, L40 | 48GB | 64GB | 28 | \$2.99/hour | \$2.15/hour | | 2X-Large | A100 | 80GB | 96GB | 28 | \$4.99/hour | \$3.99/hour | | 2X-Large Plus | H100 | 80GB | 180GB | 28 | \$7.49/hour | \$5.99/hour | | 3X-Large | H200 | 141GB | 240GB | 24 | \$8.75/hour | \$6.99/hour | *** ## How Billing Works Billing is usage-based and calculated per second: * Billing starts when an instance is signaled to wake up. * Billing stops when the instance is fully shut down. Your deployment can run a mix of persistent and on-demand instances, controlled by `minimum_instances` and `maximum_instances`. ### Persistent Instances * Set `minimum_instances` > 0 to keep that many instances running. * You are billed for the full uptime (including idle time) until you scale down. ### On-Demand Instances * Additional instances spin up to handle demand above `minimum_instances` (or all demand when `minimum_instances` = 0). * They scale down after the keep-warm period. * You are billed for cold start, execution, and keep-warm time. ## Instance Cost Breakdown * **Cold start / warm-up time**: instance boots and loads models/assets. Duration depends on machine tier, workflow complexity, and model size. * **Execution time**: workflows run. This is the main compute time. * **Keep-warm time**: idle time before scale-down. This time is billed. > **Note:** You may also see **Queue Time** (waiting for resources or concurrency). Queue time is not billed. *** ## Controlling cost * **Scale to zero:** set **minimum instances = 0** to avoid idle cost (first request may be slower due to cold start). * **Cap concurrency:** keep **maximum instances** conservative to limit parallel capacity and spend. * **Tune keep-warm:** shorter keep-warm lowers idle cost; longer keep-warm reduces cold starts during bursty traffic. *** ## Support If you believe you’ve been incorrectly billed, contact [hi@runcomfy.com](mailto:hi@runcomfy.com) with your deployment ID, request ID (if applicable), and the approximate time of the issue. # Choose a LoRA Inference API Source: https://docs.runcomfy.com/serverless-lora/api-types RunComfy Trainer lets you run inference with the **same LoRA + base model inference setup** in **two different ways**. * Both options keep **training and inference parity** (same base model, same setup, same defaults). * What changes is **how you run it** (on‑demand vs. your own dedicated endpoint) and **how you’re billed**.
On‑demand, no deployment, call a model\_id and pass your LoRA in the request body.
Billing: per request

Deploy your LoRA as a dedicated endpoint (a deployment) and call it with a deployment\_id.
Billing: GPU uptime, you can autoscale and scale down when idle
*** ## How to decide (10 seconds) If your goal is simply: * “**I trained/imported a LoRA and I want to generate with it on top of the base model**” Start with the **Model API**. Only choose the **Serverless API (LoRA)** when you specifically need a **dedicated endpoint**, for example: * you want to **pick a GPU** tier for your workload * you need to control **how many runs can happen in parallel** (autoscaling / concurrency) * you want to **keep capacity warm** so the first request isn’t slow (reduce cold starts) * you need **more predictable response time** for production traffic > Tip: If you’re unsure, start with the Model API. You can always deploy a dedicated endpoint later without changing your prompts or workflow logic. *** ## Quick comparison | What you care about | Model API (on‑demand) | Serverless API (LoRA) (dedicated endpoint) | | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Do I need to deploy anything first? | **No** | **Yes**, create a Deployment from your LoRA Asset | | What ID do I call? | `model_id` | `deployment_id` | | Where do I find that ID? | In **Trainer > [Run LoRA](https://www.runcomfy.com/trainer/inference)**, select your LoRA’s **base model**, then open that base model page and copy its `model_id`. | In **Trainer > [Deployments](https://www.runcomfy.com/trainer/deployments)**, open your Deployment and copy the `deployment_id` from **Deployment details**. | | Where does the LoRA go? | You pass the LoRA in the **request body** (e.g. `lora.path`) | The LoRA is **already attached** to the Deployment (you don’t pass `lora.path`) | | Submit endpoint | `POST https://model-api.runcomfy.net/v1/models/{model_id}` | `POST https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/inference` | | Job flow | Async: submit > `request_id` > poll status/result | Async: submit > `request_id` > poll status/result | | Billing | **Per request** | **GPU uptime** (per‑second; can scale down to zero when idle) | *** ## Same async pattern, different IDs Both options are asynchronous. **Model API** ```text theme={null} POST https://model-api.runcomfy.net/v1/models/{model_id} -> returns request_id GET https://model-api.runcomfy.net/v1/requests/{request_id}/status GET https://model-api.runcomfy.net/v1/requests/{request_id}/result POST https://model-api.runcomfy.net/v1/requests/{request_id}/cancel ``` **Serverless API (LoRA) (Deployment)** ```text theme={null} POST https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/inference -> returns request_id GET https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/status GET https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/result POST https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/cancel ``` *** ## Option A — Model API (on‑demand, no deployment) Use this when you want the **fastest path** from LoRA to generation. ### What you do 1. In **Trainer > [Run LoRA](https://www.runcomfy.com/trainer/inference)**, select your LoRA’s **base model**, then open that base model page and copy its `model_id`. * This `model_id` represents the **inference setup** you’ll run (the model’s pipeline/workflow). 2. Call the **Model API** with that `model_id` and include your LoRA as an input parameter (for example `lora.path`). 3. Poll status and fetch results using the returned `request_id`. ### Key thing to remember The Model API will run **whatever inference setup** the `model_id` points to. For your trained LoRA inference: * `model_id` = the **base model’s** inference setup (from the model page) * LoRA = an **input** you provide on each request ### Where to go next * Start here: **[Model APIs Quickstart](/model-apis/quickstart)** * LoRA request fields + examples: **[LoRA Inputs (Trainer)](/model-apis/async-queue-endpoints#lora-inputs-trainer)** *** ## Option B — Serverless API (LoRA) (dedicated endpoint / Deployment) Use this when you need **more control over runtime behavior** (GPU choice, autoscaling, warm instances) and want to call your LoRA through a **stable dedicated endpoint**. ### What you do 1. In Trainer, turn your LoRA Asset into a **Deployment** (this creates the dedicated endpoint and pins the LoRA + base model + default settings). 2. Copy the `deployment_id` from the Deployment details page. 3. Submit inference to `POST /prod/v2/deployments/{deployment_id}/inference`. 4. Poll status and fetch results (same async pattern, but scoped under the deployment). ### Key thing to remember With a Deployment, the LoRA is already part of the endpoint. That means: * `deployment_id` selects the endpoint * Your request body only includes the **inputs defined by the deployment’s schema** (prompt, images, params, etc.) * You typically **do not** pass `lora.path` because the LoRA is already attached ### Where to go next * Overview: **[Serverless API (LoRA) Introduction](/serverless-lora/introduction)** * Full walkthrough: **[Serverless API (LoRA) Quickstart](/serverless-lora/quickstart)** * Request lifecycle details: **[Async Queue Endpoints](/serverless-lora/async-queue-endpoints)** # Async Queue Endpoints Source: https://docs.runcomfy.com/serverless-lora/async-queue-endpoints These endpoints run **asynchronous inference jobs** against a **Serverless API (LoRA) Deployment**. The endpoint shape is the same async pattern used by Serverless API (ComfyUI) (submit > `request_id` > status/result).\ What changes is the **request/response schema**, which is specific to your deployment and is shown in the Deployment **API** tab. ## Endpoints **Base URL**: `https://api.runcomfy.net` | Endpoint | Method | Description | | :------------------------------------------------------------------ | :----: | :------------------ | | `/prod/v2/deployments/{deployment_id}/inference` | POST | Submit an inference | | `/prod/v2/deployments/{deployment_id}/requests/{request_id}/status` | GET | Check job status | | `/prod/v2/deployments/{deployment_id}/requests/{request_id}/result` | GET | Get job result | | `/prod/v2/deployments/{deployment_id}/requests/{request_id}/cancel` | POST | Cancel queued job | ## Common path parameters * `deployment_id`: Required string. The unique ID for your LoRA deployment. * `request_id`: Required string for status, result, and cancel. The unique ID for a specific inference job. *** ## Submit a request ```text theme={null} POST /prod/v2/deployments/{deployment_id}/inference ``` This call enqueues a job and returns a `request_id` right away, plus URLs you can use to poll and fetch results. **The request body must conform to the deployment’s input schema shown on the deployment details page under the API tab**; treat that schema as the contract for required fields, types, enums, and defaults. ### Request example ```bash theme={null} curl --request POST \ --url https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/inference \ --header "Content-Type: application/json" \ --header "Authorization: Bearer " \ --data '{ "prompt": "Make the scene feel warmer and more cinematic. Add a subtle orange glow.", "ctrl_img_1": "https://playgrounds-storage-public.runcomfy.net/tools/7063/media-files/usecase1-1-input.webp", "width": 1024, "height": 1024, "guidance_scale": 4, "sample_steps": 25, "network_multiplier": 1.0, "neg": "", "seed": 42, "sampler": "flowmatch", "num_frames": 1, "fps": 1 }' ``` ### Input files (URLs) If your schema includes `image_uri` inputs such as `ctrl_img_1`, pass a **publicly accessible HTTPS URL** that returns the raw file via an unauthenticated `GET` request. The URL must not require cookies and must not redirect to a login page. ### Response example ```json theme={null} { "request_id": "{request_id}", "status_url": "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/status", "result_url": "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/result", "cancel_url": "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/cancel" } ``` *** ## Monitor request status ```text theme={null} GET /prod/v2/deployments/{deployment_id}/requests/{request_id}/status ``` Use this endpoint to poll queue progress and determine when results are ready. A typical lifecycle is `in_queue`, then `in_progress`, then a terminal outcome such as `succeeded`, `failed`, or `cancelled`. ### Request example ```bash theme={null} curl --request GET \ --url https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/status \ --header "Authorization: Bearer " ``` ### Response example ```json theme={null} { "request_id": "{request_id}", "status": "in_queue", "queue_position": 0, "result_url": "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/result", "status_url": "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/status" } ``` *** ## Retrieve request results ```text theme={null} GET /prod/v2/deployments/{deployment_id}/requests/{request_id}/result ``` When the request succeeds, this endpoint returns an `output` object. **The `output` object conforms to the deployment’s output schema shown on the deployment details page under the API tab**. Outputs typically include **one or more hosted URLs**. Generated assets (for example, images or videos) are hosted on temporary storage for convenience. The hosted output URLs remain available for up to 7 days after a request succeeds; after that, the files are automatically removed and the URLs will expire. If you need to keep outputs longer, download them or copy them to your own persistent storage. ### Request example ```bash theme={null} curl --request GET \ --url https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/result \ --header "Authorization: Bearer " ``` ### Response example ```json theme={null} { "request_id": "{request_id}", "status": "succeeded", "output": { "images": [ "https://example.com/output-1.png", "https://example.com/output-2.png" ] }, "created_at": "2025-07-22T13:05:16.143086", "finished_at": "2025-07-22T13:13:03.624471" } ``` *** ## Cancel a request ```text theme={null} POST /prod/v2/deployments/{deployment_id}/requests/{request_id}/cancel ``` Use this to cancel a job while it is still in the queue. If the job is already running inference or has finished, it cannot be cancelled. ### Request example ```bash theme={null} curl --request POST \ --url https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/cancel \ --header "Authorization: Bearer " ``` ### Response example ```json theme={null} { "request_id": "{request_id}", "status": "completed", "outcome": "cancelled" } ``` # Authentication Source: https://docs.runcomfy.com/serverless-lora/authentication Serverless API (LoRA) uses Bearer token authentication. Send your API token in the `Authorization` header: `Authorization: Bearer ` ## Get an API token Get your API token from your [Profile](https://www.runcomfy.com/profile) page (avatar menu > **API Token**). If you regenerate your token, the old token stops working immediately — update any deployed integrations. ## Keep your token secret Do not ship your API token in client-side environments (browsers, mobile apps). Route requests through a server-side service you control. # Create a Deployment Source: https://docs.runcomfy.com/serverless-lora/create-a-deployment Create a **Deployment** when you want to serve your trained LoRA through a **dedicated serverless endpoint** (stable `deployment_id`, GPU selection, autoscaling). > If you don’t need a deployment (you just want on-demand inference and per-request billing), use the **Model API** instead.\ > See: **[Choose a LoRA inference API](/serverless-lora/api-types)** *** ## Prerequisites You need a **LoRA Asset** in RunComfy Trainer: * Train a LoRA in Trainer (it appears under **LoRA Assets**), or * Import a LoRA (`.safetensors`). If available, also provide the training config so RunComfy can keep the base model + defaults consistent. LoRA Assets live here: [Trainer > LoRA Assets](https://www.runcomfy.com/trainer/lora-assets) *** ## Create a Deployment You can create a deployment from either place: * From **LoRA Assets**, click **Deploy** / **Deploy Endpoint** (recommended — the LoRA is preselected) * From **Deployments**, click **Create a deployment** Both routes open the same configuration screen. ### 1) Name the deployment Give it a human-readable name for dashboards and logs. API calls use the generated `deployment_id`. ### 2) Select a LoRA (base model is pinned) Select the LoRA you want to serve. The deployment is pinned to the base model that LoRA was trained with. ### 3) Choose hardware Pick a GPU tier based on your latency target and VRAM needs. Exact GPU models can vary by region/capacity, but the VRAM tier is what matters. ### 4) Configure autoscaling Autoscaling controls how many instances can run in parallel and whether you keep warm capacity. Common knobs: * **Minimum instances (`0–30`)**: the baseline number of instances kept running. Set to `0` to minimize cost (idle deployments can stay at `$0/hr`), but the first request after idle may cold start. Set to `1+` to keep capacity warm and reduce cold starts, with ongoing runtime cost. * **Maximum instances (`1–60`)**: the upper bound on instances that can run in parallel. This caps cost and effectively defines your concurrency ceiling; requests beyond capacity will wait in the queue until an instance is available. Need higher limits? Contact us. * **Queue size (`≥ 1`)**: how many requests can remain pending while the deployment scales up (up to the maximum instances limit). Lower values prioritize latency (fail/pressure sooner), higher values prioritize cost (buffer bursts and scale gradually). * **Keep warm duration (seconds)**: how long an instance stays up after finishing a request before scaling down. Shorter windows reduce idle cost; longer windows improve responsiveness for bursty traffic by avoiding frequent cold starts. *** ## Deploy Click **Deploy**. When it finishes, copy the `deployment_id` from the deployment details page. Next: **[Submit requests via the Async Queue Endpoints](/serverless-lora/async-queue-endpoints)**. # Edit a Deployment Source: https://docs.runcomfy.com/serverless-lora/edit-a-deployment Edit a deployment when you want to update what the endpoint serves, such as switching to a newer LoRA revision, or change how it runs, including hardware and scaling, without changing the deployment’s identity. ## Pinned base model A deployment always pins the **base model checkpoint** used during training and treats it as part of the contract. You can edit the deployment’s LoRA, hardware tier, autoscaling behavior, and whether it is enabled or disabled, but you cannot change the base model. If you need to serve a LoRA trained on a different base model, create a new deployment for that LoRA instead of editing the existing one. ## Change LoRA on the same base model Use this when you trained a new revision or imported updated `.safetensors` weights and want the same endpoint to start serving the new adapter. First confirm the new LoRA asset exists in [LoRA Assets](https://www.runcomfy.com/trainer/lora-assets). Then open the deployment, choose **Edit**, select the new LoRA in the LoRA picker, and save. Because the base model is locked, the LoRA picker only shows LoRAs that are compatible with the deployment’s base checkpoint, so you cannot accidentally select an incompatible adapter. ## Tune hardware and scaling You can switch GPU tiers to match your VRAM and throughput needs, and tune autoscaling settings such as minimum instances, maximum instances, queue size, and keep warm to balance cost, latency, and cold starts. ## Rollout behavior Changing the served LoRA or hardware tier may trigger a brief warm-up. If minimum instances is 0, the first request may incur a cold start. Autoscaling-only changes take effect immediately for future scaling decisions, though brief queuing may occur while updated instances come online. ## Enable, disable, delete * **Disable a deployment** Disabling a deployment is an immediate "off switch". It stops serving requests and shuts down capacity to halt runtime cost. New requests will fail immediately, and in-flight requests may be interrupted depending on execution state. * **Re-enable a deployment** Re-enabling makes the deployment accept requests again and applies your autoscaling rules. If minimum instances is 0, the first request after enabling may incur a cold start. * **Delete a deployment** Delete a deployment only when you no longer need the endpoint itself. Deleting removes the deployment configuration and endpoint, but it does not delete your underlying LoRA assets. Those remain available for future deployments. ## Save changes After updating the LoRA selection, hardware, scaling settings, or enabled state, save the edit to apply it. # Error Codes Source: https://docs.runcomfy.com/serverless-lora/error-codes When an API call fails, RunComfy returns an HTTP error status and a JSON body that may include an `error_code` and message. This page lists common errors you may see when calling **Serverless API (LoRA)**. *** ## 11007 InferenceServiceConnectionError **Meaning:** The gateway could not reach the inference backend for your deployment. **What to try:** * Check the deployment is **enabled** and has capacity to start. * If your deployment can scale to zero (`min_instances = 0`), the first request may need a **cold start** — retry after a short delay. * If the problem persists, contact support with your `deployment_id` and `request_id`. *** ## 11008 InferenceServiceInferenceRequestError **Meaning:** Your request was rejected as a client error (4xx). **What to try:** * Validate your payload against the deployment’s **input schema** (Deployment > API tab). * For file inputs, make sure URLs are publicly accessible over HTTPS and do not require cookies/auth. *** ## 11004 InferenceServiceInferenceUnexpectedError **Meaning:** The request could not be submitted due to an unexpected server-side error. **What to try:** * Retry with exponential backoff. * If it repeats, share the full error response with support. *** ## 11011 InferenceServiceExecutionError **Meaning:** The deployment started processing the request, but the run failed at execution time. **What to try:** * Inspect the error details returned by the API. * Confirm that parameters (prompt, sizes, steps, etc.) are within the allowed ranges for your deployment. *** ## 11012 InferenceServiceRequestMissing **Meaning:** The `request_id` could not be found (for example it expired, was never accepted, or tracking state was lost). **What to try:** * Confirm you are polling the correct deployment and `request_id`. * Resubmit the request if needed. *** ## 11005 InferenceServicePollingResultUnexpectedError **Meaning:** Status polling failed unexpectedly. **What to try:** * Retry after a short delay. * If it persists, check deployment health in the dashboard. *** ## 11006 InferenceServiceResultRetrievalUnexpectedError **Meaning:** Result retrieval failed unexpectedly. **What to try:** * Retry after a short delay (outputs may still be uploading). * If the request is `succeeded` but outputs are missing, contact support. *** ## Getting help If you hit an error not listed here, contact [hi@runcomfy.com](mailto:hi@runcomfy.com) with the full error response plus your `deployment_id` and `request_id`. # Introduction Source: https://docs.runcomfy.com/serverless-lora/introduction **API v1 is being deprecated — please migrate to v2.** All new integrations should use the `/prod/v2/...` endpoints. v1 remains available for now but will not receive new features and will be retired in a future release. **Serverless API (LoRA)** lets you deploy a **LoRA** as a **dedicated, scalable endpoint** (a *Deployment*) and run inference through a standard async queue API. It is built on the same serverless system as **Serverless API (ComfyUI)** — the difference is simply what you deploy: * **Serverless API (ComfyUI):** you deploy a *ComfyUI workflow* * **Serverless API (LoRA):** you deploy *your trained LoRA* (pinned to its base model + default inference config) > If you only want to run LoRA inference **without creating a deployment**, use the **Model API** instead.\ > Start here: **[Choose a LoRA inference API](/serverless-lora/api-types)** *** ## Key concepts Serverless API (LoRA) revolves around three objects: ### LoRA Asset A **LoRA Asset** is the output of training or importing a LoRA in RunComfy Trainer. It includes: * LoRA adapter weights (`.safetensors`) * training metadata (for example the base model reference) * the defaults Trainer uses for inference ### Deployment A **Deployment** is the serverless endpoint you call from your app. When you create a Deployment from a LoRA Asset, RunComfy: * **pins the base model checkpoint** the LoRA was trained on * attaches the LoRA weights * loads the same default inference setup you used in Trainer This is what gives you “training and inference parity”: the deployed endpoint starts from the same setup that produced your training samples. ### Request A **request** is a single inference job against a Deployment. You submit a request, get back a `request_id`, then poll status/results (or use webhooks). *** ## Where to find things in the UI * [LoRA Assets](https://www.runcomfy.com/trainer/lora-assets) * [Deployments](https://www.runcomfy.com/trainer/deployments) * [Requests](https://www.runcomfy.com/trainer/requests) *** ## Typical workflow 1. **Train or import a LoRA** in Trainer > you get a LoRA Asset 2. **Create a Deployment** (choose hardware + autoscaling) 3. **Submit inference** to the Deployment endpoint (`POST …/inference`) 4. **Poll status** (`GET …/status`) and **fetch outputs** (`GET …/result`) Next step: **[Quickstart](/serverless-lora/quickstart)** > Want to train a LoRA model yourself? Start with **[Trainer APIs Quickstart](/trainer-apis/quickstart)**. # Quickstart Source: https://docs.runcomfy.com/serverless-lora/quickstart This guide shows how to **deploy your trained LoRA (trained with its base model) as a serverless endpoint** and run your first inference request. > Want to run LoRA inference **without deploying**? Use the **Model API** instead.\ > Start here: **[Choose a LoRA inference API](/serverless-lora/api-types)** *** ## Step 1: Make sure you have a LoRA Asset A **LoRA Asset** comes from either: * training in RunComfy Trainer, or * importing a `.safetensors` LoRA (plus its training config) You can view your assets here: [Trainer > LoRA Assets](https://www.runcomfy.com/trainer/lora-assets) Alt LoRA Asset *** ## Step 2: Create a Deployment From the LoRA Asset page, click **Deploy** (or **Deploy Endpoint**) to create a Deployment. Alt LoRA Asset During deployment you can choose: * **hardware** (GPU tier) * **autoscaling** (scale to zero, max instances, keep-warm) When the deployment is ready, copy the `deployment_id` from the Deployment details page. *** ## Step 3: Authenticate All API calls require a Bearer token: `Authorization: Bearer ` Get your API token from your [Profile](https://www.runcomfy.com/profile) page. *** ## Step 4: Submit an inference request The exact request body depends on the pipeline attached to your Deployment.\ Use the Deployment **API** tab (or its request schema in the dashboard) as the source of truth. ```bash theme={null} curl --request POST \ --url "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/inference" \ --header "Content-Type: application/json" \ --header "Authorization: Bearer " \ --data '{ "prompt": "A cinematic portrait photo", "...": "other fields depend on your deployment" }' ``` **Response example:** ```json theme={null} { "request_id": "{request_id}", "status_url": "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/status", "result_url": "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/result", "cancel_url": "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/cancel" } ``` *** ## Step 5: Poll status, then fetch results Check status: ```bash theme={null} curl --request GET \ --url "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/status" \ --header "Authorization: Bearer " ``` Fetch results: ```bash theme={null} curl --request GET \ --url "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/result" \ --header "Authorization: Bearer " ``` *** ## Next steps * Request lifecycle + file uploads: **[Async Queue Endpoints](/serverless-lora/async-queue-endpoints)** * Deployment settings: **[Create a Deployment](/serverless-lora/create-a-deployment)** * Troubleshooting: **[Error Codes](/serverless-lora/error-codes)** # Webhooks Source: https://docs.runcomfy.com/serverless-lora/webhooks Webhooks let RunComfy **push request updates to your server** instead of requiring polling. When enabled on a request: * RunComfy sends `POST` callbacks with status/progress updates * you can react immediately (store results, update UI, trigger downstream jobs) * you can reduce or eliminate polling load *** ## How to enable webhooks When you submit an inference request, pass webhook options as **query parameters**: * `webhook`: your HTTPS endpoint that will receive callbacks (**URL-encoded**) * `webhook_intermediate_status`: set to `true` to receive intermediate updates (in queue / in progress) ```text theme={null} POST /prod/v2/deployments/{deployment_id}/inference?webhook={url_encoded_webhook}&webhook_intermediate_status=true ``` > Note: Because the webhook URL is part of the query string, it must be URL-encoded. > Example: `https://example.com/api/runcomfy/webhook` → `https%3A%2F%2Fexample.com%2Fapi%2Fruncomfy%2Fwebhook` ### Request example (with webhook) The request body must conform to the deployment’s input schema shown on the deployment details page under the **API** tab. ```bash theme={null} curl --request POST \ --url 'https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/inference?webhook=https%3A%2F%2Fexample.com%2Fapi%2Fruncomfy%2Fwebhook&webhook_intermediate_status=true' \ --header "Content-Type: application/json" \ --header "Authorization: Bearer " \ --data '{ "prompt": "Make the scene feel warmer and more cinematic. Add a subtle orange glow.", "ctrl_img_1": "https://playgrounds-storage-public.runcomfy.net/tools/7063/media-files/usecase1-1-input.webp", "width": 1024, "height": 1024, "guidance_scale": 4, "sample_steps": 25, "network_multiplier": 1.0, "neg": "", "seed": 42, "sampler": "flowmatch", "num_frames": 1, "fps": 1 }' ``` *** ## Callback payloads Callbacks are delivered as JSON. Payload shape depends on the current state of the request. Common fields you’ll see: * `request_id` * `deployment_id` * `status` and/or `outcome` * `created_at` / `finished_at` * `output` (on success) — matches the output schema you get from **[GET …/result](/serverless-lora/async-queue-endpoints#retrieve-request-results)** ### Example: in\_queue ```json theme={null} { "request_id": "{request_id}", "status_url": "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/status", "result_url": "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/result", "cancel_url": "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/cancel" } ``` ### Example: in\_progress ```json theme={null} { "request_id": "{request_id}", "deployment_id": "{deployment_id}", "status": "in_progress", "status_url": "https://api.runcomfy.net/prod/v2/deployments/dep_abc/requests/rq_123/status", "result_url": "https://api.runcomfy.net/prod/v2/deployments/dep_abc/requests/rq_123/result", "cancel_url": "https://api.runcomfy.net/prod/v2/deployments/dep_abc/requests/rq_123/cancel", "instance_id": "{instance_id}", "created_at": "2025-11-18T10:00:00Z", "started_at": "2025-11-18T10:01:00Z" } ``` ### Example: succeeded ```json theme={null} { "request_id": "{request_id}", "deployment_id": "{deployment_id}", "status": "succeeded", "output": { "images": [ "https://example.com/output-1.png", "https://example.com/output-2.png" ] }, "instance_id": "{instance_id}", "created_at": "2025-11-18T10:00:00Z", "started_at": "2025-11-18T10:01:12Z", "finished_at": "2025-11-18T10:08:30Z" } ``` ### Example: failed ```json theme={null} { "request_id": "{request_id}", "deployment_id": "{deployment_id}", "status": "failed", "error": { "error": "ExampleErrorType", "details": "This is an example error message explaining the failure.", "debugInfo": "Example debug information or stack trace here.", "errorCode": 12345 }, "instance_id": "{instance_id}", "created_at": "2025-11-18T10:00:00Z", "started_at": "2025-11-18T10:01:12Z", "finished_at": "2025-11-18T10:08:30Z" } ``` *** ## Delivery and retries * Your webhook endpoint should respond with **2xx** quickly. * Non-2xx responses may trigger retries. * Keep your handler idempotent (you may receive the same event more than once). If a request fails and you need troubleshooting guidance, see **[Error Codes](/serverless-lora/error-codes)**. # Pricing Source: https://docs.runcomfy.com/serverless/about-billing Serverless API (ComfyUI) offers flexible, pay-per-use pricing with no upfront costs. Unlike the **Model API** (per-request pricing), Serverless API pricing is based on **GPU instance uptime** for your deployments (billed per second). *** ## Pricing overview Serverless API (ComfyUI) supports two billing plans: * **Pay as You Go**: standard hourly rates by machine tier * **Pro (subscription)**: **20%–30%** discount on Pay as You Go rates Prices and machine availability may change. Refer to [RunComfy Pricing](https://www.runcomfy.com/pricing) for the latest machine rates, plan benefits, and extras. | Machine Type | GPU Options | VRAM | RAM | vCPUs | Pay as You Go Price | Pro Price | | ------------- | ----------- | ----- | ----- | ----- | ------------------- | ----------- | | Medium | T4, A4000 | 16GB | 16GB | 8 | \$0.99/hour | \$0.79/hour | | Large | A10G, A5000 | 24GB | 32GB | 8 | \$1.75/hour | \$1.39/hour | | X-Large | A6000 | 48GB | 48GB | 28 | \$2.50/hour | \$1.99/hour | | X-Large Plus | L40S, L40 | 48GB | 64GB | 28 | \$2.99/hour | \$2.15/hour | | 2X-Large | A100 | 80GB | 96GB | 28 | \$4.99/hour | \$3.99/hour | | 2X-Large Plus | H100 | 80GB | 180GB | 28 | \$7.49/hour | \$5.99/hour | | 3X-Large | H200 | 141GB | 240GB | 24 | \$8.75/hour | \$6.99/hour | *** ## How billing works Billing is usage-based and calculated **per second**: * Billing starts when an instance is signaled to wake up (cold start + initialization). * Billing stops when the instance is fully shut down. Your deployment can run a mix of persistent and on-demand instances, controlled by: * `min_instances` / `max_instances` (autoscaling bounds) * `keep_warm_duration_in_seconds` (how long to keep idle instances warm) See also: **[Creating a Deployment](/serverless/create-a-deployment)** ### Persistent instances * Set `min_instances > 0` to keep that many instances running. * You are billed for the full uptime (including idle time) until you scale down. ### On-demand instances * Additional instances spin up to handle demand above `min_instances` (or all demand when `min_instances = 0`). * Instances scale down after the keep-warm period. * You are billed for cold start, execution, and keep-warm time. *** ## Instance cost breakdown * **Cold start**: instance boots and loads models/assets. Duration depends on machine tier, workflow complexity, and model size. * **Execution time**: workflows run. This is the main compute time. * **Keep-warm time**: idle time before scale-down. This time is billed. > **Note:** You may also see **Queue Time** (waiting for resources or concurrency). Queue time is **not** billed. *** ## Support If you believe you’ve been incorrectly billed, contact us at [**hi@runcomfy.com**](mailto:hi@runcomfy.com) with your `deployment_id`, the `request_id` (if applicable), and the approximate time of the issue. # Async Queue Endpoints Source: https://docs.runcomfy.com/serverless/async-queue-endpoints These endpoints run **asynchronous inference jobs** against a **Serverless API (ComfyUI) Deployment**. The pattern is always the same: 1. **Submit** a job → get a `request_id` immediately 2. **Poll status** (or use webhooks) until the run completes 3. **Fetch results** (hosted URLs) or handle failures 4. Optionally **cancel** a queued/running job > Tip: The Serverless API (LoRA) uses the same request-id pattern—only the request schema differs. See **[Serverless API (LoRA)](/serverless-lora/introduction)**. **Base URL**: `https://api.runcomfy.net` *** ## Queue endpoints | Endpoint | Method | Description | | ------------------------------------------------------------------- | :----: | ------------------------ | | `/prod/v2/deployments/{deployment_id}/inference` | `POST` | Submit a request | | `/prod/v2/deployments/{deployment_id}/requests/{request_id}/status` | `GET` | Check request status | | `/prod/v2/deployments/{deployment_id}/requests/{request_id}/result` | `GET` | Retrieve request results | | `/prod/v2/deployments/{deployment_id}/requests/{request_id}/cancel` | `POST` | Cancel a queued request | ## Common path parameters * `deployment_id` (string, required): The deployment you want to call. * `request_id` (string, required for status/result/cancel): Returned by the submit call. *** ## Submit a request ```text theme={null} POST /prod/v2/deployments/{deployment_id}/inference ``` You can submit requests in two ways: * **Use the deployment’s cloud-saved workflow** (most common): send an `overrides` object to change only specific node inputs. * **Send a full workflow at request time** (advanced): include `workflow_api_json` inline to run a different workflow without updating the deployment. *** ### Use cloud-saved workflow When a workflow is deployed, RunComfy stores its `workflow_api.json`. At request time, you usually send only: * `overrides`: a partial object keyed by node ID (as strings) * optional webhook fields (`webhook`, `webhook_intermediate_status`) #### What overrides reference Overrides must match the deployed `workflow_api.json`: * node IDs must exist * input keys must exist under the node’s `inputs` * values should match each node’s schema (see `object_info.json`) See: **[Workflow Files](/serverless/workflow-files)** For example, in `workflow_api.json`, node `"6"` is a `CLIPTextEncode` node that has a `text` input: ```json theme={null} { "6": { "inputs": { "text": "Add ASCII style text only the single word \"Kontext\" no additional letters to the display", "speak_and_recognation": { "__value__": [ false, true ] }, "clip": [ "38", 0 ] }, "class_type": "CLIPTextEncode", "_meta": { "title": "CLIP Text Encode (Positive Prompt)" } }, "31": { "inputs": { "seed": 736220757721744, "steps": 20, "cfg": 1, "sampler_name": "euler", "scheduler": "simple", "denoise": 1, "model": [ "37", 0 ], "positive": [ "35", 0 ], "negative": [ "135", 0 ], "latent_image": [ "124", 0 ] }, "class_type": "KSampler", "_meta": { "title": "KSampler" } } } ``` A matching override updates that node’s inputs: ```json theme={null} { "overrides": { "6": { "inputs": { "text": "futuristic cityscape" } }, "31": { "inputs": { "seed": 987654321 } } } } ``` Anything you omit keeps the default from the stored `workflow_api.json`. *** ### Request example (basic) ```bash theme={null} curl --request POST \ --url https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/inference \ --header "Content-Type: application/json" \ --header "Authorization: Bearer " \ --data '{ "overrides": { "31": { "inputs": { "seed": 987654321 } }, "6": { "inputs": { "text": "futuristic cityscape" } } } }' ``` *** ### Request example (image/video via URL) Use a **publicly accessible HTTPS URL** that returns the raw file via an unauthenticated `GET` request (no cookies, no login pages). Avoid expiring links. ```bash theme={null} curl --request POST \ --url https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/inference \ --header "Content-Type: application/json" \ --header "Authorization: Bearer " \ --data '{ "overrides": { "189": { "inputs": { "image": "https://example.com/new-image.jpg" } } } }' ``` *** ### Request example (image/video via Base64) ```bash theme={null} curl --request POST \ --url https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/inference \ --header "Content-Type: application/json" \ --header "Authorization: Bearer " \ --data '{ "overrides": { "189": { "inputs": { "image": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD..." } } } }' ``` *** ### Request example (API nodes) If your workflow uses ComfyUI Core API nodes (for example, nodes that require a [Comfy API key](https://platform.comfy.org/profile/api-keys)), include your Comfy Org API key(always starts with 'comfyui-') in the request body: ```bash theme={null} curl --request POST \ --url https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/inference \ --header "Content-Type: application/json" \ --header "Authorization: Bearer " \ --data '{ "overrides": { "10": { "inputs": { "prompt": "a golden retriever playing in a park" } } }, "extra_data": { "api_key_comfy_org": " starts with comfyui-xxxxx" } }' ``` *** ### Response example ```json theme={null} { "request_id": "{request_id}", "status_url": "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/status", "result_url": "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/result", "cancel_url": "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/cancel" } ``` Successful requests return **200 OK** with: * `request_id`: unique identifier for your job * `status_url`: poll this to track progress * `result_url`: fetch final outputs * `cancel_url`: cancel if still cancellable *** ## Send dynamic workflow (or any workflow\_api.json) Use this when you want to run a different workflow *without* changing the deployment’s stored workflow. In this mode: * include `workflow_api_json` in the request body * omit `overrides` (or keep it empty) * the job runs the workflow you sent ```bash theme={null} curl --request POST \ --url https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/inference \ --header "Content-Type: application/json" \ --header "Authorization: Bearer " \ --data '{ "workflow_api_json": {your-full-workflow-api-json-here} }' ``` ### Response example (dynamic workflow) ```json theme={null} { "request_id": "{request_id}", "status_url": "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/status", "result_url": "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/result", "cancel_url": "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/cancel" } ``` *** ## Monitor request status ```text theme={null} GET /prod/v2/deployments/{deployment_id}/requests/{request_id}/status ``` Use this endpoint to poll queue progress and determine when results are ready. A typical lifecycle is `in_queue` → `in_progress` → `completed` (or `cancelled`). ### Request example ```bash theme={null} curl --request GET \ --url https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/status \ --header "Authorization: Bearer " ``` ### Response example ```json theme={null} { "request_id": "{request_id}", "status": "in_queue", "queue_position": 0, "result_url": "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/result", "status_url": "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/status", "instance_id": "{instance_id}" } ``` Fields you may see: * `status`: `in_queue`, `in_progress`, `completed`, or `cancelled` * `queue_position`: present while `in_queue` * `instance_id`: present once an instance is running your job (useful for the instance proxy) > Want to call ComfyUI backend endpoints on the live instance? See **[Instance Proxy Endpoints](/serverless/instance-proxy-endpoints)**. *** ## Retrieve request results ```text theme={null} GET /prod/v2/deployments/{deployment_id}/requests/{request_id}/result ``` When the request completes successfully, this endpoint returns the final `outputs`. Generated assets (images/videos) are hosted on temporary storage for convenience. Hosted output URLs remain available for up to **7 days** after success; after that, they are automatically removed. If you need longer retention, download outputs or copy them into your own storage. If you need the *opposite* — no lingering retention at all, e.g. because your pipeline streams user-generated content — call the [delete endpoint](#delete-a-request) as soon as you have finished reading the result. It purges both the stored input assets and the generated outputs from serverless storage immediately. ### Request example ```bash theme={null} curl --request GET \ --url https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/result \ --header "Authorization: Bearer " ``` ### Response example ```json theme={null} { "request_id": "{request_id}", "status": "succeeded", "outputs": { "136": { "images": [ { "url": "https://example.com/ComfyUI_00001_.png", "filename": "ComfyUI_00001_.png", "subfolder": "", "type": "output" } ] } }, "created_at": "2025-07-22T13:05:16.143086", "finished_at": "2025-07-22T13:13:03.624471", "instance_id": "{instance_id}" } ``` Result payload notes: * For `succeeded`, `outputs` contains node outputs (usually hosted URLs) * For `failed`, you’ll receive an `error` object describing why the run failed * For `cancelled`, `finished_at` indicates when it was cancelled *** ## Cancel a request ```text theme={null} POST /prod/v2/deployments/{deployment_id}/requests/{request_id}/cancel ``` Use this endpoint to cancel a queued or running job. ### Request example ```bash theme={null} curl --request POST \ --url https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/cancel \ --header "Authorization: Bearer " ``` ### Response example ```json theme={null} { "request_id": "{request_id}", "status": "completed", "outcome": "cancelled" } ``` Successful requests return a **202 Accepted** status with a JSON object containing the cancellation outcome. * `outcome` (string): `cancelled` if accepted; `not_cancellable` if the job is completed or otherwise cannot be cancelled. *** ## Delete a request ```text theme={null} DELETE /prod/v2/deployments/{deployment_id}/requests/{request_id} ``` Purges every input and output asset for a request from serverless storage, bypassing the default 7-day retention window. Useful when you do not want user-uploaded content or generated outputs to sit on our side any longer than strictly necessary — for example after you have already streamed the result back to an end user. The request must be in a **terminal state** (`succeeded`, `failed`, or `cancelled`). If the job is still running, cancel it first via the [cancel endpoint](#cancel-a-request), then delete. ### Request example ```bash theme={null} curl --request DELETE \ --url https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id} \ --header "Authorization: Bearer " ``` ### Response example ```json theme={null} { "request_id": "{request_id}", "status": "deleted", "deleted_objects": 3 } ``` * `deleted_objects` (integer): number of storage objects actually removed. Zero is valid — it just means there was nothing left to delete (e.g. the call was repeated). If the request is still in progress, the call fails with **409 Conflict**. *** ## Webhooks (recommended for production) Webhooks reduce polling and give you automatic updates. See **[Webhooks](/serverless/webhooks)**. # Authentication Source: https://docs.runcomfy.com/serverless/authentication RunComfy API endpoints use **Bearer token authentication**. Send your API key in the `Authorization` header: `Authorization: Bearer ` The same API key works across RunComfy APIs (including **Serverless API (ComfyUI)**, **Serverless API (LoRA)**, and the **Model API**). *** ## Get an API key 1. Sign in to RunComfy. 2. Click your avatar in the top-right. 3. Open **Account** and find **API Token**. Alt RunComfy Profile Button Alt RunComfy API Token If you **Regenerate** your API key, the old key is revoked immediately—update all integrations and environments accordingly. *** ## Security best practices * Never expose your API key in client-side environments (browsers, mobile apps, desktop GUIs). * Route requests through a server-side proxy you control. * Treat API keys like passwords: rotate if leaked, and limit access to trusted systems. # Core Concepts Source: https://docs.runcomfy.com/serverless/core-concepts ## ComfyUI Cloud RunComfy gives you a fully managed **ComfyUI Cloud** environment that stays in sync with the official [comfyanonymous/ComfyUI](https://github.com/comfyanonymous/ComfyUI) repository. This means everything you’re used to locally, from custom nodes to downloaded models, works exactly the same in the RunComfy cloud. You can install new nodes, bring in your own models, and run workflows without compatibility issues. ## Workflows In ComfyUI, a workflow is a **visual program** built from interconnected nodes. Each node performs a specific function, and together they form a pipeline for generative AI tasks, such as creating images, videos, or other media. ## `workflow.json` The `workflow.json` file captures the full ComfyUI workflow structure, including nodes, connections, and UI elements like positions, sizes, and states. It details execution order, inputs/outputs with links, properties, and widget values, enabling easy saving, loading, and editing in the interface for sharing or backups. ## `workflow_api.json` The `workflow_api.json` file is a streamlined version for API use, omitting UI details to focus on the computational graph with node IDs, inputs (values or references), class types, and optional metadata like titles. This supports efficient programmatic execution. ## `object_info.json` The `object_info.json` file serves as a schema registry for all nodes in the ComfyUI workflow, detailing input requirements (required/optional with types, defaults, ranges, tooltips), outputs, categories, descriptions, and metadata like display\_name and python\_module. it enables validation by checking compliance with specs, preventing errors in rendering, execution. ## Cloud Saving In RunComfy, **Cloud Saving** packages your entire ComfyUI workflow, including its runtime environment, drivers, libraries, custom nodes, models, and dependencies, into a **fully reproducible container image**. This ensures your workflow runs consistently in the cloud, regardless of the underlying hardware or environment. Cloud Saving keeps workflows deployment-ready, supports **versioning** for iterative updates, and enables **private sharing** within your team, so you can collaborate smoothly without worrying about dependency conflicts. > **Note:** Community workflows in RunComfy are already pre-saved with Cloud Saving, so you can use them immediately or modify and save them as your own. ## Deployments A deployment turns a cloud-saved ComfyUI workflow into a **serverless API endpoint**. You choose the hardware (e.g., GPU type) and autoscaling settings, and RunComfy handles containerization and GPU orchestration. Your deployment becomes the **production-ready interface** for inference requests, identified by a unique `deployment_id` that you’ll use in all API calls. ## Instances An instance is a **running containerized environment** of your deployed workflow on a dedicated GPU. It’s the execution engine that processes inference requests using the full workflow. Instances are isolated for performance and security, configured at the deployment level, and **ephemeral**, they start and stop automatically based on demand, keeping costs efficient. ## Scaling Scaling in RunComfy automatically adjusts the number of active instances based on workload and your deployment settings. You can control parameters like minimum/maximum instances, queue size limits, and keep-warm durations to balance **cost efficiency** with **low-latency performance**. This ensures smooth handling of bursty or unpredictable workloads. ## Overrides (in Inference Request Body) Overrides let you **customize specific workflow inputs** directly in your API calls without resending the full `workflow_api.json` each time. Using node IDs from the `workflow_api.json`, you can change values like prompts, seeds, or media inputs while leaving everything else unchanged. This makes requests lighter, faster, and easier to maintain. # Creating a Deployment Source: https://docs.runcomfy.com/serverless/create-a-deployment A **Deployment** turns a cloud-saved ComfyUI workflow into a **serverless API endpoint** (identified by `deployment_id`). Use deployments when you want a stable endpoint with configurable **hardware** and **autoscaling**. RunComfy handles containerization, GPU allocation, and scaling—your application just calls the endpoint. > **Note:** If you don’t want to use the web UI, you can also **create deployments via API**. See **[Deployment Endpoints](/serverless/deployment-endpoints)**. *** ## Create a deployment (use web UI) You can create a deployment from: * **Deployments**: [Deployments](https://www.runcomfy.com/comfyui-api/deployments) → **Deploy workflow as API** * **My Workflows**: [My Workflows](https://www.runcomfy.com/comfyui-workflows/my-workflows) → select a workflow → **Deploy as API** * **Explore**: [Explore](https://www.runcomfy.com/comfyui-workflows) → select a workflow → **Deploy as API** *** ## 1) Select a workflow To deploy as an API, choose either: * a **custom workflow** from **My Workflows** (built/modified by you and Cloud Saved with dependencies), or * a **community workflow** from **Explore** (pre-saved and ready to deploy) For guidance on building your own workflow, see **[Custom Workflows](/serverless/custom-workflows)**. *** ## 2) Configure hardware Choose GPU hardware based on your workflow’s VRAM requirements and performance needs. Test the workflow in a ComfyUI session first to estimate usage and avoid runtime errors. Typical VRAM tiers include: * **16GB:** T4 or A4000 * **24GB:** A10G or A5000 * **48GB:** A6000 * **48GB Plus:** L40S or L40 * **80GB:** A100 * **80GB Plus:** H100 * **141GB:** H200 Exact GPU models and availability can vary. Refer to [Pricing](https://www.runcomfy.com/pricing) for the latest tiers and rates. *** ## 3) Configure autoscaling Autoscaling controls how many instances (running containerized copies of your workflow) can run in parallel and how quickly the deployment scales up/down. Common knobs: * **Minimum instances (0–30)**\ Baseline warm capacity. Setting this to `1` keeps one instance always warm (avoids cold starts but incurs ongoing cost). Use `0` to allow scale-to-zero (lowest idle cost), but the first request after idle may take a few minutes to start. * **Maximum instances (1–60)**\ Upper bound for parallel instances. Requests above capacity will queue. This caps cost and defines your concurrency ceiling. Need higher limits? Contact us. * **Queue size (≥ 1)**\ How many pending requests are allowed before the deployment tries to add capacity (up to max instances). Lower values prioritize latency; higher values buffer spikes. * **Keep warm (seconds)**\ How long an idle instance stays active after its last job before shutting down. Longer windows reduce cold-start frequency for bursty traffic, but increase idle cost. > **Tip:** A reasonable starting point for many apps is minimum `0`, maximum `1`, queue size `1`, keep warm `60`. Then tune based on real traffic. *** ## 4) Deploy Review your workflow, hardware, and scaling settings, then click **Deploy**. After creation, the deployment details page shows your `deployment_id`—use it in all inference calls. Next: **[Async Queue Endpoints](/serverless/async-queue-endpoints)**. # Custom Workflows Source: https://docs.runcomfy.com/serverless/custom-workflows This guide shows how to **create**, **test**, and **Cloud Save** your own ComfyUI workflows in RunComfy’s cloud environment, then deploy them via **Serverless API (ComfyUI)**. *** ## Overview In RunComfy, workflows are node-based graphs built in a cloud-hosted ComfyUI session. When your workflow is ready, you can **Cloud Save** it, which packages the workflow *and its full runtime* (drivers, libraries, custom nodes, models, dependencies) into a reproducible container image. Once Cloud Saved, you can deploy the workflow as a **serverless API endpoint** (a Deployment) and call it from your application. > Note: Workflows in RunComfy are kept in sync with the official [ComfyUI GitHub repository](https://github.com/comfyanonymous/ComfyUI) (including historical versions), so you can work in an interface that matches your local setup. We also keep older ComfyUI versions available, so you can switch to an earlier version if needed. *** ## Build a workflow You can start from scratch or use a community workflow as a template. ### Option A: Start from scratch 1. Go to **[My Workflows](https://www.runcomfy.com/comfyui-workflows/my-workflows)**. 2. Launch a ComfyUI session. * **ComfyUI-NodesLoaded**: common nodes pre-installed (slower startup, less setup) * **ComfyUI-Minimal**: minimal environment (faster startup, more manual setup) 3. Build your workflow on the canvas, or drag-and-drop an existing workflow JSON to import it. Alt Build a Workflow from Scratch ### Option B: Use a community template 1. Visit **[Explore](https://www.runcomfy.com/comfyui-workflows)**. 2. Browse/search for a workflow. 3. Click **Run Workflow** to load it into a machine instance. 4. Modify it as needed (nodes, models, parameters), then Cloud Save as your own workflow. Alt Use a Workflow Template *** ## Add custom nodes To install custom nodes: 1. Click the **Manager** button. 2. Use **Install Missing Custom Nodes** to auto-detect what your workflow needs, or **Install Custom Nodes** to search and install by name. 3. Restart ComfyUI using the Manager’s restart feature, then refresh your browser. Alt Install Custom Nodes in ComfyUI *** ## Add models RunComfy supports downloading models from **Civitai**, **Hugging Face**, and **Google Drive**, and uploading your own files. ### Download models (URL) 1. Click **Assets** (right sidebar) to open the file browser. 2. Navigate to the right folder (for example `models/checkpoints` or `models/loras`). 3. Paste the model URL into the download bar and click **Download**. 4. Refresh the interface to load the new model. Read more: [How to download models from Civitai, Hugging Face, and Google Drive?](https://comfyui-guides.runcomfy.com/ultimate-comfyui-how-tos-a-runcomfy-guide/how-to-download-models-from-civitai-hugging-face-and-google-drive) Alt Download Models in ComfyUI ### Upload models (local files) 1. Navigate to the target folder in the file browser. 2. Click the three-dot menu. 3. Select **Upload** and choose your files. Read more: [How to Upload/Delete/Move files in RunComfy?](https://comfyui-guides.runcomfy.com/ultimate-comfyui-how-tos-a-runcomfy-guide/how-to-upload-files-in-runcomfy) Alt Upload Models in ComfyUI *** ## Test the workflow Click **Queue Prompt** to run the workflow and preview outputs directly in output nodes. Generated files are saved to the output folder. For debugging: * check the custom node author’s GitHub repos for errors/issues * refer to common fixes in [RunComfy’s How-tos](https://comfyui-guides.runcomfy.com/ultimate-comfyui-how-tos-a-runcomfy-guide) *** ## Cloud Save the workflow **Cloud Saving** packages your workflow and its runtime into a reproducible container image. This enables: * consistent execution in the cloud * workflow **versioning** * private sharing within a team > Note: Community workflows in RunComfy (from **Explore**) are already Cloud Saved, so you can deploy them immediately or modify and save them as your own. ### Save to cloud Click **Cloud Save** in the top bar. > Note: If multiple workflow tabs are open, only the active tab is saved. Save other tabs separately as new workflows if you want to keep each one. Alt Save Workflow to Cloud in RunComfy Read more: [RunComfy ComfyUI Workflow Cloud Save and Sharing features](https://comfyui-guides.runcomfy.com/ultimate-comfyui-how-tos-a-runcomfy-guide/runcomfy-comfyui-workflow-cloud-save-and-sharing-features) ### View saved workflows Saved workflows appear on **[My Workflows](https://www.runcomfy.com/comfyui-workflows/my-workflows)**. When you launch again, RunComfy loads the saved version with its environment. Alt Saved Workflows *** ## Deploy the saved workflow Saved workflows are ready to deploy right away: * Click the **API** button in the workflow UI, **or** * Go to **[Deployments](https://www.runcomfy.com/comfyui-api/deployments)**, select your workflow, and create a Deployment. Next: **[Creating a Deployment](/serverless/create-a-deployment)**. # Deployment Endpoints Source: https://docs.runcomfy.com/serverless/deployment-endpoints The Deployment API lets you **create, retrieve, list, update, and delete** deployments in RunComfy Serverless APIs. Use deployments to control **capacity**, **hardware**, and **warm/cool-down behavior** for your serverless async queue jobs, so your workloads run with the performance and cost profile you expect. > **Note:** The Deployment API uses the same settings and behavior as the actions available on the web page at [Deployments](https://runcomfy.com/comfyui-api/deployments). ## Deployment Endpoints **Base URL:** `https://api.runcomfy.net` | Endpoint | Method | Description | | :------------------------------------- | :------- | :------------------ | | `/prod/v2/deployments` | `POST` | Create a deployment | | `/prod/v2/deployments/{deployment_id}` | `GET` | Get a deployment | | `/prod/v2/deployments/{deployment_id}` | `PATCH` | Update a deployment | | `/prod/v2/deployments` | `GET` | List deployments | | `/prod/v2/deployments/{deployment_id}` | `DELETE` | Delete a deployment | ### Common path parameters * `deployment_id`: **string** (UUID). The unique identifier of the deployment resource. Returned when you create a deployment and used for subsequent operations on that deployment. ### Common query parameters * `includes`: repeatable string. Use `includes=readme` and/or `includes=payload` to include extra fields. * `ids`: repeatable string (UUID). Filter to one or more deployment IDs, e.g. `?ids=&ids=`. *** ## Create a Deployment ```text theme={null} POST /prod/v2/deployments ``` ### Request body (JSON) | Field | Type | Required | Description | | :------------------------------ | :-------------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | string | Yes | Human-readable name for dashboards/logs. Example: `"image-to-video"`. | | `workflow_id` | string (UUID) | Yes | The workflow to deploy. Example: `"00000000-0000-0000-0000-000000001111"`. | | `workflow_version` | string | Yes | The workflow version label or tag. Example: `"v1"`. | | `hardware` | array of enum strings | Yes | **Exactly one** SKU. Although the field is an array, only a single value is supported for now. Example: `["AMPERE_48"]`. Supplying multiple values (e.g., `["AMPERE_24", "AMPERE_48"]`) will be rejected with a validation error. | | `min_instances` | integer (0–30) | Yes | Minimum instances kept warm (billable even when idle if > 0). | | `max_instances` | integer (1–60) | Yes | Upper bound for concurrent instances. Must be **≥** `min_instances`. Need higher limits? Contact us. | | `queue_size` | integer (≥ 0) | Yes | Target queue length per instance before adding capacity (subject to `max_instances`). | | `keep_warm_duration_in_seconds` | integer (≥ 0) | Yes | How long to keep an instance warm after its last job to avoid cold starts. | ### Hardware SKUs Valid `hardware` values: ``` TURING_16 AMPERE_24 AMPERE_48 ADA_48_PLUS AMPERE_80 ADA_80_PLUS HOPPER_141 ``` ### Scaling Behavior * If `min_instances > 0`, the deployment maintains at least `min_instances` warm instances to reduce cold starts, lower latency at higher cost. It will not scale below this floor, and those instances incur charges even when idle. * If `queue_size = 1`, when a second request arrives for an instance, the platform may start another instance (up to `max_instances`). ### Request example ```bash theme={null} curl --request POST \ --url https://api.runcomfy.net/prod/v2/deployments \ --header "Content-Type: application/json" \ --header "Authorization: Bearer " \ --data '{ "name": "image-to-video", "workflow_id": "00000000-0000-0000-0000-000000001111", "workflow_version": "v1", "hardware": ["AMPERE_48"], "min_instances": 0, "max_instances": 3, "queue_size": 1, "keep_warm_duration_in_seconds": 60 }' ``` ### Response example ```json theme={null} { "id": "a1b2c3d4-1111-2222-3333-abcdefabcdef", "name": "image-to-video", "created_at": "2025-09-08T11:00:07.864492Z", "updated_at": "2025-09-08T11:00:07.864492Z", "workflow_id": "00000000-0000-0000-0000-000000001111", "workflow_version": "v1", "hardware": ["AMPERE_48"], "min_instances": 0, "max_instances": 3, "queue_size": 1, "keep_warm_duration_in_seconds": 60, "status": "standby", "is_enabled": true, "payload": { "workflow_api_json": {}, "overrides": {}, "object_info_url": "" } } ``` ### Payload Explanation The `payload` object includes key configuration details for the workflow. `workflow_api_json` contains the workflow structure in API format. For more details, refer to the documentation at [`workflow_api.json`](/serverless/workflow-files). `overrides` allows for dynamic modifications to workflow inputs. To learn about using overrides, check out [using overrides](/serverless/async-queue-endpoints#use-cloud-saved-workflow). `object_info_url` provides a URL pointing to an `object_info.json` file, which serves as a schema registry for nodes. For more details, see [object\_info.json](/serverless/workflow-files). Here's an example of the `object_info_url` field: ```json theme={null} "object_info_url": "https://serverless-api-storage.runcomfy.net/object-infos/example/object_info.json" ``` *** ## Get a Deployment ```text theme={null} GET /prod/v2/deployments/{deployment_id} ``` ### Request example ```bash theme={null} curl --request GET \ --url https://api.runcomfy.net/prod/v2/deployments/a1b2c3d4-1111-2222-3333-abcdefabcdef \ --header "Accept: application/json" \ --header "Authorization: Bearer " ``` ### Response example ```json theme={null} { "id": "a1b2c3d4-1111-2222-3333-abcdefabcdef", "created_at": "2025-09-08T11:00:07.864492Z", "updated_at": "2025-09-08T11:00:07.864492Z", "name": "image-to-video", "workflow_id": "00000000-0000-0000-0000-000000001111", "workflow_version": "v1", "hardware": ["AMPERE_48"], "min_instances": 0, "max_instances": 3, "queue_size": 1, "keep_warm_duration_in_seconds": 60, "status": "standby", "is_enabled": true, "payload": { "workflow_api_json": {}, "overrides": {}, "object_info_url": "" } } ``` *** ## Update a Deployment ```text theme={null} PATCH /prod/v2/deployments/{deployment_id} ``` Partially update a deployment’s mutable fields. **Only include the field(s) you want to change in the request body; omitted fields remain unchanged.** ### Updatable fields ``` name (string) workflow_version (string) hardware (array; exactly 1 value) min_instances (integer 0–30) max_instances (integer 1–60) queue_size (integer >= 0) keep_warm_duration_in_seconds (integer >= 0) is_enabled (boolean) ``` > Need higher limits? Contact us. > Tip: Set "is\_enabled": false to stop handling new requests (and stop warm capacity). Set it back to true to re-enable. ### Request example ```bash theme={null} curl --request PATCH \ --url https://api.runcomfy.net/prod/v2/deployments/a1b2c3d4-1111-2222-3333-abcdefabcdef \ --header "Content-Type: application/json" \ --header "Authorization: Bearer " \ --data '{ "hardware": ["AMPERE_24"] }' ``` ```bash theme={null} curl --request PATCH \ --url https://api.runcomfy.net/prod/v2/deployments/a1b2c3d4-1111-2222-3333-abcdefabcdef \ --header "Content-Type: application/json" \ --header "Authorization: Bearer " \ --data '{ "min_instances": 1, "max_instances": 5 }' ``` ### Response example ```json theme={null} { "id": "a1b2c3d4-1111-2222-3333-abcdefabcdef", "created_at": "2025-09-08T11:00:07.864492Z", "updated_at": "2025-09-08T12:41:05.796289Z", "name": "image-to-video", "workflow_id": "00000000-0000-0000-0000-000000001111", "workflow_version": "v1", "hardware": ["AMPERE_24"], "min_instances": 0, "max_instances": 2, "queue_size": 2, "keep_warm_duration_in_seconds": 120, "status": "standby", "is_enabled": true, "payload": { "workflow_api_json": {}, "overrides": {}, "object_info_url": "" } } ``` *** ## List Deployments ```text theme={null} GET /prod/v2/deployments ``` ### List All Deployments Get a summary list of all deployments without readme and payload data. #### Request example ```bash theme={null} curl --request GET \ --url "https://api.runcomfy.net/prod/v2/deployments" \ --header "Authorization: Bearer " ``` #### Response example ```json theme={null} [ { "id": "a1b2c3d4-1111-2222-3333-abcdefabcdef", "created_at": "2025-08-01T09:00:00Z", "updated_at": "2025-09-01T10:00:00Z", "name": "text to video", "workflow_id": "00000000-0000-0000-0000-000000001111", "workflow_version": "v1", "hardware": ["AMPERE_24"], "min_instances": 0, "max_instances": 2, "queue_size": 1, "keep_warm_duration_in_seconds": 60, "status": "standby", "is_enabled": true }, { "id": "b5c6d7e8-4444-5555-6666-123412341234", "created_at": "2025-08-05T09:00:00Z", "updated_at": "2025-09-02T10:00:00Z", "name": "image to video", "workflow_id": "00000000-0000-0000-0000-000000002222", "workflow_version": "v1", "hardware": ["AMPERE_48"], "min_instances": 0, "max_instances": 3, "queue_size": 1, "keep_warm_duration_in_seconds": 60, "status": "standby", "is_enabled": true }, { "id": "c9d0e1f2-7777-8888-9999-deadbeefdead", "created_at": "2025-08-07T09:00:00Z", "updated_at": "2025-09-03T10:00:00Z", "name": "video to video", "workflow_id": "00000000-0000-0000-0000-000000003333", "workflow_version": "v1", "hardware": ["AMPERE_80"], "min_instances": 0, "max_instances": 1, "queue_size": 1, "keep_warm_duration_in_seconds": 60, "status": "standby", "is_enabled": true } ] ``` ### List All Deployments (Details) Get all deployments including **readme** and **payload** data using query parameters. > **Extra fields with `includes`:** > > * `?includes=payload` → adds a `payload` *(object)*: `{ "workflow_api_json": {}, "overrides": {}, "object_info_url": ""}` > * `?includes=readme` → adds a `readme` *(string, Markdown)*: `""` > * Use both `includes=readme&includes=payload` to get both fields. > > Requesting `readme` and/or `payload` can significantly increase the response size. #### Request example ```bash theme={null} curl --request GET \ --url "https://api.runcomfy.net/prod/v2/deployments?includes=readme&includes=payload" \ --header "Authorization: Bearer " ``` #### Response example > **Note:** The `payload` and `readme` fields are only included when using the `?includes=payload` and `?includes=readme` query parameters respectively. ```json theme={null} [ { "id": "a1b2c3d4-1111-2222-3333-abcdefabcdef", "created_at": "2025-08-01T09:00:00Z", "updated_at": "2025-09-01T10:00:00Z", "name": "text to video", "workflow_id": "00000000-0000-0000-0000-000000001111", "workflow_version": "v1", "hardware": ["AMPERE_24"], "min_instances": 0, "max_instances": 2, "queue_size": 1, "keep_warm_duration_in_seconds": 60, "status": "standby", "is_enabled": true, "payload": { "workflow_api_json": {}, "overrides": {}, "object_info_url": ""}, "readme": "" }, { "id": "b5c6d7e8-4444-5555-6666-123412341234", "created_at": "2025-08-05T09:00:00Z", "updated_at": "2025-09-02T10:00:00Z", "name": "image to video", "workflow_id": "00000000-0000-0000-0000-000000002222", "workflow_version": "v1", "hardware": ["AMPERE_48"], "min_instances": 0, "max_instances": 3, "queue_size": 1, "keep_warm_duration_in_seconds": 60, "status": "standby", "is_enabled": true, "payload": { "workflow_api_json": {}, "overrides": {}, "object_info_url": ""}, "readme": "" }, { "id": "c9d0e1f2-7777-8888-9999-deadbeefdead", "created_at": "2025-08-07T09:00:00Z", "updated_at": "2025-09-03T10:00:00Z", "name": "video to video", "workflow_id": "00000000-0000-0000-0000-000000003333", "workflow_version": "v1", "hardware": ["AMPERE_80"], "min_instances": 0, "max_instances": 1, "queue_size": 1, "keep_warm_duration_in_seconds": 60, "status": "standby", "is_enabled": true, "payload": { "workflow_api_json": {}, "overrides": {}, "object_info_url": ""}, "readme": "" } ] ``` ### List deployments by IDs Filter deployments by specific IDs without readme and payload data. > **Filter by IDs with `ids`** > > * `?ids=` (repeatable) → returns only those deployments #### Request example ```bash theme={null} curl --request GET \ --url "https://api.runcomfy.net/prod/v2/deployments?ids=a1b2c3d4-1111-2222-3333-abcdefabcdef&ids=b5c6d7e8-4444-5555-6666-123412341234" \ --header "Authorization: Bearer " ``` #### Response example ```json theme={null} [ { "id": "a1b2c3d4-1111-2222-3333-abcdefabcdef", "created_at": "2025-08-01T09:00:00Z", "updated_at": "2025-09-01T10:00:00Z", "name": "text to video", "workflow_id": "00000000-0000-0000-0000-000000001111", "workflow_version": "v1", "hardware": ["AMPERE_24"], "min_instances": 0, "max_instances": 2, "queue_size": 1, "keep_warm_duration_in_seconds": 60, "status": "standby", "is_enabled": true }, { "id": "b5c6d7e8-4444-5555-6666-123412341234", "created_at": "2025-08-05T09:00:00Z", "updated_at": "2025-09-02T10:00:00Z", "name": "image to video", "workflow_id": "00000000-0000-0000-0000-000000002222", "workflow_version": "v1", "hardware": ["AMPERE_48"], "min_instances": 0, "max_instances": 3, "queue_size": 1, "keep_warm_duration_in_seconds": 60, "status": "standby", "is_enabled": true } ] ``` ### List deployments by IDs (Details) Filter deployments by specific IDs and include **readme** and **payload** data. > **Filter by IDs with `includes`:** > > * `?ids=&includes=payload` → adds `payload` for selected IDs > * `?ids=&includes=readme` → adds `readme` for selected IDs > * `?ids=&ids=&includes=readme&includes=payload` → multiple IDs + both fields *(ids is repeatable)* #### Request example ```bash theme={null} curl --request GET \ --url "https://api.runcomfy.net/prod/v2/deployments?includes=readme&includes=payload&ids=a1b2c3d4-1111-2222-3333-abcdefabcdef&ids=b5c6d7e8-4444-5555-6666-123412341234" \ --header "Authorization: Bearer " ``` #### Response example > **Note:** The `payload` and `readme` fields are only included when using the `?includes=payload` and `?includes=readme` query parameters respectively. ```json theme={null} [ { "id": "a1b2c3d4-1111-2222-3333-abcdefabcdef", "created_at": "2025-08-01T09:00:00Z", "updated_at": "2025-09-01T10:00:00Z", "name": "text to video", "workflow_id": "00000000-0000-0000-0000-000000001111", "workflow_version": "v1", "hardware": ["AMPERE_24"], "min_instances": 0, "max_instances": 2, "queue_size": 1, "keep_warm_duration_in_seconds": 60, "status": "standby", "is_enabled": true, "payload": { "workflow_api_json": {}, "overrides": {}, "object_info_url": ""}, "readme": "" }, { "id": "b5c6d7e8-4444-5555-6666-123412341234", "created_at": "2025-08-05T09:00:00Z", "updated_at": "2025-09-02T10:00:00Z", "name": "image to video", "workflow_id": "00000000-0000-0000-0000-000000002222", "workflow_version": "v1", "hardware": ["AMPERE_48"], "min_instances": 0, "max_instances": 3, "queue_size": 1, "keep_warm_duration_in_seconds": 60, "status": "standby", "is_enabled": true, "payload": { "workflow_api_json": {}, "overrides": {}, "object_info_url": ""}, "readme": "" } ] ``` *** ## Delete a Deployment ```text theme={null} DELETE /prod/v2/deployments/{deployment_id} ``` ### Request example ```bash theme={null} curl --request DELETE \ --url https://api.runcomfy.net/prod/v2/deployments/a1b2c3d4-1111-2222-3333-abcdefabcdef \ --header "Authorization: Bearer " ``` ### Response example ```json theme={null} { "success": true } ``` # Deployment Explained Source: https://docs.runcomfy.com/serverless/deployment-explained Here are some of the key features of a Deployment: * **Immutable versions**: Cloud-saved workflows are snapshots (like container images). * **Stable endpoints**: A deployment ID/URL doesn’t change, even if you update the workflow version (like a K8s service endpoint stays constant). * **Explicit updates**: New workflow versions must be manually applied to deployments (just like updating an image tag in Kubernetes). When working with **RunComfy deployments** and **cloud-saved workflow versions**, it’s helpful to think in terms of Kubernetes concepts. If you don’t use Kubernetes, you can skip the analogy, the important idea is: **a Deployment is a stable endpoint pinned to a specific workflow version**. A deployment in RunComfy is similar to a **Kubernetes Deployment**. Each one is tied to a specific workflow version, just as a Kubernetes Deployment is pinned to a specific container image tag. This ensures stability: * Saving a new cloud workflow version does **not** automatically change the deployment (like pushing a new image does not automatically update pods). * If you want to use the new version, you **update the deployment spec** to point to it—keeping the same deployment ID and endpoint URL. *** ## Concept Mapping | **RunComfy** | **Kubernetes** | **Explanation** | | ----------------------------------------- | ---------------------------------------------- | --------------------------------------------------------------------------------- | | Cloud-saved workflow version | Container image (with tag) | Each workflow version is like a built image. It’s immutable and versioned. | | Deployment | Kubernetes Deployment | A deployment references a specific version/image to run, ensuring consistency. | | Deployment ID / Endpoint URL | Deployment UID / Service endpoint | These remain stable, even if the version/image inside is updated. | | Updating workflow version in a deployment | `kubectl set image` / updating deployment spec | You update the reference to a new version without changing the deployment itself. | | Saving a new workflow version | Building & pushing a new container image | Creates a new artifact, but doesn’t affect running deployments until updated. | *** ## Visual Comparison between RunComfy Deployment vs. K8s Deployment Comparison between RunComfy Deployment vs. K8s Deployment # Edit a Deployment Source: https://docs.runcomfy.com/serverless/edit-a-deployment From the [Deployments](https://www.runcomfy.com/comfyui-api/deployments) page, you can update an existing deployment to match changing requirements—upgrade to a new workflow version, adjust autoscaling, switch hardware tiers, temporarily disable it for maintenance, or delete it permanently. **Tip:** If you prefer automation (no UI), you can manage deployments via API. See **[Deployment Endpoints](/serverless/deployment-endpoints)**. *** ## Find the Edit Deployment button ### Step 1 RunComfy Edit Deployment Step #1 ### Step 2 RunComfy Edit Deployment Step #2 *** ## Update workflow version If you Cloud Saved a new workflow version (for example, you fixed bugs, optimized nodes, or added features), your deployment will **continue running the version it was created with**. To use the new version: 1. test the new version in a ComfyUI session to confirm it runs end-to-end 2. edit the deployment and select the new workflow version 3. (important) re-check your API `overrides` — node IDs or input names may have changed between versions ### Select a version in the dropdown RunComfy Edit Deployment Step #3 *** ## Change hardware You can adjust the GPU hardware tier at any time, for example, upgrade to higher VRAM for larger models or higher throughput. *** ## Change autoscaling You can update autoscaling rules at any time, including: * min/max instances * queue size * keep-warm duration Tune these settings to balance latency (warm capacity, higher max) vs. cost (scale-to-zero, shorter keep-warm). *** ## Rollout behavior (what happens after you save) Deployment changes roll out via a rolling update: * For **workflow version** or **hardware** updates: existing instances finish in-flight requests on the old config while new instances start with the updated config; once ready, new requests are routed to the new instances. * For **autoscaling-only** changes: the system adjusts the current instance pool according to the new rules. This avoids downtime for most production use cases. *** ## Disable / enable a deployment * **Disable**: new and in-flight requests are rejected and instances are shut down (stops billing). The configuration is preserved so you can re-enable later. * **Enable**: the deployment restarts and begins accepting requests again, scaling according to your autoscaling rules. *** ## Delete a deployment If a deployment is no longer needed, you can delete it permanently. This removes the deployment configuration and stops any associated costs, but your underlying workflow remains available for future deployments. Before deleting, note down any settings you want to keep for reference—the action is irreversible. *** ## Save changes RunComfy Edit Deployment Step #4 # Error Codes Source: https://docs.runcomfy.com/serverless/error-codes When an API call fails, RunComfy returns a JSON error payload with: * an HTTP status code * a numeric `error_code` (when available) * a human-readable message * optional details (for example a `"detail"` array for validation errors) This page lists the most common errors for **Serverless API (ComfyUI)**, grouped by phase. *** ## General errors (HTTP layer) ### 401001 Unauthorized API token missing/invalid/expired. Make sure you are sending: `Authorization: Bearer ` You can regenerate your token from your [Profile](https://www.runcomfy.com/profile). ### 403003 Forbidden The authenticated user does not own the deployment/request you are trying to access. Double check: * you’re using the correct `deployment_id` * you’re using the token for the account that owns that deployment ### 404001 Not Found The specified deployment, version, or request does not exist (deleted, never created, or wrong ID). Verify: * the `deployment_id` in the URL * the `request_id` you are polling * that the resource still exists in your RunComfy account ### 422001 Validation Error Request body failed schema validation. Common causes: * `overrides` is missing or malformed * node IDs don’t exist in the deployment’s `workflow_api.json` * input keys don’t exist under the target node’s `inputs` * wrong value types (enums, ranges, malformed URLs/Base64) What to do: * inspect the `"detail"` array in the response (it usually points to the exact field) * compare your payload to the deployment’s saved workflow schema * keep overrides minimal and add fields incrementally Helpful references: * **[Workflow Files](/serverless/workflow-files)** * **[Async Queue Endpoints](/serverless/async-queue-endpoints)** ### 500001 Internal Error Unexpected server-side error. Retry with exponential backoff. *** ## Deployment gating errors These errors occur before the request is accepted. ### 10001 InsufficientFunds The deployment is disabled because your balance/plan does not allow new runs. To resolve: 1. Add funds or upgrade your plan on [RunComfy Pricing](https://www.runcomfy.com/pricing) 2. Re-enable the deployment in the UI (Edit Deployment) ### 10002 DisabledDeployment The deployment is disabled. Re-enable it on the Deployment Edit page. ### 10003 DeletedDeployment The `deployment_id` no longer exists (it was deleted). Create/select a new deployment and update your client code. *** ## ComfyUI prompt queuing errors These happen when the gateway tries to queue the prompt on the ComfyUI backend. ### 10013 EmptyWorkflowApiJson The deployment is missing a saved `workflow_api.json`. Fix: * open the workflow in ComfyUI Cloud * confirm it runs * click **Cloud Save** to generate a new version * update the deployment to use that version ### 10007 ComfyUIConnectionError The gateway couldn’t reach a healthy ComfyUI backend. Typical causes: * cold start still initializing * backend crash / container restart * timeout / origin down / DNS routing issues If it persists, try lowering load (lower concurrency) or selecting a larger machine tier. ### 10009 FileUploadException The server could not retrieve the media you referenced. For image/video inputs under `overrides."".inputs.`, the value must be: * a **direct, publicly accessible HTTPS URL** that returns the file (no login pages, no cookies), **or** * a valid **Base64 data URI** (`data:;base64,...`) Local filenames/paths, share pages, redirects, or expired presigned links will fail. See upload examples in **[Async Queue Endpoints](/serverless/async-queue-endpoints)** (Request Example – Image/Video). ### 10008 ComfyUIQueuePromptError Your `overrides` do not match the deployment’s stored `workflow_api.json`. Guidelines: * treat `workflow_api.json` as the source of truth * use exact node IDs and input names * don’t replace a connected input (latent/model/etc.) with a literal value unless the workflow expects it * validate values against the node schema (`object_info.json`) References: * **[Workflow Files](/serverless/workflow-files)** * **[Async Queue Endpoints](/serverless/async-queue-endpoints)** ### 10004 QueuePromptUnexpectedError Unexpected internal error while queuing the prompt. Retry with backoff and keep overrides minimal. *** ## Status polling errors These happen while polling `.../status`. ### 10007 ComfyUIConnectionError Same as above — backend unreachable or unhealthy. ### 10012 ComfyUIRequestMissing The backend crashed or ran out of memory before it could accept your request, so the gateway has no in-flight record to track. Common signs include OOM kills and container restarts around the request time. ### 10005 PollingResultUnexpectedError Unexpected error while polling status. To debug: * run the same workflow and inputs in ComfyUI Cloud * confirm the workflow completes end-to-end * retry the API request *** ## Result retrieval errors These happen while fetching `.../result`. ### 10007 ComfyUIConnectionError Same as above — backend unreachable or unhealthy. ### 10012 ComfyUIRequestMissing Same as above — request disappeared due to crash/restart. ### 10011 ComfyUIExecutionError A node threw an exception at runtime. Common causes: * GPU out-of-memory (too large resolution/batch/steps) * unreadable/corrupt media * malformed URLs or partial downloads * type/shape mismatch between nodes Mitigations: * reduce resolution/batch/steps * reduce concurrency * switch to a larger GPU tier ### 10006 ResultRetrievalUnexpectedError The run appears finished, but outputs could not be returned. Common causes: * outputs weren’t materialized due to `cg_use_everywhere` virtual links * no outputs were written because inputs were identical to a previous run (ComfyUI caching) * workflow configuration/path prevented outputs from being written Fixes: * In ComfyUI: right-click the canvas → `Convert all UEs to real links`, then re-run * Change the seed or modify any input to force a new output * Re-run the same workflow in ComfyUI Cloud to verify outputs are produced Convert all UEs to real links ### 200001 ExecutionWithoutNewYield No new outputs were written because the run was identical to a previous one. Change the seed or any input (prompt/image/video/etc.) and retry. *** ## General server error ### 10000 InternalServerError Unexpected internal error not matching another category. Retry after a short delay. If the issue persists, collect the full error response and contact support. *** ## Getting help If you encounter an error code not listed here, or if the suggestions do not resolve your issue: 1. Review the relevant API reference page to confirm required fields and request format 2. Email support at [hi@runcomfy.com](mailto:hi@runcomfy.com) with the full error response, your `deployment_id`, your `request_id` (if applicable), and the approximate time of the issue # Instance Proxy Endpoints Source: https://docs.runcomfy.com/serverless/instance-proxy-endpoints The **Instance Proxy API** lets you call **ComfyUI’s native backend endpoints** on a **live instance**. After you submit a job via the async queue, the status response will include an `instance_id` once the instance is active. With that ID, you can send authenticated requests through a proxy path to perform operational tasks such as unloading models or freeing GPU memory. *** ## When to use the proxy Start proxy calls once the request status is **in\_progress** (after cold start) or **completed**, and you can read `instance_id` from the Status endpoint: * See: **[Async Queue Endpoints – Monitor request status](/serverless/async-queue-endpoints#monitor-request-status)** *** ## Instance proxy endpoint **Base URL**: `https://api.runcomfy.net` ```text theme={null} POST /prod/v2/deployments/{deployment_id}/instances/{instance_id}/proxy/{comfy_backend_path} ``` ## Path parameters * `deployment_id`: string (required) * `instance_id`: string (required) * `comfy_backend_path`: string (required) — the target ComfyUI backend route, e.g. `api/free` *** ## What you can call The proxy forwards your request to the live instance. Common targets include: * **ComfyUI backend** endpoints (e.g. `GET /object_info`, `POST /api/prompt`) * **ComfyUI Manager** endpoints (e.g. `POST /api/free`) *** ## Free memory / unload models You can release GPU memory or unload models via ComfyUI Manager’s native `POST /api/free` endpoint. This can be useful in long-running sessions to ensure the next request starts from a clean state. ### Request example: unload models only ```bash theme={null} curl --request POST \ --url "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/instances/{instance_id}/proxy/api/free" \ --header "Content-Type: application/json" \ --header "Authorization: Bearer " \ --data '{ "unload_models": true }' ``` Meaning in ComfyUI: Unloads currently loaded model weights (checkpoints/LoRAs/CLIP/VAE) from memory; does not clear the execution cache.\ Equivalent in the ComfyUI web UI: Manager → Unload models. Unload models in ComfyUI ### Request example: unload models and free memory ```bash theme={null} curl --request POST \ --url "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/instances/{instance_id}/proxy/api/free" \ --header "Content-Type: application/json" \ --header "Authorization: Bearer " \ --data '{ "unload_models": true, "free_memory": true }' ``` Meaning in ComfyUI: Unloads models and clears the execution cache to return cached GPU VRAM.\ Equivalent in the ComfyUI web UI: Manager → Unload models and Clear execution cache (free memory). Unload models + clear execution cache in ComfyUI ### Response example ```text theme={null} 200 OK (no response body) ``` *** ## Lifecycle notes and errors * An `instance_id` is valid only while its instance is running. If the instance shuts down due to keep-warm/idle timeout, subsequent proxy calls will fail. Submit a new job to start a fresh instance and obtain a new `instance_id`. * Immediately after submitting a request, proxy calls may fail until the job status shows `in_progress` (after cold start) or `completed` in the status endpoint. Poll status and retry once it transitions. # Introduction Source: https://docs.runcomfy.com/serverless/introduction **API v1 is being deprecated — please migrate to v2.** All new integrations should use the `/prod/v2/...` endpoints. v1 remains available for now but will not receive new features and will be retired in a future release. **Serverless API (ComfyUI)** lets you turn a **cloud-saved ComfyUI workflow** into a **callable, scalable endpoint** (a *Deployment*). You deploy a workflow once, then your application calls that deployment by `deployment_id` using an async queue API (submit → get `request_id` → poll status/result, or receive updates via webhooks). *** ## What you get With Serverless API (ComfyUI) you can: * **Deploy a workflow as an API**, no infra to manage (RunComfy handles containerization + GPU orchestration) * **Choose hardware per deployment** (GPU/VRAM tier) and change it later if requirements evolve * **Autoscale** with explicit knobs (min/max instances, queue threshold, keep-warm duration) * **Version workflows** safely (deployments are pinned to a workflow version; upgrades are explicit and reversible) * **Integrate in production** with **webhooks** and the **instance proxy** for advanced operations *** ## Key objects * **Workflow (cloud-saved)**: a ComfyUI workflow packaged together with its runtime (nodes, models, dependencies). * **Workflow version**: each Cloud Save creates an immutable version (like a container image snapshot). * **Deployment**: the serverless endpoint you call (identified by `deployment_id`), pinned to a workflow version. * **Request**: a single async inference job against a deployment (identified by `request_id`). * **Instance**: a running container for a deployment that actually executes requests; instances scale up/down based on your autoscaling settings. *** ## Typical workflow 1. Build or customize a workflow in RunComfy’s ComfyUI Cloud. 2. **Cloud Save** the workflow (creates a version). 3. Create a **Deployment** (choose hardware + autoscaling). 4. Submit inference: `POST /prod/v2/deployments/{deployment_id}/inference` 5. Poll status/result (or use webhooks). Next step: **[Quickstart](/serverless/quickstart)** *** ## How this relates to the other RunComfy APIs * **[Model API](/model-apis/quickstart)**: on-demand inference for hosted models/pipelines, **no deployment**, **per-request billing**, call by `model_id`. * **[Serverless API (LoRA)](/serverless-lora/introduction)**: built on the same serverless deployment system, but what you deploy is a **Trainer LoRA** (instead of a workflow). *** ## Alternative option: Server API (ComfyUI) If you need full control of a dedicated ComfyUI backend instance (for example, to integrate ComfyUI directly into tools like Krita, Photoshop, Blender, iClone, etc.), RunComfy also provides a **Server API** paired with the **ComfyUI Backend API**. See the Server API documentation here: [RunComfy ComfyUI Backend API](https://comfyui-guides.runcomfy.com/api-reference) # Quickstart Source: https://docs.runcomfy.com/serverless/quickstart Deploy a ComfyUI workflow as **Serverless API (ComfyUI)** and make your first inference call. This quickstart uses a pre-built community workflow (**RunComfy/FLUX**) so you can learn the end-to-end API flow first. Want to deploy your own workflow instead? See **[Custom Workflows](/serverless/custom-workflows)**. *** ## What you’ll need * A `workflow_api.json` exported from ComfyUI (**Workflow → Export (API)**) * A deployed endpoint (`deployment_id`) * An API token (Bearer token auth) *** ## Step 1: Prepare the workflow API file and overrides 1. Visit the [RunComfy/FLUX workflow](https://www.runcomfy.com/comfyui-workflows/comfyui-flux-a-new-art-image-generation) page and click **Run Workflow** to launch a ComfyUI session (initial startup may take a few minutes). 2. Once the UI loads, run the workflow once to confirm it works. 3. From the **Workflow** menu in the top-left, select **Export (API)** to download `workflow_api.json`. The exported file will be similar to [flux\_workflow\_api.json](/static/flux_workflow_api.json).\ Learn more: **[Workflow Files](/serverless/workflow-files)** Alt Export API in ComfyUI ### Identify which inputs you want to override `workflow_api.json` uses **Node IDs** as keys. To make it easier to map node IDs → nodes in the UI: * Open **Settings** (bottom-left) * Go to **Lite Graph** * Set **Node ID Badge Mode** to **Show All** Alt Enable Node ID display in ComfyUI Alt Node ID in ComfyUI ### Build an overrides object For inference requests, RunComfy uses the deployment’s saved `workflow_api.json` as the base. You usually send only an `overrides` object to customize specific inputs (prompt, seed, media URLs, etc.). Here’s an example override payload for this workflow: Alt Example API Overrides JSON ```json theme={null} { "overrides": { "6": { "inputs": { "text": "Your custom prompt here" } }, "25": { "inputs": { "noise_seed": 123456789 } } } } ``` For the full request lifecycle and override rules, see **[Async Queue Endpoints](/serverless/async-queue-endpoints)**. *** ## Step 2: Deploy the workflow as an API Go to the [Deployments](https://www.runcomfy.com/comfyui-api/deployments) page and select **Deploy workflow as API**. Search for the workflow by its name (**RunComfy/FLUX**) or ID (`00000000-0000-0000-0000-000000001111`). For a quick setup, choose **Instant Deploy**, which uses default settings like 48GB hardware (A6000) and autoscaling. These settings can be adjusted later. After the deployment is complete, copy the `deployment_id` — you’ll need it for API calls. *** ## Step 3: Authenticate All API calls require a Bearer token. Add this header to every request (replace `` with your API key): `Authorization: Bearer ` Get your API token from the [Profile](https://www.runcomfy.com/profile) page (click your avatar in the upper-right). *** ## Step 4: Submit a request Send a POST request to the inference endpoint, replacing `{deployment_id}` with your actual deployment ID. In the request body, include the overrides you prepared in Step 1 under the `overrides` field. ```bash theme={null} curl --request POST \ --url https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/inference \ --header "Content-Type: application/json" \ --header "Authorization: Bearer " \ --data '{ "overrides": { "6": { "inputs": { "text": "Your custom prompt here" } }, "25": { "inputs": { "noise_seed": 123456789 } } } }' ``` **Expected response:** ```json theme={null} { "request_id": "{request_id}", "status_url": "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/status", "result_url": "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/result", "cancel_url": "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/cancel" } ``` *** ## Image/video inputs If your workflow needs an image or video input, you can pass media in `overrides` using either: * a **public HTTPS URL**, or * a **Base64 data URI** (Replace the node ID and input name with what your workflow expects.) ### Using a public URL ```bash theme={null} curl --request POST \ --url https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/inference \ --header "Content-Type: application/json" \ --header "Authorization: Bearer " \ --data '{ "overrides": { "189": { "inputs": { "image": "https://example.com/new-image.jpg" } } } }' ``` ### Using a Base64 data URI ```bash theme={null} curl --request POST \ --url https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/inference \ --header "Content-Type: application/json" \ --header "Authorization: Bearer " \ --data '{ "overrides": { "189": { "inputs": { "image": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD..." } } } }' ``` For the full set of upload rules and limits, see **[Async Queue Endpoints](/serverless/async-queue-endpoints)** (Request Example – Image/Video). *** ## Core API nodes If your workflow uses ComfyUI Core API nodes that require an API key, send the Comfy Org API Key in the request body as shown here: **[Async Queue Endpoints – Request Example (API Nodes)](/serverless/async-queue-endpoints#request-example-api-nodes)**. *** ## Step 5: Monitor and retrieve results After submitting a request, you can track its progress and fetch outputs once it’s ready. ### Check request status Poll `status_url` until the status becomes `"completed"`: ```bash theme={null} curl --request GET \ --url https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/status \ --header "Authorization: Bearer " ``` **Example response:** ```json theme={null} { "status": "in_queue", "queue_position": 0, "result_url": "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/result" } ``` ### Retrieve request results **All results from the Serverless API are automatically deleted after 7 days. If you need long-term storage, save the results elsewhere. If you need the opposite — immediate deletion of inputs and outputs after you have streamed the result to your user — call `DELETE /prod/v2/deployments/{deployment_id}/requests/{request_id}` (see [Delete a request](/serverless/async-queue-endpoints#delete-a-request)).** ```bash theme={null} curl --request GET \ --url https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/result \ --header "Authorization: Bearer " ``` **Example response:** ```json theme={null} { "status": "succeeded", "outputs": { "136": { "images": [ { "url": "https://example.com/ComfyUI_00001_.png", "filename": "ComfyUI_00001_.png", "subfolder": "", "type": "output" } ] } }, "created_at": "2025-07-22T13:05:16.143086", "finished_at": "2025-07-22T13:13:03.624471" } ``` *** ## Webhooks (optional) Instead of polling, you can also use **[Webhooks](/serverless/webhooks)** to receive progress and final result updates. # Webhooks Source: https://docs.runcomfy.com/serverless/webhooks Webhooks let RunComfy **push request updates to your server** instead of requiring polling. When enabled on a request: * RunComfy sends `POST` callbacks with status/progress updates * you can react immediately (store results, update UI, trigger downstream jobs) * you can reduce or eliminate polling load *** ## How to enable webhooks When you submit an inference request, you can pass webhook options as **query parameters** (recommended): * `webhook`: your HTTPS endpoint that will receive callbacks (**URL-encoded**) * `webhook_intermediate_status`: set to `true` to receive intermediate updates (in queue / in progress) ```text theme={null} POST /prod/v2/deployments/{deployment_id}/inference?webhook={url_encoded_webhook}&webhook_intermediate_status=true ``` > Note: Because the webhook URL is part of the query string, it must be URL-encoded. > Example: `https://example.com/api/runcomfy/webhook` → `https%3A%2F%2Fexample.com%2Fapi%2Fruncomfy%2Fwebhook` ### Recommended request example (query parameters) ```bash theme={null} curl --request POST \ --url 'https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/inference?webhook=https%3A%2F%2Fexample.com%2Fapi%2Fruncomfy%2Fwebhook&webhook_intermediate_status=true' \ --header "Content-Type: application/json" \ --header "Authorization: Bearer " \ --data '{ "overrides": { } }' ``` ### Legacy request example (request body) The following body-based fields are still supported, but they are considered **legacy** and are not the primary recommended approach: * `webhook` * `webhook_intermediate_status` ```bash theme={null} curl --request POST \ --url https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/inference \ --header "Content-Type: application/json" \ --header "Authorization: Bearer " \ --data '{ "overrides": { }, "webhook": "https://example.com/api/runcomfy/webhook", "webhook_intermediate_status": true }' ``` *** ## Callback payloads Callbacks are delivered as JSON. Payload shape depends on the current state of the request. Common fields you’ll see: * `request_id` * `deployment_id` * `status` and/or `outcome` * `created_at` / `finished_at` * `output` (on success) — matches the output schema you get from **[GET …/result](/serverless/async-queue-endpoints#retrieve-request-results)** ### Example: in\_queue ```json theme={null} { "request_id": "{request_id}", "status_url": "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/status", "result_url": "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/result", "cancel_url": "https://api.runcomfy.net/prod/v2/deployments/{deployment_id}/requests/{request_id}/cancel" } ``` ### Example: in\_progress ```json theme={null} { "request_id": "{request_id}", "deployment_id": "{deployment_id}", "status": "in_progress", "status_url": "https://api.runcomfy.net/prod/v2/deployments/dep_abc/requests/rq_123/status", "result_url": "https://api.runcomfy.net/prod/v2/deployments/dep_abc/requests/rq_123/result", "cancel_url": "https://api.runcomfy.net/prod/v2/deployments/dep_abc/requests/rq_123/cancel", "instance_id": "{instance_id}", "created_at": "2025-11-18T10:00:00Z", "started_at": "2025-11-18T10:01:00Z" } ``` ### Example: succeeded ```json theme={null} { "request_id": "{request_id}", "deployment_id": "{deployment_id}", "status": "succeeded", "outputs": { "136": { "images": [ { "url": "https://example.com/ComfyUI_00001_.png", "filename": "ComfyUI_00001_.png", "subfolder": "", "type": "output" } ] } }, "instance_id": "{instance_id}", "created_at": "2025-11-18T10:00:00Z", "started_at": "2025-11-18T10:01:12Z", "finished_at": "2025-11-18T10:08:30Z" } ``` ### Example: failed ```json theme={null} { "request_id": "{request_id}", "deployment_id": "{deployment_id}", "status": "failed", "error": { "error": "ExampleErrorType", "details": "This is an example error message explaining the failure.", "debugInfo": "Example debug information or stack trace here.", "errorCode": 12345 }, "instance_id": "{instance_id}", "created_at": "2025-11-18T10:00:00Z", "started_at": "2025-11-18T10:01:12Z", "finished_at": "2025-11-18T10:08:30Z" } ``` *** ## Delivery and retries * Your webhook endpoint should respond with **2xx** quickly. * Non-2xx responses may trigger retries. * Keep your handler idempotent (you may receive the same event more than once). If a request fails and you need troubleshooting guidance, see **[Error Codes](/serverless/error-codes)**. # Workflow Files Source: https://docs.runcomfy.com/serverless/workflow-files When you deploy a ComfyUI workflow as **Serverless API (ComfyUI)**, you’ll work with three JSON files: * `workflow.json` — full UI export (graph + layout) * `workflow_api.json` — execution graph optimized for API calls (what deployments run) * `object_info.json` — schema registry for all nodes in a running ComfyUI instance Examples in this guide use the [RunComfy/FLUX workflow](https://www.runcomfy.com/comfyui-workflows/comfyui-flux-a-new-art-image-generation). *** ## Quick comparison | File | Contains | Typical use | | ------------------- | -------------------------------------------------------------- | --------------------------------------------------- | | `workflow.json` | Nodes + links + canvas layout (groups, positions, UI metadata) | Sharing/editing in the ComfyUI UI | | `workflow_api.json` | Only what’s required to execute (node inputs + connections) | Referenced by `overrides` when calling a Deployment | | `object_info.json` | Input/output schemas for every node in the running instance | Validating inputs, building tools/UIs, debugging | *** ## `workflow.json` `workflow.json` is the **full workflow export**. It includes nodes, positions, links, and UI elements like groups. To download `workflow.json`: 1. Open your workflow in the ComfyUI interface on RunComfy. 2. Click the **Workflow** menu in the top-left. 3. Select **Export**. Alt download ComfyUI workflow.json The file’s main content is in the `"nodes"` array, which lists each node as an object. Each node object includes keys like `"id"` (a unique number for the node), `"type"` (the node's class, e.g., "SamplerCustomAdvanced"), `"pos"` (an array \[x, y] for canvas position), `"size"` (an array \[width, height] for node dimensions), `"flags"` (an object for node states like collapsed), `"order"` (execution order index), `"mode"` (node mode, often 0 for active), `"inputs"` (an array of input objects with "name", "type", and "link" to a connection ID), `"outputs"` (an array of output objects with "name", "type", "slot\_index", and "links" array of connection IDs), `"properties"` (an object for node-specific settings), and `"widgets_values"` (an array of widget values if any). For example: ``` { "id": 13, "type": "SamplerCustomAdvanced", "pos": [ 842, 215 ], "size": [ 355.20001220703125, 106 ], "flags": {}, "order": 10, "mode": 0, "inputs": [ { "name": "noise", "type": "NOISE", "link": 37 }, { "name": "guider", "type": "GUIDER", "link": 30 }, { "name": "sampler", "type": "SAMPLER", "link": 19 }, { "name": "sigmas", "type": "SIGMAS", "link": 20 }, { "name": "latent_image", "type": "LATENT", "link": 23 } ], "outputs": [ { "name": "output", "type": "LATENT", "slot_index": 0, "links": [ 24 ] }, { "name": "denoised_output", "type": "LATENT", "links": null } ], "properties": { "Node name for S&R": "SamplerCustomAdvanced" }, "widgets_values": [] } ``` For the complete example, you can check [flux\_workflow.json](/static/flux_workflow.json). *** ## `workflow_api.json` `workflow_api.json` is a **streamlined workflow export designed for API execution**. It removes UI-related details (node positions, sizes, groups) and keeps only: * node types (`class_type`) * node inputs (`inputs`) * connections between nodes When you deploy a workflow on RunComfy, the platform stores this file internally and uses it as the basis for serverless API calls. During API calls, RunComfy references this stored file and applies your `overrides` without requiring you to resend the whole workflow. To get `workflow_api.json`: 1. Open your workflow in the ComfyUI interface on RunComfy. 2. Click the **Workflow** menu in the top-left. 3. Select **Export (API)**. Alt download ComfyUI workflow_api.json The file is a single JSON object where: * keys are node IDs (as strings) * values are node definitions (`inputs`, `class_type`, and optional `_meta`) For example: ``` { "5": { "inputs": { "width": 1024, "height": 1024, "batch_size": 1 }, "class_type": "EmptyLatentImage", "_meta": { "title": "Empty Latent Image" } }, "6": { "inputs": { "text": "n old tv with the word \\"FLUX\\" on it, sitting in an abandoned workshop environment, created in Unreal Engine 5 with Octane render in the style of ArtStation.", "clip": [ "11", 0 ] }, "class_type": "CLIPTextEncode", "_meta": { "title": "CLIP Text Encode (Prompt)" } } // other nodes } ``` For the complete example, you can check [flux\_workflow\_api.json](/static/flux_workflow_api.json). *** ## `object_info.json` `object_info.json` is a **schema catalog for a running ComfyUI instance**. It includes each node’s: * required/optional inputs * accepted types and ranges * output types * tooltips/metadata Use this file to validate inputs (for example in your own UI), or to build tools that dynamically generate/modify workflows. Fetch it from a running server: 1. Launch a ComfyUI instance on RunComfy. 2. Note the server ID. 3. Visit `https://-comfyui.runcomfy.com/object_info` in your browser. Alt download ComfyUI object_info.json For example: ``` { "KSampler": { "input": { "required": { "model": [ "MODEL", { "tooltip": "The model used for denoising the input latent." } ], "seed": [ "INT", { "default": 0, "min": 0, "max": 18446744073709551615, "control_after_generate": true, "tooltip": "The random seed used for creating the noise." } ], "steps": [ "INT", { "default": 20, "min": 1, "max": 10000, "tooltip": "The number of steps used in the denoising process." } ], "cfg": [ "FLOAT", { "default": 8.0, "min": 0.0, "max": 100.0, "step": 0.1, "round": 0.01, "tooltip": "The Classifier-Free Guidance scale balances creativity and adherence to the prompt. Higher values result in images more closely matching the prompt however too high values will negatively impact quality." } ], "sampler_name": [ [ "euler", "euler_cfg_pp", "euler_ancestral", "euler_ancestral_cfg_pp", "heun", "heunpp2", "dpm_2", "dpm_2_ancestral", "lms", "dpm_fast", "dpm_adaptive", "dpmpp_2s_ancestral", "dpmpp_2s_ancestral_cfg_pp", "dpmpp_sde", "dpmpp_sde_gpu", "dpmpp_2m", "dpmpp_2m_cfg_pp", "dpmpp_2m_sde", "dpmpp_2m_sde_gpu", "dpmpp_3m_sde", "dpmpp_3m_sde_gpu", "ddpm", "lcm", "ipndm", "ipndm_v", "deis", "res_multistep", "res_multistep_cfg_pp", "res_multistep_ancestral", "res_multistep_ancestral_cfg_pp", "gradient_estimation", "gradient_estimation_cfg_pp", "er_sde", "seeds_2", "seeds_3", "sa_solver", "sa_solver_pece", "ddim", "uni_pc", "uni_pc_bh2" ], { "tooltip": "The algorithm used when sampling, this can affect the quality, speed, and style of the generated output." } ], "scheduler": [ [ "simple", "sgm_uniform", "karras", "exponential", "ddim_uniform", "beta", "normal", "linear_quadratic", "kl_optimal" ], { "tooltip": "The scheduler controls how noise is gradually removed to form the image." } ], "positive": [ "CONDITIONING", { "tooltip": "The conditioning describing the attributes you want to include in the image." } ], "negative": [ "CONDITIONING", { "tooltip": "The conditioning describing the attributes you want to exclude from the image." } ], "latent_image": [ "LATENT", { "tooltip": "The latent image to denoise." } ], "denoise": [ "FLOAT", { "default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "The amount of denoising applied, lower values will maintain the structure of the initial image allowing for image to image sampling." } ] } }, "input_order": { "required": [ "model", "seed", "steps", "cfg", "sampler_name", "scheduler", "positive", "negative", "latent_image", "denoise" ] }, "output": [ "LATENT" ], "output_is_list": [ false ], "output_name": [ "LATENT" ], "name": "KSampler", "display_name": "KSampler", "description": "Uses the provided model, positive and negative conditioning to denoise the latent image.", "python_module": "nodes", "category": "sampling", "output_node": false, "output_tooltips": [ "The denoised latent." ] } // other nodes } ``` For the complete example, you can check [flux\_object\_info.json](/static/flux_object_info.json). *** ## Files in API calls When making API requests to a deployed workflow: * use `workflow_api.json` to find node IDs and inputs * send only the values you want to change under `overrides` (you don’t include the full file in your request) This keeps requests efficient and makes it easy to evolve your workflow over time. For exact formatting and examples, refer to **[Async Queue Endpoints](/serverless/async-queue-endpoints)**. # Workflow Versions Source: https://docs.runcomfy.com/serverless/workflow-versions RunComfy workflow versioning lets you iterate on ComfyUI workflows while keeping deployments stable and reproducible. Each time you **Cloud Save** a workflow, RunComfy creates a new **immutable version** (a packaged snapshot of the graph + runtime environment). Deployments can then be pinned to a specific version, and upgrades are explicit. *** ## Save a new version To create a new version: 1. Launch an existing workflow into a ComfyUI session. 2. Make your updates (nodes, models, parameters, etc.). 3. Click **Cloud Save** in the top bar **using the same workflow name**. RunComfy automatically increments the version number and packages the updated graph along with its complete runtime environment (drivers, libraries, custom nodes, models, and dependencies) into a new container image. *** ## Version limits and auto-cleanup Each workflow keeps up to **3 saved versions**. When you save a new version: * the oldest **non-deployed** version is removed automatically to stay within the limit * versions currently used by a Serverless API deployment are protected and will not be auto-removed *** ## View and launch versions Go to **[My Workflows](https://www.runcomfy.com/comfyui-workflows/my-workflows)**, open the three-dot menu, and select **More Versions** to see the version history. You can launch any version into a ComfyUI session to review, edit, or prepare it for deployment. *** ## Deployments and versions Deployments are pinned to a specific workflow version. Creating new versions does **not** change running endpoints—deployments keep using their currently selected version for uninterrupted operation. To roll out a new version: 1. test the new version in a ComfyUI session 2. update the deployment to point to the new version (see **[Edit a Deployment](/serverless/edit-a-deployment)**) Changes roll out with minimal downtime (similar to a rolling update) without disrupting live API traffic. # Pricing & Billing Source: https://docs.runcomfy.com/trainer-apis/about-billing Trainer runs are billed by **GPU time** while the training job is running. For the latest pricing details, see: [RunComfy Pricing](https://www.runcomfy.com/pricing) *** ## Special rate for training runs | GPU (Training) | Pay as You Go | Pro | | --------------- | ------------: | --------: | | H100 (Training) | \$4.49/hr | \$3.59/hr | | H200 (Training) | \$5.25/hr | \$4.19/hr | *** ## Practical notes * You choose training hardware via the `gpu_type` field when you **submit a training job**. * If you cancel a job, you can still call `GET .../result` to retrieve any artifacts produced so far. # Async Queue Endpoints - Datasets Source: https://docs.runcomfy.com/trainer-apis/async-queue-endpoints-datasets A **dataset** is the collection of training data you use when training a LoRA. Before you start a training job, you must create and upload a dataset first—only datasets in `READY` status can be mounted and used by a training job. ## Quickstart (minimum working flow) 1. `POST /prod/v1/trainers/datasets` → create a dataset (get `dataset_id` + `dataset_name`) 2. Upload files * **≤150MB per file**: `POST /prod/v1/trainers/datasets/{dataset_id}/upload` * **>150MB per file**: `POST /prod/v1/trainers/datasets/{dataset_id}/get-upload-endpoint` → `PUT` each file to the returned `upload_url` 3. `GET /prod/v1/trainers/datasets/{dataset_id}/status` → poll until `READY` 4. Use `dataset_name` in training job requests ## Dataset status lifecycle Datasets move through these statuses: * **`DRAFT`**: dataset resource created, but it contains **no uploaded files yet** * **`UPLOADING`**: dataset is currently receiving files (either direct upload or signed URL uploads) * **`READY`**: all uploaded files are complete and validation passed; the duration depends on **file count**, **file size**, and whether all uploads complete successfully; when it is READY, the dataset can be mounted by a training job * **`FAILED`**: upload/validation failed; `error` field is present *** ## Endpoints **Base URL**: `https://trainer-api.runcomfy.net` | Endpoint | Method | Description | | ------------------------------------------------------------- | -------- | ---------------------------------------------------------- | | `/prod/v1/trainers/datasets` | `POST` | Create a dataset resource (metadata only) | | `/prod/v1/trainers/datasets/{dataset_id}/upload` | `POST` | Upload a dataset file (**≤150MB**) | | `/prod/v1/trainers/datasets/{dataset_id}/get-upload-endpoint` | `POST` | Get **signed upload URLs** (for larger/multi-file uploads) | | `/prod/v1/trainers/datasets/{dataset_id}/status` | `GET` | Get a dataset status | | `/prod/v1/trainers/datasets` | `GET` | List datasets | | `/prod/v1/trainers/datasets/{dataset_id}` | `DELETE` | Delete a dataset | *** ## Common Parameters | Field | Type | Description | | ------------ | ------ | ----------------------------------------------------------------------------------------------------------------- | | `id` | string | Stable identifier for this dataset (used as `dataset_id` in API paths for upload/status/delete) | | `name` | string | Human-readable dataset name (used as `dataset_name` in training job requests; must be unique within your account) | | `status` | string | One of: `DRAFT`, `UPLOADING`, `READY`, `FAILED` | | `created_at` | string | ISO 8601 timestamp (microsecond precision, e.g. `2025-07-22T13:05:16.143086`) | | `updated_at` | string | ISO 8601 timestamp (microsecond precision, e.g. `2025-07-22T13:05:16.143086`) | | `error` | object | Present when `status = FAILED` | *** ## Create a dataset Create a new dataset resource (metadata only) that you will upload training files into. Right after creation, the dataset is empty (no files uploaded yet) and its `status` is `DRAFT`. ``` POST /prod/v1/trainers/datasets ``` ### Request body | Field | Type | Required | Description | | ------ | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | string | no | Optional. Human-readable dataset name. Must be unique within your account. This value is used as `dataset_name` in training job requests. If omitted, RunComfy generates one (e.g. `ds_...`). | ### Request example ```bash theme={null} curl --request POST \ --url "https://trainer-api.runcomfy.net/prod/v1/trainers/datasets" \ --header "Content-Type: application/json" \ --header "Authorization: Bearer " \ --data '{ "name": "" }' ``` ### Response example ```json theme={null} { "id": "{dataset_id}", "name": "{dataset_name}", "status": "DRAFT", "created_at": "2026-01-31T10:20:30.143086", "updated_at": "2026-01-31T10:20:30.143086" } ``` *** ## Upload a dataset file (≤150MB) Use this endpoint for small files. For larger uploads or multi-file batches, use **Get signed upload URLs**. **Rules (important):** * **Size limit**: **≤150MB per file** (150,000,000 bytes). For larger files, use signed URLs. * **Supported file types**: images, videos, and caption `.txt` files. * **Caption naming rule (critical for LoRA / AI Toolkit)**: each image/video must have a caption file with the **same base filename**. * Example: `img_0001.jpg` ↔ `img_0001.txt` * Example: `clip_0001.mp4` ↔ `clip_0001.txt` * **Track upload success per file**: check the response for each upload request. If an upload fails, the response returns an error and the file is **not** added to the dataset. * If the **same filename** is uploaded multiple times within the same `dataset_id`, the **latest upload overwrites** the previous one. * In `curl --form "file=@./path/to/file"`, the `@./path/to/file` is a local path on the machine running `curl` (relative to your current directory or an absolute path). ``` POST /prod/v1/trainers/datasets/{dataset_id}/upload ``` ### Request * `file` (required): the file to upload ### Request example ```bash theme={null} curl --request POST \ --url "https://trainer-api.runcomfy.net/prod/v1/trainers/datasets/{dataset_id}/upload" \ --header "Authorization: Bearer " \ --form "file=@./dog_01.jpg" ``` ### Response example ```json theme={null} { "id": "{dataset_id}", "name": "{dataset_name}", "object": "file", "bytes": 2134567, "created_at": "2026-01-31T10:21:05.143086", "filename": "dog_01.jpg" } ``` *** ## Get signed upload URLs (file size > 150MB) RunComfy returns short-lived signed URLs you can upload to (typically object storage). Use this when a file is >150MB. ``` POST /prod/v1/trainers/datasets/{dataset_id}/get-upload-endpoint ``` ### Request body For multi-file uploads, provide a map of `filename -> size_in_bytes`. Notes: **`size_in_bytes` must exactly match the actual file size in bytes.** RunComfy generates signed upload URLs based on the byte size you provide. If the size is incorrect (larger or smaller than the real file), the upload may be rejected by the storage service and fail. ```json theme={null} { "filenameToByteSize": { "img_0001.jpg": 2000000, "img_0001.txt": 12000, "img_0002.jpg": 3100000, "img_0002.txt": 14000 } } ``` ### Request example ```bash theme={null} curl --request POST \ --url "https://trainer-api.runcomfy.net/prod/v1/trainers/datasets/{dataset_id}/get-upload-endpoint" \ --header "Content-Type: application/json" \ --header "Authorization: Bearer " \ --data '{ "filenameToByteSize": { "img_0001.jpg": 2000000, "img_0001.txt": 12000, "img_0002.jpg": 3100000, "img_0002.txt": 14000 } }' ``` ### Response example ```json theme={null} { "uploads": { "img_0001.jpg": { "upload_url": "https://storage.example.com/presigned/datasets/ds_123/img_0001.jpg?X-Amz-Signature=...", "method": "PUT", "headers": { "Content-Type": "image/jpeg" }, "expires_at": "2026-01-31T10:40:30Z" }, "img_0001.txt": { "upload_url": "https://storage.example.com/presigned/datasets/ds_123/img_0001.txt?X-Amz-Signature=...", "method": "PUT", "headers": { "Content-Type": "text/plain" }, "expires_at": "2026-01-31T10:40:30Z" }, "img_0002.jpg": { "upload_url": "https://storage.example.com/presigned/datasets/ds_123/img_0002.jpg?X-Amz-Signature=...", "method": "PUT", "headers": { "Content-Type": "image/jpeg" }, "expires_at": "2026-01-31T10:40:30Z" }, "img_0002.txt": { "upload_url": "https://storage.example.com/presigned/datasets/ds_123/img_0002.txt?X-Amz-Signature=...", "method": "PUT", "headers": { "Content-Type": "text/plain" }, "expires_at": "2026-01-31T10:40:30Z" } } } ``` ### Upload bytes to the signed URL Use the `method` and `headers` returned in the response. ```bash theme={null} curl -X PUT \ --upload-file "./img_0001.jpg" \ -H "Content-Type: image/jpeg" \ "" ``` #### Note: * If a signed URL expires, call `get-upload-endpoint` again to get a fresh URL. * **Track upload success per file**: your client should record whether each `PUT` succeeded. A successful `PUT` typically returns HTTP **200** or **204**. If a `PUT` fails, the response returns an error and the file is **not** added to the dataset. * After all files have uploaded successfully, poll `GET /prod/v1/trainers/datasets/{dataset_id}/status` until `READY`. *** ## Get a dataset status After you finish uploading your dataset (direct upload or signed URLs), poll this endpoint until the dataset becomes `READY`. If it becomes `FAILED`, check the `error` field, fix the issue, and re-upload (or create a new dataset). The response includes a `files` array so you can see which files are currently available in the dataset. **Only successfully uploaded files appear in `files`**—files that are still uploading or that failed to upload are not listed. ``` GET /prod/v1/trainers/datasets/{dataset_id}/status ``` ### Request example ```bash theme={null} curl --request GET \ --url "https://trainer-api.runcomfy.net/prod/v1/trainers/datasets/{dataset_id}/status" \ --header "Authorization: Bearer " ``` ### Response example ```json theme={null} { "id": "{dataset_id}", "name": "{dataset_name}", "status": "READY", "files": [ { "filename": "img_0001.png", "size_bytes": 215290 }, { "filename": "img_0001.txt", "size_bytes": 24 } ], "created_at": "2026-01-31T10:20:30.143086", "updated_at": "2026-01-31T10:41:02.143086" } ``` *** ## List datasets List all datasets in your account, including their current `status`. Use this to find the dataset `name` and `id` you’ll reference in training requests. ``` GET /prod/v1/trainers/datasets ``` ### Request example ```bash theme={null} curl --request GET \ --url "https://trainer-api.runcomfy.net/prod/v1/trainers/datasets" \ --header "Authorization: Bearer " ``` ### Response example ```json theme={null} { "datasets": [ { "id": "{dataset_id}", "name": "{dataset_name}", "status": "DRAFT", "created_at": "2026-01-31T10:20:30.143086", "updated_at": "2026-01-31T10:20:30.143086" }, { "id": "{dataset_id}", "name": "{dataset_name}", "status": "READY", "created_at": "2026-01-31T10:20:30.143086", "updated_at": "2026-01-31T10:20:30.143086" } ] } ``` *** ## Delete a dataset Permanently delete a dataset by `dataset_id`. This is irreversible—only delete datasets you no longer need for training. ``` DELETE /prod/v1/trainers/datasets/{dataset_id} ``` ### Request example ```bash theme={null} curl --request DELETE \ --url "https://trainer-api.runcomfy.net/prod/v1/trainers/datasets/{dataset_id}" \ --header "Authorization: Bearer " ``` ### Response example ```json theme={null} { "id": "{dataset_id}", "name": "{dataset_name}", "deleted": true } ``` # Async Queue Endpoints - Training Jobs Source: https://docs.runcomfy.com/trainer-apis/async-queue-endpoints-training-jobs These endpoints let you submit and monitor **AI Toolkit** training jobs (typically LoRA training), then download training artifacts (checkpoints, config yaml, samples) as hosted URLs. *** ## Endpoints **Base URL**: `https://trainer-api.runcomfy.net` | Endpoint | Method | Description | | --------------------------------------------------- | ------ | ------------------------------------------------- | | `/prod/v1/trainers/ai-toolkit/jobs` | `POST` | Submit a training job | | `/prod/v1/trainers/ai-toolkit/jobs/{job_id}/status` | `GET` | Check status | | `/prod/v1/trainers/ai-toolkit/jobs/{job_id}/result` | `GET` | Retrieve training results (artifacts/checkpoints) | | `/prod/v1/trainers/ai-toolkit/jobs/{job_id}/cancel` | `POST` | Cancel a queued/running job | | `/prod/v1/trainers/ai-toolkit/jobs/{job_id}/resume` | `POST` | Resume from the latest checkpoint (if available) | | `/prod/v1/trainers/ai-toolkit/jobs/{job_id}/edit` | `POST` | Edit config of a non-running job | *** ## Quickstart (minimum working flow) 1. **Create + upload a dataset** (Dataset API) until it becomes `READY`. See: **[Training Datasets API](/trainer-apis/async-queue-endpoints-datasets)** 2. Write an **AI Toolkit YAML config** that references dataset paths in the mounted dataset folder. 3. Submit a training job with your `config_file` and required `gpu_type`. 4. Poll `status` and fetch `result` artifacts. *** ## Preparing your config file. Before submit your request, you need to save your AI Toolkit config as `config.yaml`. Your `config.yaml` is the full AI Toolkit YAML configuration for the training job (model/quantization/train/sample settings, etc.). You should set the parameters you need for your training run in this file first. Alt RunComfy config file Below is the dataset path portion inside `config_file`. This is how the dataset is mounted into the training job and referenced by the AI Toolkit config: * `training_folder` must be **`/app/ai-toolkit/output`** (fixed; do not change) * `folder_path` must be **`/app/ai-toolkit/datasets/{dataset_name}`** (fixed prefix; only `{dataset_name}` changes) This is an example for part of YAML snippet: ```yaml theme={null} job: extension config: name: "trainingjobname1" process: - save: datasets: folder_path: "/app/ai-toolkit/datasets/{dataset_name}" # ... other datasets config ... training_folder: "/app/ai-toolkit/output" # ... your model/network/train/sample config ... meta: name: "[name]" ``` **Important details:** * `{dataset_name}` is your dataset’s **`name`** from the Dataset API response (or from `GET /prod/v1/trainers/datasets`). ## Submit a training job Submit a new AI Toolkit training job (typically LoRA training) to the async queue. The job will mount your `READY` dataset and run the YAML config you provide. ``` POST /prod/v1/trainers/ai-toolkit/jobs ``` ### Request body (important fields) | Field | Type | Required | Description | | -------------------- | ------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `config_file_format` | string | ✅ | Must be `yaml` | | `config_file` | string | ✅ | Full AI Toolkit YAML config file (as a JSON string) . Note: `config_file` is **multiline YAML** but the Trainer API request body is JSON, you must **JSON-escape** the YAML into a string before sending. | | `gpu_type` | string | ✅ | Supported values: `ADA_80_PLUS` (RunComfy Trainer UI: **H100**) or `HOPPER_141` (RunComfy Trainer UI: **H200**) | | `gpu_count` | integer | ❌ | Number of GPUs. `1` for single-GPU (default), `8` for multi-GPU. Multi-GPU (`8`) is currently only supported for `ADA_80_PLUS` (H100). | ### Request example ```bash theme={null} curl --request POST \ --url "https://trainer-api.runcomfy.net/prod/v1/trainers/ai-toolkit/jobs" \ --header "Content-Type: application/json" \ --header "Authorization: Bearer " \ --data '{ "config_file_format": "yaml", "config_file": "", "gpu_type": "ADA_80_PLUS", "gpu_id":"#1" }' ``` ### Response ```json theme={null} { "id": "{job_id}", "name": "{job_name}", "status_url": "https://trainer-api.runcomfy.net/prod/v1/trainers/ai-toolkit/jobs/{job_id}/status", "result_url": "https://trainer-api.runcomfy.net/prod/v1/trainers/ai-toolkit/jobs/{job_id}/result", "cancel_url": "https://trainer-api.runcomfy.net/prod/v1/trainers/ai-toolkit/jobs/{job_id}/cancel" } ``` *** ## Monitor training job status Use this endpoint to check a training job’s current status and progress. Poll it periodically to track the lifecycle and decide when to fetch results or take action. A typical lifecycle is: `IN_QUEUE` → `RUNNING` → `STOPPED` (or `FAILED` or `CANCELED`) ### Training job status values * **`IN_QUEUE`**: Job is accepted and waiting in the queue * **`RUNNING`**: Training is currently running * **`STOPPED`**: Training has stopped without an error (typically completed successfully, or stopped due to preemption) * **`FAILED`**: Training has stopped due to an error; the response includes an `error` field describing what went wrong (for example, AI Toolkit training errors, or the job stopping due to insufficient account balance) * **`CANCELED`**: Job was canceled by the user ``` GET /prod/v1/trainers/ai-toolkit/jobs/{job_id}/status ``` ### Request example ```bash theme={null} curl --request GET \ --url "https://trainer-api.runcomfy.net/prod/v1/trainers/ai-toolkit/jobs/{job_id}/status" \ --header "Authorization: Bearer " ``` ### Response example ```json theme={null} { "id": "{job_id}", "name": "{job_name}", "status": "RUNNING", "progress": { "current_step": 320, "total_steps": 2000, "percent": 16 }, "result_url": "https://trainer-api.runcomfy.net/prod/v1/trainers/ai-toolkit/jobs/{job_id}/result", "cancel_url": "https://trainer-api.runcomfy.net/prod/v1/trainers/ai-toolkit/jobs/{job_id}/cancel" } ``` *** ## Retrieve training job results Use this endpoint to retrieve the latest artifacts produced by a training job (checkpoints, resolved config, samples) as hosted URLs. It can be called while the job is still `RUNNING` to fetch whatever is available so far. If the job ends in `FAILED`, you can still call this endpoint to download any artifacts that were produced before the failure (if any). **Note**: If the training process has already produced checkpoints/samples, the response will include whatever is available so far. The artifacts list will grow over time while the job is running. ``` GET /prod/v1/trainers/ai-toolkit/jobs/{job_id}/result ``` ### Request example ```bash theme={null} curl --request GET \ --url "https://trainer-api.runcomfy.net/prod/v1/trainers/ai-toolkit/jobs/{job_id}/result" \ --header "Authorization: Bearer " ``` ### Response example ```json theme={null} { "id": "{job_id}", "name": "{job_name}", "status": "STOPPED", "artifacts": { "checkpoints": [ { "path": "https://example.com/output/dog_portrait_lora_00000100.safetensors" }, { "path": "https://example.com/output/dog_portrait_lora_00000200.safetensors" } ], "config": { "path": "https://example.com/output/resolved_config.yaml" }, "samples": [ { "sample_index": 1, "type": "image", "step": 2000, "seed": 42, "prompt": "", "control_image": [], "path": "https://example.com/output/samples/step_2000.png" } ] }, "created_at": "2026-01-31T12:00:00.143086", "started_at": "2026-01-31T12:03:10.143086", "finished_at": "2026-01-31T16:12:34.143086" } ``` *** ## Cancel a job Use this endpoint to cancel a training job that is currently queued (`IN_QUEUE`) or executing (`RUNNING`). Cancellation stops further progress, but you can still retrieve any artifacts produced so far via the result endpoint. **Note**: After canceling, you can still call `GET .../result` to retrieve any artifacts produced so far. ``` POST /prod/v1/trainers/ai-toolkit/jobs/{job_id}/cancel ``` ### Request example ```bash theme={null} curl --request POST \ --url "https://trainer-api.runcomfy.net/prod/v1/trainers/ai-toolkit/jobs/{job_id}/cancel" \ --header "Authorization: Bearer " ``` ### Response example ```json theme={null} { "id": "{job_id}", "name": "{job_name}", "status": "CANCELED" } ``` *** ## Resume a training job Use this endpoint to resume a training job from its latest checkpoint (if available). This is useful when a job is `STOPPED` (for example, preemption) but has already produced checkpoints. If a job is `FAILED`, inspect the `error` field from the status endpoint to understand and fix the underlying issue. **Note**: * **It reuses the same `job_id`** (does not create a new job). * When resuming, RunComfy will start from the **latest checkpoint** (the highest-step checkpoint). If no checkpoint exists, the job will start from **step 0**. ``` POST /prod/v1/trainers/ai-toolkit/jobs/{job_id}/resume ``` ### Request example ```bash theme={null} curl --request POST \ --url "https://trainer-api.runcomfy.net/prod/v1/trainers/ai-toolkit/jobs/{job_id}/resume" \ --header "Content-Type: application/json" \ --header "Authorization: Bearer " \ --data '{}' ``` ### Response example ```json theme={null} { "id": "{job_id}", "name": "{job_name}", "status_url": "https://trainer-api.runcomfy.net/prod/v1/trainers/ai-toolkit/jobs/{job_id}/status", "result_url": "https://trainer-api.runcomfy.net/prod/v1/trainers/ai-toolkit/jobs/{job_id}/result", "cancel_url": "https://trainer-api.runcomfy.net/prod/v1/trainers/ai-toolkit/jobs/{job_id}/cancel" } ``` *** ## Edit a training job Use this endpoint to edit the training configuration of a non-running job. This allows updating `config_file` on a job that is currently `STOPPED`, `CANCELED`, or `FAILED`. GPU type and count are determined when you resume the job. **Note**: * The config name (`config.name`) **cannot be changed** via edit. It must remain the same as the original job. * After editing, call `POST .../resume` to re-queue the job with the updated configuration. ``` POST /prod/v1/trainers/ai-toolkit/jobs/{job_id}/edit ``` ### Request body | Field | Type | Required | Description | | -------------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------- | | `config_file_format` | string | ✅ | Must be `yaml` | | `config_file` | string | ✅ | Full AI Toolkit YAML config file (as a JSON string). Note: `config.name` must match the original job name. | ### Request example ```bash theme={null} curl --request POST \ --url "https://trainer-api.runcomfy.net/prod/v1/trainers/ai-toolkit/jobs/{job_id}/edit" \ --header "Content-Type: application/json" \ --header "Authorization: Bearer " \ --data '{ "config_file_format": "yaml", "config_file": "" }' ``` ### Response example ```json theme={null} { "id": "{job_id}", "name": "{job_name}", "status": "STOPPED", "status_url": "https://trainer-api.runcomfy.net/prod/v1/trainers/ai-toolkit/jobs/{job_id}/status", "result_url": "https://trainer-api.runcomfy.net/prod/v1/trainers/ai-toolkit/jobs/{job_id}/result", "cancel_url": "https://trainer-api.runcomfy.net/prod/v1/trainers/ai-toolkit/jobs/{job_id}/cancel" } ``` # Authentication Source: https://docs.runcomfy.com/trainer-apis/authentication Trainer API uses Bearer token authentication. Send your API token in the `Authorization` header: `Authorization: Bearer ` ## Get an API token Get your token from your [Profile](https://www.runcomfy.com/profile) page (avatar menu > **API Token**). Alt RunComfy API Token If you regenerate your token, the old token stops working immediately — update any integrations. ## Keep your token secret Do not ship your token in client-side apps (browsers, mobile apps). Route requests through a server-side service you control. # Error Codes Source: https://docs.runcomfy.com/trainer-apis/error-codes When an API call fails, RunComfy returns an HTTP error status and a JSON body that may include an `error_code` and message. This page lists error codes you may see when calling the **Trainer API**, including **datasets** (create/upload/processing) and **AI Toolkit training jobs** (submit/status/result). If you haven't yet, start with: * **[Trainer API Quickstart](/trainer-apis/quickstart)** * **[Training Datasets API](/trainer-apis/async-queue-endpoints-datasets)** * **[Training Jobs API](/trainer-apis/async-queue-endpoints-training-jobs)** *** ## Error code structure Trainer API error codes follow a consistent numeric pattern: * The **first 3 digits** match the HTTP status (e.g. `400xx`, `422xx`, `500xx`). * The **last 2 digits** are a resource-specific sequence: * **`01–49`**: Dataset errors * **`51–99`**: Job errors *** ## Dataset API error codes Applies to endpoints under `/prod/v1/trainers/datasets` (create/upload/status/list/delete). ### 40001 INVALID\_DATASET\_ID **Meaning:** The supplied dataset identifier is not a valid UUID. **Where you may see it:** Any endpoint that takes `{dataset_id}` in the URL path. **What to try:** * Use the exact `id` returned by `POST /prod/v1/trainers/datasets` (or listed by `GET /prod/v1/trainers/datasets`). * Make sure you didn’t accidentally pass a dataset **name** where a dataset **id** is required. *** ### 40401 DATASET\_NOT\_FOUND **Meaning:** Dataset does not exist, was deleted, or is not owned by the caller. **Where you may see it:** Any endpoint that takes `{dataset_id}` in the URL path. **What to try:** * Confirm the dataset exists by calling `GET /prod/v1/trainers/datasets`. * Make sure you are using the token for the account that owns the dataset. * If you recently deleted the dataset, create a new dataset and upload again. *** ### 40901 DATASET\_NAME\_CONFLICT **Meaning:** A non-deleted dataset with the same name already exists for the user. **Where you may see it:** `POST /prod/v1/trainers/datasets` **What to try:** * Pick a **unique** dataset name, or omit `name` and let RunComfy generate one (e.g. `ds_...`). * If you intended to reuse an existing dataset, call `GET /prod/v1/trainers/datasets` to find its `id` and `name`. *** ### 42201 INVALID\_DATASET\_NAME **Meaning:** Dataset name contains invalid characters. **Where you may see it:** `POST /prod/v1/trainers/datasets` **What to try:** * Avoid spaces and special characters in dataset names. * Use a simple name with letters/numbers (and optionally `_` / `-`). *** ### 42202 INVALID\_FILE\_TYPE **Meaning:** The file extension is not in the list of supported dataset formats. **Where you may see it:** `POST /prod/v1/trainers/datasets/{dataset_id}/upload` and `POST /prod/v1/trainers/datasets/{dataset_id}/get-upload-endpoint` **What to try:** * Upload supported dataset file types (images/videos) plus optional caption `.txt` files. * Ensure captions follow the pairing rule: `img_0001.jpg` ↔ `img_0001.txt`, `clip_0001.mp4` ↔ `clip_0001.txt`. *** ### 42203 FILE\_SIZE\_EXCEEDED **Meaning:** File exceeds the 150 MB direct-upload limit. **Where you may see it:** `POST /prod/v1/trainers/datasets/{dataset_id}/upload` **What to try:** * For direct upload, keep each file **≤150MB** (150,000,000 bytes). * For files **>150MB**, use signed URLs: `POST /prod/v1/trainers/datasets/{dataset_id}/get-upload-endpoint` then `PUT` bytes to `upload_url`. *** ### 42204 UPLOAD\_TO\_FAILED\_DATASET **Meaning:** Upload rejected because the dataset is in `FAILED` status. **Where you may see it:** `POST /prod/v1/trainers/datasets/{dataset_id}/upload` and `POST /prod/v1/trainers/datasets/{dataset_id}/get-upload-endpoint` **What to try:** * Check dataset status via `GET /prod/v1/trainers/datasets/{dataset_id}/status` and inspect the `error` field. * Fix the underlying issue, then **create a new dataset** and re-upload files (recommended). *** ### 42205 EMPTY\_FILE\_LIST **Meaning:** The `filenameToByteSize` map in the request body is empty. **Where you may see it:** `POST /prod/v1/trainers/datasets/{dataset_id}/get-upload-endpoint` **What to try:** * Provide a **non-empty** `filenameToByteSize` map (each filename mapped to its exact byte size). * If you meant to direct-upload a single file, use `POST /prod/v1/trainers/datasets/{dataset_id}/upload` with a `file` form field. *** ### 50001 DATASET\_INTERNAL\_ERROR **Meaning:** Unexpected internal error not matching another category. **What to try:** * Retry the request after a short delay. * If it repeats, contact support with the full error response and your `dataset_id`. *** ### 50002 UPLOAD\_IO\_ERROR **Meaning:** File write to the storage backend failed. **Where you may see it:** `POST /prod/v1/trainers/datasets/{dataset_id}/upload` **What to try:** * Retry the upload (preferably with backoff) and ensure your network is stable. * If you continue to see this error, try signed URL uploads instead (see the Datasets API doc). *** ### 50003 STORAGE\_SCAN\_ERROR **Meaning:** Failed to scan / list the dataset directory on disk. **Where you may see it:** `GET /prod/v1/trainers/datasets/{dataset_id}/status` **What to try:** * Confirm every upload completed successfully (direct upload response, or signed URL `PUT` returning 200/204). * Re-upload the problematic files (or create a new dataset and upload again). * If the issue persists, capture the full error response and contact support. *** ### 50004 DATASET\_PROCESSING\_FAILED **Meaning:** Generic dataset processing failure (fallback for legacy records). **What to try:** * Check `GET /prod/v1/trainers/datasets/{dataset_id}/status` for the dataset `error` details. * Verify dataset rules (supported file types + caption pairing by **same base filename**). * Re-upload after fixing the files (or create a new dataset and upload again), then poll until `READY`. *** ## Training Jobs (AI Toolkit) API error codes Applies to endpoints under `/prod/v1/trainers/ai-toolkit/jobs` (submit/status/result/cancel/resume/edit). ### 40051 INVALID\_JOB\_ID **Meaning:** Job identifier is not a valid UUID. **Where you may see it:** Any endpoint that takes `{job_id}` in the URL path. **What to try:** * Use the exact `job_id` returned by `POST /prod/v1/trainers/ai-toolkit/jobs`. * Double-check you didn’t paste a different identifier (for example a dataset id) into the job path. *** ### 40451 JOB\_NOT\_FOUND **Meaning:** Job does not exist, was deleted, or is not owned by the caller. **Where you may see it:** Any endpoint that takes `{job_id}` in the URL path. **What to try:** * Confirm you’re using the correct token (the job must belong to the authenticated account). * Re-submit the training job if the original job was deleted or never created successfully. *** ### 40951 JOB\_NAME\_CONFLICT **Meaning:** Job name already exists for this user. **Where you may see it:** `POST /prod/v1/trainers/ai-toolkit/jobs` **What to try:** * Pick a unique job name in your YAML config (commonly `config.name`, and/or `meta.name`). *** ### 42251 INVALID\_CONFIG\_FORMAT **Meaning:** `config_file_format` must be `yaml`. **Where you may see it:** `POST /prod/v1/trainers/ai-toolkit/jobs` **What to try:** * Set `"config_file_format": "yaml"` in the request body. *** ### 42252 INVALID\_GPU\_TYPE **Meaning:** `gpu_type` is not a supported value. **Where you may see it:** `POST /prod/v1/trainers/ai-toolkit/jobs` **What to try:** * Use one of the supported `gpu_type` values listed in **[Training Jobs API](/trainer-apis/async-queue-endpoints-training-jobs#request-body-important-fields)**. *** ### 42253 INVALID\_YAML **Meaning:** `config_file` is not valid YAML. **Where you may see it:** `POST /prod/v1/trainers/ai-toolkit/jobs` **What to try:** * Validate the YAML locally before submitting. * Make sure the JSON request body contains `config_file` as a **string** (your YAML must be JSON-escaped). *** ### 42254 DATASET\_NOT\_FOUND **Meaning:** Dataset referenced in config was not found. **Where you may see it:** `POST /prod/v1/trainers/ai-toolkit/jobs` **What to try:** * Make sure your YAML references the correct `{dataset_name}` (the dataset’s `name`, not its `id`). * Confirm the dataset exists via `GET /prod/v1/trainers/datasets`. *** ### 42255 DATASET\_NOT\_READY **Meaning:** Dataset referenced in config is not in `READY` status. **Where you may see it:** `POST /prod/v1/trainers/ai-toolkit/jobs` **What to try:** * Poll `GET /prod/v1/trainers/datasets/{dataset_id}/status` until `READY`. * If the dataset is `FAILED`, inspect `error`, fix the issue, then create a new dataset and upload again. *** ### 42256 NO\_TRAINING\_DATA **Meaning:** Dataset has no usable training files. **Where you may see it:** `POST /prod/v1/trainers/ai-toolkit/jobs` **What to try:** * Ensure the dataset contains at least one supported image/video file (and any optional captions). * Re-upload after fixing file types/paths, then wait for dataset `READY`. *** ### 42257 FLUX\_HF\_TOKEN\_REQUIRED **Meaning:** FLUX model training requires a Hugging Face token. **Where you may see it:** `POST /prod/v1/trainers/ai-toolkit/jobs` **What to try:** * Provide a valid Hugging Face token (with **read access**) in the way your training config expects (for example via a config field or secret). * Make sure the Hugging Face **model repo is authorized for your account** (many FLUX repos are gated: you must request/accept access on Hugging Face, and your token must be able to read that repo). * Follow the step-by-step guide here: **[How to set up a Hugging Face token for FLUX training](https://www.runcomfy.com/trainer/ai-toolkit/huggingface-token-flux-ostris-ai-toolkit)**. Alt Add Hugging Face token *** ### 42258 FLUX2\_OOM\_RISK **Meaning:** FLUX.2 training settings have a high out-of-memory (OOM) risk. **Where you may see it:** `POST /prod/v1/trainers/ai-toolkit/jobs` **What to try:** * Reduce `batch_size` and/or reduce resolution (`max_res`). * As a rule of thumb, this error is raised when (batch\_size \times (max\_res/1024)^2 \ge 6). *** ### 42259 QWEN\_EDIT\_CONTROL\_MISSING **Meaning:** Qwen Edit requires control images in the dataset. **Where you may see it:** `POST /prod/v1/trainers/ai-toolkit/jobs` **What to try:** * In the config\_file, add the required control images to your dataset (and re-upload), then wait for dataset `READY` and retry job submission. *** ### 42260 QWEN\_EDIT\_SAMPLE\_CONTROL\_MISSING **Meaning:** Qwen Edit requires control images in sample prompts. **Where you may see it:** `POST /prod/v1/trainers/ai-toolkit/jobs` **What to try:** * In the config\_file, update your sample configuration so each sample prompt includes the required control image(s). Alt Add Control Image in Samples *** ### 42261 WAN\_I2V\_SAMPLING\_CRASH **Meaning:** Missing Control Image in Samples. Your sample prompts are missing a **Control Image**. I2V sampling requires both a prompt and a control image for each sample. If a Control Image is missing, sampling may fail and training can stop early. **Where you may see it:** `POST /prod/v1/trainers/ai-toolkit/jobs` **What to try:** * Add a **Control Image** for every sample prompt in **Samples** in your config\_file. *** ### 42262 INVALID\_JOB\_NAME **Meaning:** Job name contains invalid characters. **Where you may see it:** `POST /prod/v1/trainers/ai-toolkit/jobs` **What to try:** * In the config\_file, update the job name in your YAML (commonly `config.name`) to avoid spaces and special characters. Alt Add Job Name *** ### 42263 MULTI\_FRAME\_LATENT\_CACHING **Meaning:** `num_frames > 1` together with `cache_latents_to_disk` is not supported. **Where you may see it:** `POST /prod/v1/trainers/ai-toolkit/jobs` **What to try:** * If you need multi-frame training, disable latent caching to disk. * If you need latent caching, set `num_frames: 1`. *** ### 42264 DIFF\_OUTPUT\_PRESERVATION\_TRIGGER **Meaning:** `diff_output_preservation` is enabled but `trigger_word` is missing. **Where you may see it:** `POST /prod/v1/trainers/ai-toolkit/jobs` **What to try:** * Set `trigger_word` when enabling `diff_output_preservation`, then resubmit. *** ### 42265 VIDEO\_LORA\_NUM\_FRAMES\_ONE **Meaning:** Your dataset contains **video samples**, but your training job's config\_file is configured with `num_frames = 1`. With video data and `num_frames = 1`, AI Toolkit can’t correctly locate and load the training frames, so the job is very likely to fail. **Where you may see it:** `POST /prod/v1/trainers/ai-toolkit/jobs` **What to try:** * Set **Num Frames** to a value **greater than 1** (for example `41` or `81`), then resubmit the job. * Double-check the dataset referenced by your config `folder_path` contains video files (or video + caption `.txt` files) and that your `num_frames` matches the dataset type. Alt Add num_frames *** ### 42266 IMAGE\_DATASET\_MULTI\_FRAMES **Meaning:** Image-only dataset is being used with `num_frames > 1`. **Where you may see it:** `POST /prod/v1/trainers/ai-toolkit/jobs` **What to try:** * If your dataset contains only images, set `num_frames: 1`. *** ### 42267 INVALID\_RESUME\_STATE **Meaning:** Job can only be resumed when it is `STOPPED` (or `CANCELED`). **Where you may see it:** `POST /prod/v1/trainers/ai-toolkit/jobs/{job_id}/resume` **What to try:** * Check the job status via `GET /prod/v1/trainers/ai-toolkit/jobs/{job_id}/status`. * Only call `resume` after the job transitions to `STOPPED` (or `CANCELED`). *** ### 42268 LTX2\_VIDEO\_I2V\_BATCH\_SIZE **Meaning:** LTX2 with video (`num_frames > 1`) or I2V (`do_i2v = true`) requires `batch_size = 1`. **Where you may see it:** `POST /prod/v1/trainers/ai-toolkit/jobs` and `POST /prod/v1/trainers/ai-toolkit/jobs/{job_id}/edit` **What to try:** * Set `train.batch_size` to `1` when training LTX2 with video data or I2V mode. * Current `batch_size > 1` will cause a tensor shape mismatch at training start. *** ### 42269 DIFF\_PRESERVATION\_CACHE\_CONFLICT **Meaning:** `diff_output_preservation` and `cache_text_embeddings` cannot both be enabled at the same time. **Where you may see it:** `POST /prod/v1/trainers/ai-toolkit/jobs` and `POST /prod/v1/trainers/ai-toolkit/jobs/{job_id}/edit` **What to try:** * Disable one of them before starting training: either turn off `diff_output_preservation` or turn off `cache_text_embeddings`. *** ### 42270 IMAGE\_MODEL\_GC\_OFF\_OOM\_RISK **Meaning:** Large image model with `gradient_checkpointing: false` has very high out-of-memory (OOM) risk. **Where you may see it:** `POST /prod/v1/trainers/ai-toolkit/jobs` and `POST /prod/v1/trainers/ai-toolkit/jobs/{job_id}/edit` **What to try:** * Turn ON Gradient Checkpointing (`train.gradient_checkpointing: true`) in your config. *** ### 42271 FLUX\_HIGH\_BATCH\_OOM\_RISK **Meaning:** FLUX.1-dev with `batch_size >= 8` has high OOM risk even with gradient checkpointing enabled. **Where you may see it:** `POST /prod/v1/trainers/ai-toolkit/jobs` and `POST /prod/v1/trainers/ai-toolkit/jobs/{job_id}/edit` **What to try:** * Set `batch_size` to 1–4 and increase `gradient_accumulation` to compensate. *** ### 42272 WAN\_VIDEO\_OOM\_RISK **Meaning:** Wan2.2 14B with 81+ frames at high resolution or batch\_size risks OOM. **Where you may see it:** `POST /prod/v1/trainers/ai-toolkit/jobs` and `POST /prod/v1/trainers/ai-toolkit/jobs/{job_id}/edit` **What to try:** * Reduce `num_frames` below 81, lower resolution, or set `batch_size` to `1`. *** ### 42273 INVALID\_GPU\_COUNT **Meaning:** `gpu_count` must be `1` or `8`, and multi-GPU (`8`) is only supported for H100 (`ADA_80_PLUS`). **Where you may see it:** `POST /prod/v1/trainers/ai-toolkit/jobs` and `POST /prod/v1/trainers/ai-toolkit/jobs/{job_id}/edit` **What to try:** * Use `gpu_count: 1` (default, single GPU) or `gpu_count: 8` (multi-GPU, H100 only). * If you need multi-GPU training, set `gpu_type` to `ADA_80_PLUS` (H100). *** ### 42274 INVALID\_LEARNING\_RATE **Meaning:** Learning rate (`train.lr`) is missing, non-numeric, or not positive. **Where you may see it:** `POST /prod/v1/trainers/ai-toolkit/jobs` and `POST /prod/v1/trainers/ai-toolkit/jobs/{job_id}/edit` **What to try:** * Set `train.lr` to a valid positive number (e.g. `1e-4`). *** ### 42275 MISSING\_MODEL\_ARCH **Meaning:** `config.process[0].model.arch` is required but missing. **Where you may see it:** `POST /prod/v1/trainers/ai-toolkit/jobs` and `POST /prod/v1/trainers/ai-toolkit/jobs/{job_id}/edit` **What to try:** * Add the `model.arch` field to your config. Supported values include `flux`, `wan` for Wan2.1/2.2, `ltx2` for LTX2, `sdxl` for SDXL, `zimage:turbo` for Z-Image-Turbo, etc. *** ### 42276 INVALID\_EDIT\_STATE **Meaning:** Job can only be edited when it is not active (`STOPPED`, `CANCELED`, or `FAILED`). **Where you may see it:** `POST /prod/v1/trainers/ai-toolkit/jobs/{job_id}/edit` **What to try:** * Check the job status via `GET /prod/v1/trainers/ai-toolkit/jobs/{job_id}/status`. * If the job is `IN_QUEUE` or `RUNNING`, cancel it first with `POST .../cancel`, then edit. *** ### 42277 CONFIG\_NAME\_IMMUTABLE **Meaning:** `config.name` cannot be changed via edit; it must match the original job name. **Where you may see it:** `POST /prod/v1/trainers/ai-toolkit/jobs/{job_id}/edit` **What to try:** * Keep the same `config.name` (and/or `meta.name`) as the original job when editing. * If you need a different name, submit a new job instead. *** ### 50051 JOB\_INTERNAL\_ERROR **Meaning:** Unexpected internal error. **What to try:** * Retry after a short delay. * If it repeats, contact support with the full error response and your `job_id`. *** ### 50052 JOB\_CREATE\_FAILED **Meaning:** Failed to create the job record in the database. **Where you may see it:** `POST /prod/v1/trainers/ai-toolkit/jobs` **What to try:** * Retry job submission once. * If it repeats, contact support with the full error response and your request payload. ## Getting help If you hit an error not listed here, contact [hi@runcomfy.com](mailto:hi@runcomfy.com) with the full error response plus your `dataset_id` (and `job_id` if applicable). # Introduction Source: https://docs.runcomfy.com/trainer-apis/introduction **Trainer API** provides HTTP endpoints to run **AI Toolkit LoRA training**. You bring your dataset and an **AI Toolkit YAML** config, and RunComfy runs the job on GPUs, then returns the outputs you need (e.g. LoRA checkpoints, resolved config, and sample images) so you can automate end-to-end fine-tuning in code. After training, you can take the resulting LoRA and run **inference** via **[Model APIs](/model-apis/quickstart)** (on-demand) or deploy it with **[Serverless API (LoRA)](/serverless-lora/introduction)**. Not sure which one to use for inference? Start with **[Choose a LoRA Inference API](/serverless-lora/api-types)**. *** ## Key concepts Trainer API revolves around two objects: ### Dataset A **Dataset** is the training data you upload for a LoRA run. You **create** it with the Dataset API, then **upload** its files until it's `READY`. It typically includes: * images/videos * caption `.txt` files (same base filename as the media) Datasets have a lifecycle (`DRAFT` → `UPLOADING` → `READY`).\ Only datasets in `READY` can be mounted by a training job. ### Training Job A **Training Job** is an async run that executes your **AI Toolkit YAML** on a GPU. When a job starts, RunComfy: * mounts your `READY` dataset into the training container (under `/app/ai-toolkit/datasets/{dataset_name}`) * runs the YAML config you provide * produces artifacts such as **LoRA checkpoints (`.safetensors`)**, resolved config, and sample outputs A training job references its input dataset by `dataset_name` (from the Dataset API). *** ## Typical workflow 1. **Create a dataset** (metadata) 2. **Upload dataset files**, then wait until it becomes `READY` 3. **Submit a training job** with your AI Toolkit YAML config (and `gpu_type`) 4. **Poll status** and **download results** (`checkpoints`, `samples`, `config`) 5. **Run inference** with the trained LoRA: * as a dedicated endpoint via **[Serverless API (LoRA)](/serverless-lora/introduction)**, or * on-demand via **[Model APIs](/model-apis/quickstart)** Next step: **[Quickstart](/trainer-apis/quickstart)** # Quickstart Source: https://docs.runcomfy.com/trainer-apis/quickstart ## What is the Trainer API? The **Trainer API** lets you: * Create and upload **training datasets** * Submit **AI Toolkit LoRA training jobs** * Poll **status/results** and download training artifacts (checkpoints, config yaml, samples) *** ## Base URL `https://trainer-api.runcomfy.net` *** ## Authentication All requests require a Bearer token: `Authorization: Bearer ` Get your token from your [Profile](https://www.runcomfy.com/profile) page. *** ## Minimum working flow 1. **Create a dataset** (metadata) 2. **Upload dataset files** (direct upload for small files, or signed URLs for large files) 3. **Wait until the dataset becomes `READY`** 4. **Submit a training job** with your AI Toolkit YAML config 5. Poll `status` and fetch `result` artifacts *** ## Example ### 1) Create a dataset ```bash theme={null} curl --request POST \ --url "https://trainer-api.runcomfy.net/prod/v1/trainers/datasets" \ --header "Content-Type: application/json" \ --header "Authorization: Bearer " \ --data '{ "name": "" }' ``` Save the returned `dataset_id` (you’ll use it in upload and status calls). ### 2) Upload files For small dataset files (≤150MB), upload directly: ```bash theme={null} curl --request POST \ --url "https://trainer-api.runcomfy.net/prod/v1/trainers/datasets/{dataset_id}/upload" \ --header "Authorization: Bearer " \ --form "file=@./dog_01.jpg" ``` For larger uploads, request signed upload URLs and `PUT` the bytes to object storage. See **[Get signed upload URLs](/trainer-apis/async-queue-endpoints-datasets)**. ### 3) Wait until the dataset is READY ```bash theme={null} curl --request GET \ --url "https://trainer-api.runcomfy.net/prod/v1/trainers/datasets/{dataset_id}/status" \ --header "Authorization: Bearer " ``` Typical dataset lifecycle: `DRAFT` → `UPLOADING` → `READY` (or `FAILED`) ### 4) Submit a training job ```bash theme={null} curl --request POST \ --url "https://trainer-api.runcomfy.net/prod/v1/trainers/ai-toolkit/jobs" \ --header "Content-Type: application/json" \ --header "Authorization: Bearer " \ --data '{ "config_file_format": "yaml", "config_file": "", "gpu_type": "ADA_80_PLUS" }' ``` Because `config_file` is a multiline YAML file, you’ll usually want to JSON-escape it with `jq` and pipe to `curl`. See **[Submit a training job](/trainer-apis/async-queue-endpoints-training-jobs#submit-a-training-job)**. ### 6) Poll status and fetch results ```bash theme={null} curl --request GET \ --url "https://trainer-api.runcomfy.net/prod/v1/trainers/ai-toolkit/jobs/{job_id}/status" \ --header "Authorization: Bearer " curl --request GET \ --url "https://trainer-api.runcomfy.net/prod/v1/trainers/ai-toolkit/jobs/{job_id}/result" \ --header "Authorization: Bearer " ```