Skip to main content
Comfy Router’s canonical, model-ID-addressed routes. Base URL: https://api.comfy.org Every endpoint below is authenticated. Send X-API-Key: <api-key> or Authorization: Bearer <jwt>. Comfy API keys can also be sent as Bearer tokens. X-API-Key takes precedence when both credential headers are supplied. See authentication headers for the key/JWT distinction and the Quickstart for access requirements.

Endpoints

GET /v2/models

List the models Comfy Router can run. List available model IDs and billing facts. Use next_cursor while has_more is true. Parameters
RouterPageCursor
Opaque pagination cursor.Type: RouterPageCursor — Opaque cursor returned as next_cursor, 1–512 characters
integer
Number of models to return in one page.Up to 100, Default: 20
Responses
RouterModelListResponse
OK - one page of the model catalog.Body: RouterModelListResponse — Headers: X-Comfy-Request-Id
RouterErrorResponse
Invalid request. Check the error type and request body.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
Missing or invalid credentials.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
The request is not allowed for this caller or model.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
Router is temporarily unavailable. Retry with backoff.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id

GET /v2/models/{provider}/{model}

Read one partner model’s catalog entry by canonical model ID. Read details for one model without listing the full catalog. Parameters
RouterProviderSegment
required
Provider portion of the canonical {provider}/{model} model ID.Type: RouterProviderSegment — Alphanumeric slug, e.g. anthropic, Up to 64 characters
RouterModelSegment
required
Model portion of the canonical {provider}/{model} model ID.Type: RouterModelSegment — Alphanumeric slug, e.g. claude-opus-4-6, Up to 128 characters
Responses
RouterModelDetail
OK - the model’s catalog entry.Body: RouterModelDetail — Headers: X-Comfy-Request-Id
RouterErrorResponse
Missing or invalid credentials.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
The request is not allowed for this caller or model.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
The model ID was not found.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
Router is temporarily unavailable. Retry with backoff.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id

POST /v2/models/{provider}/{model}

Run a partner model synchronously by canonical model ID. Run a model and receive its finished result in the same response. Parameters
RouterProviderSegment
required
Provider portion of the canonical {provider}/{model} model ID.Type: RouterProviderSegment — Alphanumeric slug, e.g. anthropic, Up to 64 characters
RouterModelSegment
required
Model portion of the canonical {provider}/{model} model ID.Type: RouterModelSegment — Alphanumeric slug, e.g. claude-opus-4-6, Up to 128 characters
string
Caller-generated key that makes retrying one logical call safe.1–255 characters
string
Selects an alternate provider to serve this model, instead of its current default; fal, wavespeed, runware, and higgsfield are the alternates, and GET /v2/models/{provider}/{model} reports which of them a given model supports. Its refusals are checked in a fixed order, and the earlier ones answer whether or not the later ones would: an unrecognized value - not a registered provider at all - is refused 400; then an alternate provider on a request whose body selects a multipart operation (an edit) is refused 409 with detail beginning “this request’s image selects the edit operation”, but only when that provider’s translator for this model cannot serve the operation - a leg whose translator does carry the media proceeds normally; then any alternate provider on a request that resolved a bring-your-own-key credential is refused 409 with detail beginning this request resolved a BYOK credential (see that response), whether or not that provider serves this model; then the named provider’s own gate, which refuses 403 with error_type: not_enabled when that provider is not turned on for you and 503 with error_type: service_unavailable when the gate cannot be evaluated; and only past all four is a real provider with no leg for this model refused 400 with error_type: invalid_input, the same answer as an unrecognized value. fallback_provider is inert rather than refused on a bring-your-own-key request, on a request whose body selects a multipart operation, and under strict_mode=true.
boolean
Only meaningful together with model_provider.Default: False
string
Controls whether Router retries this call against the model’s other registered provider when the first attempt fails for a reason attributable to Router or to the provider tried. Omitted, or any value other than false, leaves fallback on; false turns it off. A successful fallback response carries X-Comfy-Router-Fallback-Provider. On a request that resolved a bring-your-own-key credential fallback is unavailable whatever this parameter says - the attempt is simply not made and the first failure reaches you unchanged, which is inert, not refused: no 409 originates here. The same is true, for the same reason, on a request whose body selects a multipart operation and under strict_mode=true - each binds the call to one provider that cannot be faithfully replayed against another, so fallback is simply skipped rather than refused. Only an explicit model_provider is refused with that 409, and unlike this unconditional skip its multipart 409 applies only to a leg whose translator cannot serve the operation.
boolean
Opt in to refusing a top-level request-body field this model’s input schema does not declare, rather than accepting it; nested fields are not checked, and nothing is refused for a model whose schema allows undeclared fields or has no authored schema, or on a queued submit sent with strict_mode=true.Default: False
Request body application/json — RouterModelInput (required) The partner model’s native JSON input. Without model_provider the request runs native dispatch on the model’s default provider and this body is the model’s own native schema, forwarded unchanged (strict_mode is meaningless there and changes nothing). With model_provider selecting an alternate provider and strict_mode=false (the default), the body is translated into that provider’s real schema before it is sent - any native field that cannot be expressed exactly is dropped and disclosed via the response’s X-Comfy-Router-Dropped-Params header, never silently. With model_provider selecting an alternate provider and strict_mode=true no translation runs: the body must already be that alternate provider’s own real schema, not this model’s native one (see strict_mode), and is forwarded unchanged. The body’s own fields also select which operation Router runs on a model that supports more than one: for an editable image model, including an input image switches it from text-to-image to the image-to-image (edit) operation; for a Seedance video model, a first-frame image selects image-to-video and a reference image or clip selects reference-to-video. Each conditioned operation is metered on its own rate, not the base text-to-image or text-to-video rate. Responses
RouterModelOutput
OK - without model_provider, or with model_provider and strict_mode=false (the default, translated back into this model’s native contract when possible, falling back to the alternate provider’s own raw response on a translation failure - logged, never silent), the shape is this model’s own native output; with strict_mode=true it is the alternate provider’s response returned unchanged. For a model whose partner answers a generation directly as bytes, the body is those bytes under the partner’s own Content-Type rather than application/json; branch on the response Content-Type and do not assume a JSON document.Body: RouterModelOutput or raw bytes (*/*) — Headers: X-Comfy-Request-Id, X-Content-Type-Options, X-Comfy-Router-Fallback-Provider, X-Comfy-Router-Dropped-Params, X-Comfy-Credits-Used, Idempotent-Replayed, X-Committed-Spend-Limit, X-Committed-Spend-Current, X-Committed-Spend-Remaining
RouterErrorResponse
Invalid request. Check the error type and request body.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id, X-Comfy-Upstream-Status, X-Comfy-Upstream-Detail, X-Comfy-Refusal-Subject, Idempotent-Replayed
RouterErrorResponse
Missing or invalid credentials.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
A Router request-level failure - the request never reached the model, or failed for a reason the model itself did not report.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
The request is not allowed for this caller or model.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
The model ID was not found.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
Inspect X-Comfy-Error-Type: concurrency_limit_exceeded means the original call is still running, so wait for Retry-After and reuse the same key; invalid_input requires a new key. Two invalid_input conflicts on this route are not about the key at all and no new key clears either: model_provider naming an alternate provider is refused when the request body selects a multipart operation (an edit) that provider’s translator for this model cannot serve, whose detail begins “this request’s image selects the edit operation”; and, checked after that, when the request has already resolved a bring-your-own-key credential for the provider in the path, whose detail begins this request resolved a BYOK credential. Drop model_provider, or send the request without the BYOK credential and (for the multipart case) without the edit.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id, Retry-After (when concurrency_limit_exceeded)
RouterErrorResponse
The request body is too large.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterValidationErrorResponse
The request’s contents were rejected against the model’s schema.Body: RouterValidationErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id, Idempotent-Replayed
RouterErrorResponse
Inspect X-Comfy-Error-Type: concurrency_limit_exceeded means reduce in-flight calls; rate_limited means wait for the allowance window.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id, X-Committed-Spend-Limit, X-Committed-Spend-Current, X-Committed-Spend-Remaining
RouterErrorResponse
The provider’s own response could not be turned into a result (provider_error).Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id, X-Comfy-Upstream-Status
RouterErrorResponse
Router is temporarily unavailable. Retry with backoff.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id, Retry-After (capacity refusals only)
RouterErrorResponse
The request exceeded a deadline. Check the error type before retrying.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id, X-Comfy-Upstream-Status, Retry-After

GET /v2/models/{provider}/{model}/openapi.json

Read one partner model’s input and output schemas as an OpenAPI document. Read one model’s input and output schemas as a standalone OpenAPI document. Parameters
RouterProviderSegment
required
Provider portion of the canonical {provider}/{model} model ID.Type: RouterProviderSegment — Alphanumeric slug, e.g. anthropic, Up to 64 characters
RouterModelSegment
required
Model portion of the canonical {provider}/{model} model ID.Type: RouterModelSegment — Alphanumeric slug, e.g. claude-opus-4-6, Up to 128 characters
string
The ETag a caller holds from an earlier 200.
Responses
RouterModelInputSchemaDocument
OK - the model’s input and output schemas, as a standalone OpenAPI document.Body: RouterModelInputSchemaDocument — Headers: X-Comfy-Request-Id, ETag, Cache-Control
no body
Not Modified - the document is unchanged since the ETag the caller sent in If-None-Match.Headers: X-Comfy-Request-Id, ETag, Cache-Control
RouterErrorResponse
Missing or invalid credentials.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
The request is not allowed for this caller or model.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
The model ID was not found.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
Router could not complete the request.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
Router is temporarily unavailable. Retry with backoff.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id

POST /v2/models/{provider}/{model}/requests

Submit a partner model run to the queue and return immediately. Comfy Router’s queued delivery mode. The request body is the same partner-native JSON input POST /v2/models/{provider}/{model} accepts for this model - one body shape, one per-model schema, two delivery modes - but this route does not hold the connection for the result. It admits the run, answers 201 with a handle, and the caller collects the result later through the three reads below. Parameters
RouterProviderSegment
required
Lowercase provider segment of the canonical {provider}/{model} model ID - the partner whose model is being run.Type: RouterProviderSegment — Alphanumeric slug, e.g. anthropic, Up to 64 characters
RouterModelSegment
required
Lowercase model segment of the canonical {provider}/{model} model ID - the model to run within that provider.Type: RouterModelSegment — Alphanumeric slug, e.g. claude-opus-4-6, Up to 128 characters
string
Selects an alternate provider to serve this model, instead of its current default.
boolean
Only meaningful together with model_provider.Default: False
string
Caller-generated key that makes retrying one logical call safe.1–255 characters
boolean
Opt in to refusing a top-level request-body field this model’s input schema does not declare, rather than accepting it; nested fields are not checked, and nothing is refused for a model whose schema allows undeclared fields or has no authored schema, or on a queued submit sent with strict_mode=true.Default: False
Request body application/json — RouterModelInput (required) The partner model’s native JSON input, identical to the body the synchronous route accepts for this model, and it selects the operation and is metered the same way: the body’s own fields choose which operation Router runs on a model that supports more than one (an input image switches an editable image model to image-to-image; a Seedance first-frame image selects image-to-video and a reference image or clip selects reference-to-video), and each conditioned operation is metered on its own rate, not the base text-to-image or text-to-video rate. The same provider-selection contract applies at dispatch: without model_provider, or with model_provider and strict_mode=false (the default), the body is the model’s native document and is validated against the model’s own input schema before the run is admitted, so a body the model would reject is a 422 here rather than a queued request that fails minutes later - a non-strict alternate-provider body is additionally translated into that provider’s real schema at dispatch. With strict_mode=true the body must already be the alternate provider’s own schema and is forwarded unchanged: native-schema validation is skipped, exactly as on the synchronous route (see model_provider and strict_mode). Responses
RouterQueueSubmitResponse
Created - the run was admitted to the queue.Body: RouterQueueSubmitResponse — Headers: X-Comfy-Request-Id, Idempotent-Replayed
RouterErrorResponse
Missing or invalid credentials.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
Invalid request. Check the error type and request body.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
The request body is too large.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
A Router request-level failure - the request never reached the model, or failed for a reason the model itself did not report.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
The request is not allowed for this caller or model. A model whose partner answers a generation directly as bytes cannot yet be queued and is refused with not_enabled; run it on the synchronous route instead.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
The model ID was not found.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
Inspect X-Comfy-Error-Type: concurrency_limit_exceeded means the original call is still running, so wait for Retry-After and reuse the same key; invalid_input requires a new key. Two invalid_input conflicts on this route are not about the key at all and no new key clears either: model_provider naming an alternate provider is refused when the request body selects a multipart operation (an edit) that provider’s translator for this model cannot serve, whose detail begins “this request’s image selects the edit operation”; and, checked after that, when the request has already resolved a bring-your-own-key credential for the provider in the path, whose detail begins this request resolved a BYOK credential. Drop model_provider, or send the request without the BYOK credential and (for the multipart case) without the edit.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id, Retry-After (when concurrency_limit_exceeded)
RouterValidationErrorResponse
The request’s contents were rejected against the model’s schema.Body: RouterValidationErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id, Idempotent-Replayed
RouterErrorResponse
Router is temporarily unavailable. Retry with backoff.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id, Retry-After (capacity refusals only)

GET /v2/models/{provider}/{model}/requests/{request_id}

Collect the result of one submitted request. The collect endpoint. On a request that has finished successfully it returns the partner model’s own native output, byte for byte what the synchronous route’s 200 carries for the same model and the same input - so the two delivery modes produce one result shape and a caller can move between them without a second parser. Parameters
RouterProviderSegment
required
Lowercase provider segment of the canonical {provider}/{model} model ID - the partner whose model is being run.Type: RouterProviderSegment — Alphanumeric slug, e.g. anthropic, Up to 64 characters
RouterModelSegment
required
Lowercase model segment of the canonical {provider}/{model} model ID - the model to run within that provider.Type: RouterModelSegment — Alphanumeric slug, e.g. claude-opus-4-6, Up to 128 characters
RouterQueueRequestId
required
The queued request to address - the request_id the submission returned in its body.Type: RouterQueueRequestId — pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$, uuid, Up to 36 characters
Responses
RouterModelOutput
OK - the result stored for a request that produced one - a request that completed successfully, or a terminal one that carries both a recorded charge and a stored result - returned unchanged under the partner’s own media type, exactly as the synchronous route’s 200 returns it, and under the same provider-selection contract the submit accepted: without model_provider, or with model_provider and strict_mode=false (the default, translated back into this model’s native contract when possible, falling back to the alternate provider’s own raw response on a translation failure - logged, never silent), the shape is this model’s own native output; with strict_mode=true it is the alternate provider’s response returned unchanged. For a model whose partner answers a generation directly as bytes, the body is those bytes under the partner’s own Content-Type rather than application/json; branch on the response Content-Type and do not assume a JSON document.Body: RouterModelOutput or raw bytes (*/*) — Headers: X-Comfy-Request-Id, X-Content-Type-Options
RouterQueueStatusResponse
Accepted - the request has not finished.Body: RouterQueueStatusResponse — Headers: X-Comfy-Request-Id, Retry-After
RouterErrorResponse
Missing or invalid credentials.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
The request is not allowed for this caller or model.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
The model ID was not found.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
A Router request-level failure - the request never reached the model, or failed for a reason the model itself did not report.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
Router is temporarily unavailable. Retry with backoff.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
The request is in a state that conflicts with the operation. Check the error type.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
The request exceeded a deadline. Check the error type before retrying.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterValidationErrorResponse
The request’s contents were rejected against the model’s schema.Body: RouterValidationErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id, Idempotent-Replayed
RouterErrorResponse
A Router request-level failure - the request never reached the model, or failed for a reason the model itself did not report.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id

PUT /v2/models/{provider}/{model}/requests/{request_id}/cancel

Ask for one submitted request to be cancelled. Asks Comfy to stop a request that has not finished. It is a request, not a guarantee, and the 202 says exactly that: CANCELLATION_REQUESTED means the ask was accepted, not that the run has stopped. A run already on the wire at a partner may complete anyway - and a partner generation that completes is charged, whether or not anyone collected it - so a caller who needs to know what actually happened reads the status endpoint afterwards, where a cancellation that took effect is COMPLETED carrying an error_type like every other terminal outcome. Parameters
RouterProviderSegment
required
Lowercase provider segment of the canonical {provider}/{model} model ID - the partner whose model is being run.Type: RouterProviderSegment — Alphanumeric slug, e.g. anthropic, Up to 64 characters
RouterModelSegment
required
Lowercase model segment of the canonical {provider}/{model} model ID - the model to run within that provider.Type: RouterModelSegment — Alphanumeric slug, e.g. claude-opus-4-6, Up to 128 characters
RouterQueueRequestId
required
The queued request to address - the request_id the submission returned in its body.Type: RouterQueueRequestId — pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$, uuid, Up to 36 characters
Responses
RouterQueueCancelResponse
Accepted - CANCELLATION_REQUESTED.Body: RouterQueueCancelResponse — Headers: X-Comfy-Request-Id
RouterQueueCancelResponse
Conflict - ALREADY_COMPLETED.Body: RouterQueueCancelResponse — Headers: X-Comfy-Request-Id
RouterErrorResponse
Invalid request. Check the error type and request body.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
Missing or invalid credentials.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
The request is not allowed for this caller or model.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
The model ID was not found.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
Router is temporarily unavailable. Retry with backoff.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
A Router request-level failure - the request never reached the model, or failed for a reason the model itself did not report.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id

GET /v2/models/{provider}/{model}/requests/{request_id}/status

Read the queue state of one submitted request. The poll endpoint. It answers with the request’s current state and never with the result, so a client can watch a long generation without transferring its output on every poll - the result is collected once, from the read below, when this says COMPLETED. Parameters
RouterProviderSegment
required
Lowercase provider segment of the canonical {provider}/{model} model ID - the partner whose model is being run.Type: RouterProviderSegment — Alphanumeric slug, e.g. anthropic, Up to 64 characters
RouterModelSegment
required
Lowercase model segment of the canonical {provider}/{model} model ID - the model to run within that provider.Type: RouterModelSegment — Alphanumeric slug, e.g. claude-opus-4-6, Up to 128 characters
RouterQueueRequestId
required
The queued request to address - the request_id the submission returned in its body.Type: RouterQueueRequestId — pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$, uuid, Up to 36 characters
Responses
RouterQueueStatusResponse
OK - the request’s current queue state.Body: RouterQueueStatusResponse — Headers: X-Comfy-Request-Id, Retry-After
RouterErrorResponse
Missing or invalid credentials.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
The request is not allowed for this caller or model.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
The model ID was not found.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
A Router request-level failure - the request never reached the model, or failed for a reason the model itself did not report.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
Router is temporarily unavailable. Retry with backoff.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
RouterErrorResponse
A Router request-level failure - the request never reached the model, or failed for a reason the model itself did not report.Body: RouterErrorResponse — Headers: X-Comfy-Error-Type, X-Comfy-Request-Id
Descriptions here are brief. Use Using the Comfy Router API for model selection, validation, retries, and billing, and Headers for header behavior.

Error buckets

Machine-readable Router error category, also sent in the X-Comfy-Error-Type header.

Request-level buckets

Raised for a request Router accepted and then could not complete.

Transport-level buckets

Raised by Router itself, before or around the call to the model.

Response headers

string
Freshness directives for the served schema document.
string
Strong entity tag over the served document’s bytes, for GET /v2/models/{provider}/{model}/openapi.json.
boolean
Present and true when this response was served from an Idempotency-Key’s record rather than by running the model again.
integer
Seconds to wait before re-sending the same request, unchanged.Sent on: 503
integer
Seconds to wait before polling this queued request again.Sent on: 200, 202
integer
Seconds to wait before retrying the same request with the same Idempotency-Key.Sent on: 409, 504
string
What this run cost, in Comfy credits, priced from the same rate card the charge itself is billed against - so a caller needs no price table of its own, and on a run that reached the provider through more than one billed call it is their sum rather than the last one.
RouterErrorType
Coarse, machine-readable bucket for the failure, set by Router on every error response.Type: RouterErrorType
string
Which input or output a content-policy refusal was about, as a Router-level closed vocabulary: input, output, input_text, input_image, input_video, input_audio, output_text, output_image, output_video, output_audio.
string
Server-generated identifier for this call, present on every Router response - success, 4xx and 5xx alike, because an error response is exactly when a user needs an id to quote in a support request.
string
One JSON-encoded string holding an array of strings - decode it with a JSON parser rather than splitting it on commas, because it is a single string on the wire, not a comma-separated OpenAPI array, and each entry is a sentence carrying commas of its own - present whenever a translation produced this call’s request body and could not express one or more native fields exactly on the provider that served it, naming each dropped field and why, whether the caller asked for that translation with model_provider (strict_mode=false, the default) or an automatic fallback_provider retry ran it.
string
Present, naming the provider, only when fallback_provider actually retried this call against a second provider and that retry succeeded - the provider that ultimately served the call, never one that was attempted and also failed.
string
A bounded, sanitized reason the model provider gave for rejecting the request.
integer
The model provider’s own HTTP status for this call.
integer
The USD cents the caller currently has committed to calls still in flight.
integer
The ceiling, in USD cents, on the partner spend the caller may have committed to calls still in flight - money held from the moment a call is admitted and released when that call finishes.
integer
The USD cents of headroom left under the ceiling, floored at zero.
string
Always nosniff, on every successful run of a Router model.

Result assets

A model can return asset URLs, inline bytes, or both. The providers below copy selected assets onto Comfy storage and replace their URLs. This behavior depends on the model; there is no request header that selects it. These lifetimes start when the URL is signed, not when you open it. Cached or replayed URLs can have less time remaining; replay does not renew them. Download the asset promptly. Only the assets named in each row are copied: byteplus/seedream-* and byteplus/seededit-* images are not covered by the BytePlus video row. Copying is performed by the model’s native provider only. A run served by an alternate provider — model_provider naming one, or a native run whose fallback retry landed on one (disclosed by X-Comfy-Router-Fallback-Provider) — returns that provider’s own URLs, which follow the provider’s expiry and are not copied. model_provider values default, comfy and comfyui name no alternate provider, so those runs are native and copied as above, as is a model_provider run whose fallback retry landed back on the native provider. Veo (veo/*) has a separate storage path. In response.videos[], read whichever member is present: bytesBase64Encoded contains the clip inline, while gcsUri contains a Comfy-signed HTTPS link when the environment is configured for direct provider writes to Comfy storage. That link is valid for 24 hours from the response. The latter case writes the asset directly rather than copying it, so Veo is not in the rehosting table. Other models return provider asset references or inline bytes. Provider URLs follow the provider’s expiry, which can be much shorter than the lifetimes above and is not specified by the Router contract. Copying is best effort per asset. If one copy fails, that entry keeps its provider reference; the response can contain both Comfy and provider URLs, with no explicit per-asset copy-status field. The generation still succeeds and is charged. Do not infer every URL’s lifetime from one successfully rehosted asset. Whether a result is Comfy-hosted also decides whether a completed call can still be replayed from its Idempotency-Key record later; the Idempotency-Key parameter above says what a retry is answered with when it cannot be.

Per-model input and output schemas

Read each model’s fields from GET /v2/models/{provider}/{model}/openapi.json. That call needs an API key. The same document, for every model with an authored schema, is also published on this site without one, at /router-schemas/{provider}/{model}.json. That copy is a snapshot taken at the last docs update, so the endpoint is authoritative when they differ. The operation’s requestBody describes input validation; its 200 response describes the output shape and media type when authored. When x-comfy-input-schema-authored is false, Router accepts any JSON object without model-specific prevalidation. Provider requirements still apply. The output schemas describe results; Router does not validate returned provider payloads against them. An unauthored output may use */* rather than application/json; inspect the response content type before decoding it.

Schemas

RouterChargesOnPolicyRejection

Whether a content-policy refusal is charged for this model. Treat an unknown value as potentially charged. Type: string

RouterErrorResponse

Error body for authentication, access, model lookup, quota, and provider transport failures. Fields
string
required
Human-readable description of the failure, safe to surface to an end user. Not machine-parsed - branch on error_type instead. Two documented exceptions: on POST /v2/models/{provider}/{model} the 409 is answered for three unrelated conditions that share the invalid_input bucket, and only detail separates them - a detail beginning “this request’s image selects the edit operation” is the multipart model_provider refusal, and a detail beginning this request resolved a BYOK credential is the BYOK model_provider refusal; neither clears with a new Idempotency-Key. Any other invalid_input 409 on that route is the key conflict. Both prefixes are stable contract terms and may be matched. Nowhere else does branching on detail carry a compatibility guarantee.
RouterErrorType
required
Coarse, machine-readable bucket for a Router failure, mirrored on the X-Comfy-Error-Type response header so a caller can branch without parsing the body. The set is closed at nineteen values: the six request-level buckets invalid_input, content_policy_violation, provider_error, provider_timeout, insufficient_credits and model_not_found, plus the transport-level unauthorized, forbidden, concurrency_limit_exceeded, client_disconnected, internal_error, deadline_exceeded, not_enabled, service_unavailable, rate_limited, cancelled, queue_timeout, request_not_found and queue_backlog_full. Closed describes the set as documented today, not a bound that holds forever: the set is expected to grow, which is why this is deliberately a plain string and not an enum, so a client must treat an unrecognised value as internal_error rather than switch exhaustively over the list above and break on the next addition.Type: RouterErrorType
string
A bounded, sanitized reason the model provider gave for rejecting the request, present only when error_type is invalid_input and X-Comfy-Upstream-Status is a provider 4xx or 2xx - i.e. the provider refused the request as malformed and said why. Usually that status is a 4xx; it is a 2xx for a provider that reports a rejected generation inside a success envelope (a BytePlus failed-task poll is HTTP 200 with the reason in its body). Absent on every other failure, including provider 5xx, transport failures, content-policy refusals and any refusal Router raised about itself. It mirrors the X-Comfy-Upstream-Detail header.
string
Which input or output a content-policy refusal was about, as a Router-level closed vocabulary: input, output, input_text, input_image, input_video, input_audio, output_text, output_image, output_video, output_audio. The bare input / output values name the side when the provider did not name a modality. Present only when error_type is content_policy_violation and the provider named the refused subject with a machine-readable code; absent otherwise. Never provider text. Named today for BytePlus, Runway, BFL, Gemini, Veo, Vertex, xAI and Wan refusals; a provider whose refusal does not say which side it was about leaves it absent. It mirrors the X-Comfy-Refusal-Subject header.

RouterErrorType

Machine-readable Router error category, also sent in the X-Comfy-Error-Type header. Type: string

RouterModelBilling

Billing behavior to check before invoking a model. It does not include prices or usage. Fields
RouterChargesOnPolicyRejection
required
Whether a call this model refuses on content-policy grounds is nevertheless charged to the caller. Providers differ, the difference is invisible at call time, and a user who sees an error and a charge for the same call has no way to have known - so it is stated per model, before the call, rather than left to per-provider folklore.Type: RouterChargesOnPolicyRejection

RouterModelDetail

Per-model detail for one Comfy Router model: everything the catalog listing reports for it, plus the per-model fields that only the single-model route carries. Composes RouterModelListEntry, RouterModelDetailFields. Type: object

RouterModelDetailFields

Optional fields returned by the model-details endpoint. Fields
string
URL of this model’s OpenAPI document, including its input and output schemas.HTTPS URL, e.g. https://api.comfy.org/v2/models/bfl/flux-2-pro/openapi.json, Up to 2048 characters

RouterModelId

The model ID used in POST /v2/models/{provider}/{model}. Type: string — Model ID, e.g. anthropic/claude-opus-4-6, Up to 193 characters

RouterModelInput

The model input object. Read the selected model’s OpenAPI document for fields and validation. Type: object

RouterModelInputSchemaDocument

A standalone OpenAPI document for one model’s input and output. Type: object

RouterModelListEntry

A model’s ID and billing facts. Fields
RouterModelId
required
A canonical Comfy Router model ID, {provider}/{model} - exactly the value that addresses the model on POST /v2/models/{provider}/{model}, so a caller can interpolate it into that path without re-deriving it from anything. Its pattern is RouterProviderSegment and RouterModelSegment joined by a single /, and maxLength is their sum plus that separator.Type: RouterModelId — Model ID, e.g. anthropic/claude-opus-4-6, Up to 193 characters
RouterProviderSegment
required
Lowercase provider segment of the canonical {provider}/{model} model ID - the partner whose model is being addressed. The invocation route’s provider path parameter and a catalog entry’s provider field both reference this one schema, which is what keeps the listed IDs and the accepted IDs from drifting apart.Type: RouterProviderSegment — Alphanumeric slug, e.g. anthropic, Up to 64 characters
RouterModelSegment
required
Lowercase model segment of the canonical {provider}/{model} model ID - the model to run within that provider. Shared by the invocation route’s model path parameter and a catalog entry’s model field, for the same no-drift reason as RouterProviderSegment.Type: RouterModelSegment — Alphanumeric slug, e.g. claude-opus-4-6, Up to 128 characters
RouterModelBilling
required
Per-model billing facts a caller needs before invoking - not prices. Usage and cost figures never appear here.Type: RouterModelBilling

RouterModelListResponse

One page of the Router model catalog. Fields
array of RouterModelListEntry
required
The models on this page, at most limit of them.Type: array of RouterModelListEntry
boolean
required
Whether another page exists beyond this one. Keep walking while this is true; do not infer the end of the catalog from a short or empty data.
RouterPageCursor
An opaque cursor into a Router list. It is produced by the server and only ever round-tripped: it is not an offset, not a model ID, not ordered, and not stable across catalog rebuilds, so parsing one, incrementing one, or persisting one beyond the walk it came from are all outside the contract. Cursor rather than offset because the catalog is a moving list - an offset walk silently skips or repeats entries when entries are added or removed mid-walk, and a caller cannot tell that it happened.Type: RouterPageCursor — Opaque cursor returned as next_cursor, 1–512 characters
integer
required
The page size actually served. A requested limit above the maximum is clamped down to the maximum rather than rejected, so this can be smaller than the value asked for - paginate with this number, not with the one you sent, or you will assume rows you never received.1–100

RouterModelOutput

The model result object. Read the selected model’s output schema for its exact shape. Type: object

RouterModelSegment

The model portion of a {provider}/{model} model ID. Type: string — Alphanumeric slug, e.g. claude-opus-4-6, Up to 128 characters

RouterPageCursor

An opaque catalog cursor. Pass it back unchanged as cursor. Type: string — Opaque cursor returned as next_cursor, 1–512 characters

RouterProviderSegment

The provider portion of a {provider}/{model} model ID. Type: string — Alphanumeric slug, e.g. anthropic, Up to 64 characters

RouterQueueCancelResponse

The answer to a cancellation ask on the two statuses that describe a request this route resolved - the 202 and the 400. One body shape across both rather than a success envelope plus an error envelope, because both are the same statement - what cancelling found - and a client that has to parse a different type per status code gains nothing from the split. Fields
RouterQueueRequestId
required
Identifier of one queued Router request - the handle a caller polls, cancels and collects a result by.Type: RouterQueueRequestId — pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$, uuid, Up to 36 characters
RouterQueueCancelStatus
required
What a cancellation ask found, for the two outcomes that describe a request this route actually resolved. Both are mirrored by the HTTP status, so a client may branch on either.Type: RouterQueueCancelStatus

RouterQueueCancelStatus

What a cancellation ask found, for the two outcomes that describe a request this route actually resolved. Both are mirrored by the HTTP status, so a client may branch on either. Type: string

RouterQueuePosition

How many requests are ahead of this one in the queue, at the instant the response was composed. Zero means this request is at the front. Type: integer — At least 0

RouterQueueRequestId

Identifier of one queued Router request - the handle a caller polls, cancels and collects a result by. Type: string — pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$, uuid, Up to 36 characters

RouterQueueStatus

The state of a queued Router request. It has exactly three values, and unlike RouterErrorType this one is a closed enum, because the two schemas are closed in opposite directions on purpose. RouterErrorType classifies failures and its set is expected to grow, so a generated client that hard-rejected an unrecognised bucket would fail hardest exactly when something had already gone wrong. This one is a lifecycle, and a lifecycle with a fourth state added later is a breaking change to every polling loop written against it whether it is declared as an enum or not - so it is declared as one, and the constraint is stated where a client can see it. Type: string

RouterQueueStatusFields

The half of RouterQueueStatusResponse that is not the URL block: one queued request’s identity, its current state, and - when that state is terminal and the run did not succeed - the coarse bucket saying why. Fields
RouterQueueRequestId
required
Identifier of one queued Router request - the handle a caller polls, cancels and collects a result by.Type: RouterQueueRequestId — pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$, uuid, Up to 36 characters
RouterQueueStatus
required
The state of a queued Router request. It has exactly three values, and unlike RouterErrorType this one is a closed enum, because the two schemas are closed in opposite directions on purpose. RouterErrorType classifies failures and its set is expected to grow, so a generated client that hard-rejected an unrecognised bucket would fail hardest exactly when something had already gone wrong. This one is a lifecycle, and a lifecycle with a fourth state added later is a breaking change to every polling loop written against it whether it is declared as an enum or not - so it is declared as one, and the constraint is stated where a client can see it.Type: RouterQueueStatus
RouterQueuePosition
How many requests are ahead of this one in the queue, at the instant the response was composed. Zero means this request is at the front.Type: RouterQueuePosition — At least 0
RouterErrorType
Present only on a COMPLETED request that did not succeed, carrying the same coarse bucket the result read puts on X-Comfy-Error-Type when it returns that failure. It is what distinguishes a terminal request that succeeded from one that failed or was cancelled - there is no separate terminal status for either - and it is absent on success rather than null, so branch on its presence.Type: RouterErrorType

RouterQueueStatusResponse

One queued request’s current state, composed with the same three URLs the submission returned. Composes RouterQueueUrls, RouterQueueStatusFields. Type: object

RouterQueueSubmitFields

The half of RouterQueueSubmitResponse that is not the URL block: the new request’s identity and its state at the instant it was admitted. Fields
RouterQueueRequestId
required
Identifier of one queued Router request - the handle a caller polls, cancels and collects a result by.Type: RouterQueueRequestId — pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$, uuid, Up to 36 characters
RouterQueueStatus
required
The state of a queued Router request. It has exactly three values, and unlike RouterErrorType this one is a closed enum, because the two schemas are closed in opposite directions on purpose. RouterErrorType classifies failures and its set is expected to grow, so a generated client that hard-rejected an unrecognised bucket would fail hardest exactly when something had already gone wrong. This one is a lifecycle, and a lifecycle with a fourth state added later is a breaking change to every polling loop written against it whether it is declared as an enum or not - so it is declared as one, and the constraint is stated where a client can see it.Type: RouterQueueStatus
RouterQueuePosition
How many requests are ahead of this one in the queue, at the instant the response was composed. Zero means this request is at the front.Type: RouterQueuePosition — At least 0

RouterQueueSubmitResponse

The handle returned when a run is admitted to the queue: the request’s identity and state, composed with the three URLs that address the rest of its lifetime. Composes RouterQueueUrls, RouterQueueSubmitFields. Type: object

RouterQueueUrls

The three URLs that address the rest of one queued request’s lifetime, returned on every response that carries a live handle so a client never composes a queue URL itself. Fields
string
required
Absolute URL of this request’s status read.URI
string
required
Absolute URL this request’s result is collected from.URI
string
required
Absolute URL a cancellation is asked for at.URI

RouterValidationErrorContext

Provider-supplied details about the validation rule that failed. Type: object

RouterValidationErrorDetail

One field-level validation failure. Fields
array of any
required
Path to the offending field, outermost segment first - for example ["body", "image_url"], or ["body", "images", 0] where an integer indexes into an array.
string
required
Human-readable description of this single failure.
string
required
Specific, machine-readable reason for this failure, passed through from the provider unchanged. This is the value a typed SDK exception hierarchy branches on; error_type on the response header is only its coarse bucket.
RouterValidationErrorContext
The violated bound for one RouterValidationErrorDetail, carried from the provider verbatim - for example {"limit_value": 8} alongside greater_than, {"min_width": 512} alongside image_too_small, or {"max_size_bytes": 10485760} alongside file_too_large. The key set is specific to the provider and the error type, so this is deliberately an open object: narrowing it to a fixed field list, or folding it into the msg string, is precisely how a ported integration compiles and then silently loses the branch that read the bound. Absent when the error type carries no bound.Type: RouterValidationErrorContext
RouterValidationErrorInput
The offending input value, echoed back verbatim so a caller can see what was rejected without re-deriving it from loc. Any JSON type - string, number, boolean, array, object or null - so this schema is deliberately left untyped rather than narrowed to an object. Absent when the provider does not echo the input back.Type: RouterValidationErrorInput

RouterValidationErrorInput

The rejected input value, when the provider includes it.

RouterValidationErrorResponse

The 422 validation error body. Read X-Comfy-Error-Type for its category. Fields
array of RouterValidationErrorDetail
required
Every validation failure found on the request, one entry per offending field.Type: array of RouterValidationErrorDetail