From 410810eea9b9e2d3d95a3cf2b856b289f25d15af Mon Sep 17 00:00:00 2001
From: mattmillerai <7741082+mattmillerai@users.noreply.github.com>
Date: Sun, 11 Oct 2026 09:00:07 +0000
Subject: [PATCH 1/3] chore: sync Comfy API v2 specification and Comfy Router
pages from cloud@ad24085
---
development/comfy-router/limitations.mdx | 4 +-
development/comfy-router/models.mdx | 11 +-
.../dreamina-seedance-2-0-260128/code.mdx | 1 +
.../models/google/nano-banana-2/code.mdx | 1 +
.../models/google/nano-banana-pro/code.mdx | 1 +
.../models/meshy/meshy-5/code.mdx | 4 +-
.../models/meshy/meshy-6/code.mdx | 4 +-
.../models/meshy/meshy-7-1/code.mdx | 4 +-
.../models/meshy/meshy-7/code.mdx | 4 +-
.../models/openai/gpt-image-1-5/code.mdx | 2 +-
.../models/openai/gpt-image-1/code.mdx | 2 +-
.../openai/gpt-image-2-5-flare/code.mdx | 2 +-
.../openai/gpt-image-2-5-sunburst/code.mdx | 2 +-
.../models/openai/gpt-image-2/code.mdx | 3 +-
.../pika/pika-2-5-image-to-video/code.mdx | 326 +++++
.../pika/pika-2-5-text-to-video/code.mdx | 317 ++++
.../veo/veo-3-1-fast-generate-001/code.mdx | 2 +-
.../models/veo/veo-3-1-generate-001/code.mdx | 2 +-
.../veo/veo-3-1-lite-generate-001/code.mdx | 2 +-
.../xai/grok-imagine-video-1-5-lite/code.mdx | 14 +-
.../grok-imagine-video-1-5-preview/code.mdx | 14 +-
.../xai/grok-imagine-video-1-5/code.mdx | 14 +-
.../models/xai/grok-imagine-video/code.mdx | 14 +-
development/comfy-router/pricing.mdx | 1278 ++---------------
development/comfy-router/providers.mdx | 24 +-
development/comfy-router/quickstart.mdx | 47 +
development/comfy-router/reference.mdx | 196 ++-
docs.json | 7 +
openapi-v2.yaml | 44 +-
router-openapi.yaml | 119 +-
.../dreamina-seedance-2-0-260128.json | 2 +-
router-schemas/meshy/meshy-5.json | 2 +-
router-schemas/meshy/meshy-6.json | 2 +-
router-schemas/meshy/meshy-7.1.json | 2 +-
router-schemas/meshy/meshy-7.json | 2 +-
router-schemas/openai/gpt-image-1.5.json | 2 +-
router-schemas/openai/gpt-image-1.json | 2 +-
.../openai/gpt-image-2.5-flare.json | 2 +-
.../openai/gpt-image-2.5-sunburst.json | 2 +-
router-schemas/openai/gpt-image-2.json | 2 +-
.../pika/pika-2.5-image-to-video.json | 1 +
.../pika/pika-2.5-text-to-video.json | 1 +
router-schemas/pika/pika-gpt-image-2.json | 1 +
router-schemas/pika/pika-nano-banana-2.json | 1 +
router-schemas/pika/pika-nano-banana-pro.json | 1 +
router-schemas/pika/pika-seedance-2.0.json | 1 +
.../veo/veo-3.1-fast-generate-001.json | 2 +-
router-schemas/veo/veo-3.1-generate-001.json | 2 +-
.../veo/veo-3.1-lite-generate-001.json | 2 +-
.../vertexai/gemini-2.5-flash-image.json | 2 +-
.../vertexai/gemini-3-pro-image.json | 2 +-
.../vertexai/gemini-3.1-flash-image.json | 2 +-
.../vertexai/gemini-3.1-flash-lite-image.json | 2 +-
.../vertexai/gemini-nano-banana-2.1.json | 2 +-
.../xai/grok-imagine-video-1.5-lite.json | 2 +-
.../xai/grok-imagine-video-1.5-preview.json | 2 +-
.../xai/grok-imagine-video-1.5.json | 2 +-
router-schemas/xai/grok-imagine-video.json | 2 +-
58 files changed, 1220 insertions(+), 1295 deletions(-)
create mode 100644 development/comfy-router/models/pika/pika-2-5-image-to-video/code.mdx
create mode 100644 development/comfy-router/models/pika/pika-2-5-text-to-video/code.mdx
create mode 100644 router-schemas/pika/pika-2.5-image-to-video.json
create mode 100644 router-schemas/pika/pika-2.5-text-to-video.json
create mode 100644 router-schemas/pika/pika-gpt-image-2.json
create mode 100644 router-schemas/pika/pika-nano-banana-2.json
create mode 100644 router-schemas/pika/pika-nano-banana-pro.json
create mode 100644 router-schemas/pika/pika-seedance-2.0.json
diff --git a/development/comfy-router/limitations.mdx b/development/comfy-router/limitations.mdx
index 60a6ac25c8..011c274921 100644
--- a/development/comfy-router/limitations.mdx
+++ b/development/comfy-router/limitations.mdx
@@ -79,7 +79,9 @@ Show an indeterminate progress indicator. If you need progress or streaming, use
The response can contain provider usage or cost fields. They do not represent a universal Comfy charge. `X-Comfy-Credits-Used` is optional: a missing header means the cost was not reported, not that the run was free. A replayed response (`Idempotent-Replayed: true`) is never charged again but may restate the original run's figure, so do not add the header up across retries of one `Idempotency-Key`. Use the Comfy platform for balances, usage, and invoices.
-The catalog provides billing facts, including `billing.charges_on_policy_rejection`, rather than prices. Handle `yes`, `no`, and `unknown` explicitly. See [billing](/development/comfy-router/billing#model-billing-facts).
+The catalog provides billing facts, including `billing.charges_on_policy_rejection`, but no prices. Handle `yes`, `no`, and `unknown` explicitly. See [billing](/development/comfy-router/billing#model-billing-facts).
+
+To see a price before a run, call `POST /v2/models/{provider}/{model}/estimate`, or read the `estimate` object on a queued submit's `201`. While the estimate is rolling out, the route answers `403` with `error_type: not_enabled` and the submit omits `estimate`. Each is priced from the rate card Comfy bills the run against, but it is not a price lock, a run on your own provider key is not billed by Comfy even when `/estimate` returns a figure for it, and an `unknown` estimate carries no figure; that does not mean free. `X-Comfy-Credits-Used` remains the post-hoc figure for a run. See [Pre-flight cost estimates](/development/comfy-router/reference#pre-flight-cost-estimates) for how to read `exact`, `estimated`, and `unknown`.
## Router does not cover every partner operation
diff --git a/development/comfy-router/models.mdx b/development/comfy-router/models.mdx
index 902d4c635d..94085898c7 100644
--- a/development/comfy-router/models.mdx
+++ b/development/comfy-router/models.mdx
@@ -13,7 +13,7 @@ Some of these models can also be served by an aggregator on the same route and t
## Find by output
-
+
- [SwitchX](/development/comfy-router/models/beeble/switchx/code): `beeble/switchx`
- [FLUX 3 Video](/development/comfy-router/models/black-forest-labs/flux-3-video/code): `bfl/flux-3-video`
@@ -52,6 +52,8 @@ Some of these models can also be served by an aggregator on the same route and t
- [Text To Video](/development/comfy-router/models/moonvalley/text-to-video/code): `moonvalley/text-to-video`
- [Video To Video Resize](/development/comfy-router/models/moonvalley/video-to-video-resize/code): `moonvalley/video-to-video-resize`
- [Video To Video](/development/comfy-router/models/moonvalley/video-to-video/code): `moonvalley/video-to-video`
+- [Pika 2.5 Image To Video](/development/comfy-router/models/pika/pika-2-5-image-to-video/code): `pika/pika-2.5-image-to-video`
+- [Pika 2.5 Text To Video](/development/comfy-router/models/pika/pika-2-5-text-to-video/code): `pika/pika-2.5-text-to-video`
- [P Video 2](/development/comfy-router/models/pruna/p-video-2/code): `pruna/p-video-2`
- [Aleph 2](/development/comfy-router/models/runway/aleph2/code): `runway/aleph2`
- [Gen 4 Turbo](/development/comfy-router/models/runway/gen4-turbo/code): `runway/gen4_turbo`
@@ -549,6 +551,13 @@ Some of these models can also be served by an aggregator on the same route and t
| --- | --- | --- | --- |
| [Chat Completions](/development/comfy-router/models/openrouter/chat-completions/code) | `openrouter/chat-completions` | Text, Image | Text |
+## Pika
+
+| Model | ID | Input | Output |
+| --- | --- | --- | --- |
+| [Pika 2.5 Image To Video](/development/comfy-router/models/pika/pika-2-5-image-to-video/code) | `pika/pika-2.5-image-to-video` | Text, Image | Video |
+| [Pika 2.5 Text To Video](/development/comfy-router/models/pika/pika-2-5-text-to-video/code) | `pika/pika-2.5-text-to-video` | Text | Video |
+
## Pruna
| Model | ID | Input | Output |
diff --git a/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code.mdx b/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code.mdx
index 3abf652ad2..a0b708bea3 100644
--- a/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code.mdx
+++ b/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code.mdx
@@ -174,6 +174,7 @@ This model is served by Comfy Router directly unless the request names another p
- **Comfy** (default): `POST https://api.comfy.org/v2/models/byteplus/dreamina-seedance-2-0-260128`
- **fal**, as `fal/fal-seedance-2.0`: `POST https://api.comfy.org/v2/models/byteplus/dreamina-seedance-2-0-260128?model_provider=fal`
- **Higgsfield**, as `higgsfield/higgsfield-seedance-2.0`: `POST https://api.comfy.org/v2/models/byteplus/dreamina-seedance-2-0-260128?model_provider=higgsfield`
+- **Pika**, as `pika/pika-seedance-2.0`: `POST https://api.comfy.org/v2/models/byteplus/dreamina-seedance-2-0-260128?model_provider=pika`
- **Runware**, as `runware/runware-seedance-2.0`: `POST https://api.comfy.org/v2/models/byteplus/dreamina-seedance-2-0-260128?model_provider=runware`
- **WaveSpeed**, as `wavespeed/wavespeed-seedance-2.0`: `POST https://api.comfy.org/v2/models/byteplus/dreamina-seedance-2-0-260128?model_provider=wavespeed`
diff --git a/development/comfy-router/models/google/nano-banana-2/code.mdx b/development/comfy-router/models/google/nano-banana-2/code.mdx
index 55d083b540..10bd4ddc4f 100644
--- a/development/comfy-router/models/google/nano-banana-2/code.mdx
+++ b/development/comfy-router/models/google/nano-banana-2/code.mdx
@@ -209,6 +209,7 @@ This model is served by Comfy Router directly unless the request names another p
- **Comfy** (default): `POST https://api.comfy.org/v2/models/vertexai/gemini-3.1-flash-image`
- **fal**, as `fal/fal-nano-banana-2`: `POST https://api.comfy.org/v2/models/vertexai/gemini-3.1-flash-image?model_provider=fal`
+- **Pika**, as `pika/pika-nano-banana-2`: `POST https://api.comfy.org/v2/models/vertexai/gemini-3.1-flash-image?model_provider=pika`
- **Runware**, as `runware/runware-nano-banana-2`: `POST https://api.comfy.org/v2/models/vertexai/gemini-3.1-flash-image?model_provider=runware`
- **WaveSpeed**, as `wavespeed/wavespeed-nano-banana-2`: `POST https://api.comfy.org/v2/models/vertexai/gemini-3.1-flash-image?model_provider=wavespeed`
diff --git a/development/comfy-router/models/google/nano-banana-pro/code.mdx b/development/comfy-router/models/google/nano-banana-pro/code.mdx
index 400523f204..de6f469685 100644
--- a/development/comfy-router/models/google/nano-banana-pro/code.mdx
+++ b/development/comfy-router/models/google/nano-banana-pro/code.mdx
@@ -209,6 +209,7 @@ This model is served by Comfy Router directly unless the request names another p
- **Comfy** (default): `POST https://api.comfy.org/v2/models/vertexai/gemini-3-pro-image`
- **fal**, as `fal/fal-nano-banana-pro`: `POST https://api.comfy.org/v2/models/vertexai/gemini-3-pro-image?model_provider=fal`
+- **Pika**, as `pika/pika-nano-banana-pro`: `POST https://api.comfy.org/v2/models/vertexai/gemini-3-pro-image?model_provider=pika`
- **Runware**, as `runware/runware-nano-banana-pro`: `POST https://api.comfy.org/v2/models/vertexai/gemini-3-pro-image?model_provider=runware`
- **WaveSpeed**, as `wavespeed/wavespeed-nano-banana-pro`: `POST https://api.comfy.org/v2/models/vertexai/gemini-3-pro-image?model_provider=wavespeed`
diff --git a/development/comfy-router/models/meshy/meshy-5/code.mdx b/development/comfy-router/models/meshy/meshy-5/code.mdx
index 0795a61bc4..c513aa00a1 100644
--- a/development/comfy-router/models/meshy/meshy-5/code.mdx
+++ b/development/comfy-router/models/meshy/meshy-5/code.mdx
@@ -160,7 +160,7 @@ curl https://api.comfy.org/v2/models/meshy/meshy-5/requests/$REQUEST_ID \
- Geometry resolution of the generated mesh. 2k and 4k run the Ultra pass for finer surface detail. 2k requires ai_model meshy-7, meshy-7.1 or latest; 4k requires meshy-7.1 or latest.
+ Geometry resolution of the generated mesh. 2k and 4k run the Ultra pass for finer surface detail. 2k and 4k require ai_model meshy-7.1 or latest.
Possible values: `standard`, `2k`, `4k`
@@ -212,7 +212,7 @@ curl https://api.comfy.org/v2/models/meshy/meshy-5/requests/$REQUEST_ID \
- Deprecated. Use geometry_resolution instead; true is equivalent to geometry_resolution 2k. Only supported when ai_model is meshy-7, meshy-7.1 or latest.
+ Deprecated. Use geometry_resolution instead; true is equivalent to geometry_resolution 2k. Only supported when ai_model is meshy-7, meshy-7.1 or latest. The exception is meshy-7: geometry_resolution 2k requires meshy-7.1 or latest, so on meshy-7 ultra_mode true is the way to request the Ultra pass.
#### `refine` variant
diff --git a/development/comfy-router/models/meshy/meshy-6/code.mdx b/development/comfy-router/models/meshy/meshy-6/code.mdx
index 5b85ecdb8e..32eb8ce391 100644
--- a/development/comfy-router/models/meshy/meshy-6/code.mdx
+++ b/development/comfy-router/models/meshy/meshy-6/code.mdx
@@ -160,7 +160,7 @@ curl https://api.comfy.org/v2/models/meshy/meshy-6/requests/$REQUEST_ID \
- Geometry resolution of the generated mesh. 2k and 4k run the Ultra pass for finer surface detail. 2k requires ai_model meshy-7, meshy-7.1 or latest; 4k requires meshy-7.1 or latest.
+ Geometry resolution of the generated mesh. 2k and 4k run the Ultra pass for finer surface detail. 2k and 4k require ai_model meshy-7.1 or latest.
Possible values: `standard`, `2k`, `4k`
@@ -212,7 +212,7 @@ curl https://api.comfy.org/v2/models/meshy/meshy-6/requests/$REQUEST_ID \
- Deprecated. Use geometry_resolution instead; true is equivalent to geometry_resolution 2k. Only supported when ai_model is meshy-7, meshy-7.1 or latest.
+ Deprecated. Use geometry_resolution instead; true is equivalent to geometry_resolution 2k. Only supported when ai_model is meshy-7, meshy-7.1 or latest. The exception is meshy-7: geometry_resolution 2k requires meshy-7.1 or latest, so on meshy-7 ultra_mode true is the way to request the Ultra pass.
#### `refine` variant
diff --git a/development/comfy-router/models/meshy/meshy-7-1/code.mdx b/development/comfy-router/models/meshy/meshy-7-1/code.mdx
index af0b89886e..179aaf92f9 100644
--- a/development/comfy-router/models/meshy/meshy-7-1/code.mdx
+++ b/development/comfy-router/models/meshy/meshy-7-1/code.mdx
@@ -160,7 +160,7 @@ curl https://api.comfy.org/v2/models/meshy/meshy-7.1/requests/$REQUEST_ID \
- Geometry resolution of the generated mesh. 2k and 4k run the Ultra pass for finer surface detail. 2k requires ai_model meshy-7, meshy-7.1 or latest; 4k requires meshy-7.1 or latest.
+ Geometry resolution of the generated mesh. 2k and 4k run the Ultra pass for finer surface detail. 2k and 4k require ai_model meshy-7.1 or latest.
Possible values: `standard`, `2k`, `4k`
@@ -212,7 +212,7 @@ curl https://api.comfy.org/v2/models/meshy/meshy-7.1/requests/$REQUEST_ID \
- Deprecated. Use geometry_resolution instead; true is equivalent to geometry_resolution 2k. Only supported when ai_model is meshy-7, meshy-7.1 or latest.
+ Deprecated. Use geometry_resolution instead; true is equivalent to geometry_resolution 2k. Only supported when ai_model is meshy-7, meshy-7.1 or latest. The exception is meshy-7: geometry_resolution 2k requires meshy-7.1 or latest, so on meshy-7 ultra_mode true is the way to request the Ultra pass.
#### `refine` variant
diff --git a/development/comfy-router/models/meshy/meshy-7/code.mdx b/development/comfy-router/models/meshy/meshy-7/code.mdx
index a35ca2a8a3..0e914a8073 100644
--- a/development/comfy-router/models/meshy/meshy-7/code.mdx
+++ b/development/comfy-router/models/meshy/meshy-7/code.mdx
@@ -160,7 +160,7 @@ curl https://api.comfy.org/v2/models/meshy/meshy-7/requests/$REQUEST_ID \
- Geometry resolution of the generated mesh. 2k and 4k run the Ultra pass for finer surface detail. 2k requires ai_model meshy-7, meshy-7.1 or latest; 4k requires meshy-7.1 or latest.
+ Geometry resolution of the generated mesh. 2k and 4k run the Ultra pass for finer surface detail. 2k and 4k require ai_model meshy-7.1 or latest.
Possible values: `standard`, `2k`, `4k`
@@ -212,7 +212,7 @@ curl https://api.comfy.org/v2/models/meshy/meshy-7/requests/$REQUEST_ID \
- Deprecated. Use geometry_resolution instead; true is equivalent to geometry_resolution 2k. Only supported when ai_model is meshy-7, meshy-7.1 or latest.
+ Deprecated. Use geometry_resolution instead; true is equivalent to geometry_resolution 2k. Only supported when ai_model is meshy-7, meshy-7.1 or latest. The exception is meshy-7: geometry_resolution 2k requires meshy-7.1 or latest, so on meshy-7 ultra_mode true is the way to request the Ultra pass.
#### `refine` variant
diff --git a/development/comfy-router/models/openai/gpt-image-1-5/code.mdx b/development/comfy-router/models/openai/gpt-image-1-5/code.mdx
index 6d4c56bd98..79f0d1febf 100644
--- a/development/comfy-router/models/openai/gpt-image-1-5/code.mdx
+++ b/development/comfy-router/models/openai/gpt-image-1-5/code.mdx
@@ -295,7 +295,7 @@ Generated from the schema Router serves at `GET /v2/models/openai/gpt-image-1.5/
- Unix timestamp, in seconds, of when the generation completed. It is the one member OpenAI declares REQUIRED on an image-generation response, so it is present whenever OpenAI is the producer; a fal-, wavespeed- or runware-served response on any of the alt-backed ids omits it entirely, like the other four resolved-parameter members. Declared `int64` because a present-day epoch value is close enough to 2^31 that an unformatted `integer` generates a 32-bit field in many SDK generators.
+ Unix timestamp, in seconds, of when the generation completed. It is the one member OpenAI declares REQUIRED on an image-generation response, so it is present whenever OpenAI is the producer; a fal-, pika-, wavespeed- or runware-served response on any of the alt-backed ids omits it entirely, like the other four resolved-parameter members. Declared `int64` because a present-day epoch value is close enough to 2^31 that an unformatted `integer` generates a 32-bit field in many SDK generators.
Format: `int64`
diff --git a/development/comfy-router/models/openai/gpt-image-1/code.mdx b/development/comfy-router/models/openai/gpt-image-1/code.mdx
index 4484ad2a71..5e84275e7b 100644
--- a/development/comfy-router/models/openai/gpt-image-1/code.mdx
+++ b/development/comfy-router/models/openai/gpt-image-1/code.mdx
@@ -295,7 +295,7 @@ Generated from the schema Router serves at `GET /v2/models/openai/gpt-image-1/op
- Unix timestamp, in seconds, of when the generation completed. It is the one member OpenAI declares REQUIRED on an image-generation response, so it is present whenever OpenAI is the producer; a fal-, wavespeed- or runware-served response on any of the alt-backed ids omits it entirely, like the other four resolved-parameter members. Declared `int64` because a present-day epoch value is close enough to 2^31 that an unformatted `integer` generates a 32-bit field in many SDK generators.
+ Unix timestamp, in seconds, of when the generation completed. It is the one member OpenAI declares REQUIRED on an image-generation response, so it is present whenever OpenAI is the producer; a fal-, pika-, wavespeed- or runware-served response on any of the alt-backed ids omits it entirely, like the other four resolved-parameter members. Declared `int64` because a present-day epoch value is close enough to 2^31 that an unformatted `integer` generates a 32-bit field in many SDK generators.
Format: `int64`
diff --git a/development/comfy-router/models/openai/gpt-image-2-5-flare/code.mdx b/development/comfy-router/models/openai/gpt-image-2-5-flare/code.mdx
index bf9ba58684..7ad88e4ce9 100644
--- a/development/comfy-router/models/openai/gpt-image-2-5-flare/code.mdx
+++ b/development/comfy-router/models/openai/gpt-image-2-5-flare/code.mdx
@@ -306,7 +306,7 @@ Generated from the schema Router serves at `GET /v2/models/openai/gpt-image-2.5-
- Unix timestamp, in seconds, of when the generation completed. It is the one member OpenAI declares REQUIRED on an image-generation response, so it is present whenever OpenAI is the producer; a fal-, wavespeed- or runware-served response on any of the alt-backed ids omits it entirely, like the other four resolved-parameter members. Declared `int64` because a present-day epoch value is close enough to 2^31 that an unformatted `integer` generates a 32-bit field in many SDK generators.
+ Unix timestamp, in seconds, of when the generation completed. It is the one member OpenAI declares REQUIRED on an image-generation response, so it is present whenever OpenAI is the producer; a fal-, pika-, wavespeed- or runware-served response on any of the alt-backed ids omits it entirely, like the other four resolved-parameter members. Declared `int64` because a present-day epoch value is close enough to 2^31 that an unformatted `integer` generates a 32-bit field in many SDK generators.
Format: `int64`
diff --git a/development/comfy-router/models/openai/gpt-image-2-5-sunburst/code.mdx b/development/comfy-router/models/openai/gpt-image-2-5-sunburst/code.mdx
index 6c72c41dfd..f7623a6b5b 100644
--- a/development/comfy-router/models/openai/gpt-image-2-5-sunburst/code.mdx
+++ b/development/comfy-router/models/openai/gpt-image-2-5-sunburst/code.mdx
@@ -306,7 +306,7 @@ Generated from the schema Router serves at `GET /v2/models/openai/gpt-image-2.5-
- Unix timestamp, in seconds, of when the generation completed. It is the one member OpenAI declares REQUIRED on an image-generation response, so it is present whenever OpenAI is the producer; a fal-, wavespeed- or runware-served response on any of the alt-backed ids omits it entirely, like the other four resolved-parameter members. Declared `int64` because a present-day epoch value is close enough to 2^31 that an unformatted `integer` generates a 32-bit field in many SDK generators.
+ Unix timestamp, in seconds, of when the generation completed. It is the one member OpenAI declares REQUIRED on an image-generation response, so it is present whenever OpenAI is the producer; a fal-, pika-, wavespeed- or runware-served response on any of the alt-backed ids omits it entirely, like the other four resolved-parameter members. Declared `int64` because a present-day epoch value is close enough to 2^31 that an unformatted `integer` generates a 32-bit field in many SDK generators.
Format: `int64`
diff --git a/development/comfy-router/models/openai/gpt-image-2/code.mdx b/development/comfy-router/models/openai/gpt-image-2/code.mdx
index f044d2d52a..a965b44602 100644
--- a/development/comfy-router/models/openai/gpt-image-2/code.mdx
+++ b/development/comfy-router/models/openai/gpt-image-2/code.mdx
@@ -149,6 +149,7 @@ This model is served by Comfy Router directly unless the request names another p
- **Comfy** (default): `POST https://api.comfy.org/v2/models/openai/gpt-image-2`
- **fal**, as `fal/fal-gpt-image-2`: `POST https://api.comfy.org/v2/models/openai/gpt-image-2?model_provider=fal`
+- **Pika**, as `pika/pika-gpt-image-2`: `POST https://api.comfy.org/v2/models/openai/gpt-image-2?model_provider=pika`
- **Runware**, as `runware/runware-gpt-image-2`: `POST https://api.comfy.org/v2/models/openai/gpt-image-2?model_provider=runware`
- **WaveSpeed**, as `wavespeed/wavespeed-gpt-image-2`: `POST https://api.comfy.org/v2/models/openai/gpt-image-2?model_provider=wavespeed`
@@ -306,7 +307,7 @@ Generated from the schema Router serves at `GET /v2/models/openai/gpt-image-2/op
- Unix timestamp, in seconds, of when the generation completed. It is the one member OpenAI declares REQUIRED on an image-generation response, so it is present whenever OpenAI is the producer; a fal-, wavespeed- or runware-served response on any of the alt-backed ids omits it entirely, like the other four resolved-parameter members. Declared `int64` because a present-day epoch value is close enough to 2^31 that an unformatted `integer` generates a 32-bit field in many SDK generators.
+ Unix timestamp, in seconds, of when the generation completed. It is the one member OpenAI declares REQUIRED on an image-generation response, so it is present whenever OpenAI is the producer; a fal-, pika-, wavespeed- or runware-served response on any of the alt-backed ids omits it entirely, like the other four resolved-parameter members. Declared `int64` because a present-day epoch value is close enough to 2^31 that an unformatted `integer` generates a 32-bit field in many SDK generators.
Format: `int64`
diff --git a/development/comfy-router/models/pika/pika-2-5-image-to-video/code.mdx b/development/comfy-router/models/pika/pika-2-5-image-to-video/code.mdx
new file mode 100644
index 0000000000..8c4be0eda4
--- /dev/null
+++ b/development/comfy-router/models/pika/pika-2-5-image-to-video/code.mdx
@@ -0,0 +1,326 @@
+---
+title: "Use Pika 2.5 Image To Video with Comfy Router"
+description: "Call pika/pika-2.5-image-to-video through Comfy Router: endpoint, request shape and the response Router returns."
+sidebarTitle: "Pika 2.5 Image To Video"
+---
+
+{/* GENERATED FILE. Generated from router-schemas/pika/pika-2.5-image-to-video.json by `pnpm code-pages:gen`. */}
+
+import RouterCodeFooter from "/snippets/comfy-router/model-code-footer.mdx";
+
+API Reference for `pika/pika-2.5-image-to-video`, served by Comfy Router from Pika.
+
+**Input:** Text, Image · **Output:** Video
+
+## Quick start
+
+Create a key in [your Comfy workspace](https://platform.comfy.org/profile/api-keys?onboarding=router) and export it as `COMFY_API_KEY`. The Python and TypeScript snippets use the Comfy SDKs (`pip install comfy-sdk` and `npm install @comfyorg/sdk`); the cURL snippet is the same call over raw HTTP.
+
+**Model ID:** `pika/pika-2.5-image-to-video`
+
+**Endpoint:** `POST https://api.comfy.org/v2/models/pika/pika-2.5-image-to-video`
+
+
+
+
+```python Python
+from comfy_sdk import Comfy
+
+# Reads COMFY_API_KEY from the environment.
+# The SDK automatically creates an idempotency key and reuses it for automatic retries.
+with Comfy() as client:
+ result = client.models.run(
+ "pika/pika-2.5-image-to-video",
+ {
+ "duration_s": 5,
+ "image": "https://example.invalid/pika/source.png",
+ "prompt": "The camera slowly pushes in as the leaves begin to sway",
+ "resolution": "720p",
+ },
+ )
+
+print(result)
+```
+
+```typescript TypeScript
+import { comfy } from "@comfyorg/sdk";
+
+// Reads COMFY_API_KEY from the environment.
+// The SDK automatically creates an idempotency key and reuses it for automatic retries.
+const { data } = await comfy.models.run("pika/pika-2.5-image-to-video", {
+ duration_s: 5,
+ image: "https://example.invalid/pika/source.png",
+ prompt: "The camera slowly pushes in as the leaves begin to sway",
+ resolution: "720p",
+});
+
+console.log(data);
+```
+
+```bash cURL
+curl https://api.comfy.org/v2/models/pika/pika-2.5-image-to-video \
+ -H "X-API-Key: $COMFY_API_KEY" \
+ -H "Idempotency-Key: $(uuidgen)" \
+ -H "Content-Type: application/json" \
+ -d "{\"duration_s\": 5, \"image\": \"https://example.invalid/pika/source.png\", \"prompt\": \"The camera slowly pushes in as the leaves begin to sway\", \"resolution\": \"720p\"}"
+```
+
+
+
+The same body, sent to `POST https://api.comfy.org/v2/models/pika/pika-2.5-image-to-video/requests`. Router answers `201` with a `request_id` as soon as the run is admitted, and the result is collected once it is ready, from this process or another one. [Queued delivery](/development/comfy-router/queue) walks through status, cancellation and collection.
+
+
+```python Python
+import asyncio
+from comfy_sdk import AsyncComfy
+
+# Reads COMFY_API_KEY from the environment.
+# Each submit() call mints its own Idempotency-Key and reuses it for automatic retries.
+async def main():
+ async with AsyncComfy() as client:
+ handle = await client.models.submit(
+ "pika/pika-2.5-image-to-video",
+ {
+ "duration_s": 5,
+ "image": "https://example.invalid/pika/source.png",
+ "prompt": "The camera slowly pushes in as the leaves begin to sway",
+ "resolution": "720p",
+ },
+ )
+ print("request_id:", handle.request_id) # with the model ID, all another process needs
+
+ # Poll until the request completes, waiting the Retry-After the server names.
+ async for update in handle.iter_events():
+ print(update.status, update.queue_position)
+
+ # The provider's own payload, the same value models.run() returns.
+ # A request that failed or was cancelled raises the typed Router error here.
+ result = await handle.get()
+
+ print(result)
+
+asyncio.run(main())
+```
+
+```typescript TypeScript
+import { comfy } from "@comfyorg/sdk";
+
+// Reads COMFY_API_KEY from the environment.
+// Each submit() call mints its own Idempotency-Key and reuses it for automatic retries.
+const handle = await comfy.models.submit("pika/pika-2.5-image-to-video", {
+ duration_s: 5,
+ image: "https://example.invalid/pika/source.png",
+ prompt: "The camera slowly pushes in as the leaves begin to sway",
+ resolution: "720p",
+});
+console.log("requestId:", handle.requestId); // with the model ID, all another process needs
+
+// Poll until the request completes, waiting the Retry-After the server names.
+for await (const update of handle.events()) {
+ console.log(update.status, update.queuePosition);
+}
+
+// The same result models.run() returns. A request that failed or was cancelled rejects here.
+const result = await handle.get();
+
+console.log(result.data);
+```
+
+```bash cURL
+# 1. Submit. Router answers 201 with request_id, status_url, response_url and cancel_url.
+curl https://api.comfy.org/v2/models/pika/pika-2.5-image-to-video/requests \
+ -H "X-API-Key: $COMFY_API_KEY" \
+ -H "Idempotency-Key: $(uuidgen)" \
+ -H "Content-Type: application/json" \
+ -d "{\"duration_s\": 5, \"image\": \"https://example.invalid/pika/source.png\", \"prompt\": \"The camera slowly pushes in as the leaves begin to sway\", \"resolution\": \"720p\"}"
+
+# 2. Poll until status is COMPLETED, waiting the Retry-After seconds each response names.
+REQUEST_ID=""
+curl -i https://api.comfy.org/v2/models/pika/pika-2.5-image-to-video/requests/$REQUEST_ID/status \
+ -H "X-API-Key: $COMFY_API_KEY"
+
+# 3. Collect. 200 with the model's native output, 202 with the status body while it is still running.
+curl https://api.comfy.org/v2/models/pika/pika-2.5-image-to-video/requests/$REQUEST_ID \
+ -H "X-API-Key: $COMFY_API_KEY"
+```
+
+
+
+
+## Schema
+
+### Input
+
+
+ Video length in seconds.
+
+ Possible values: `5`, `10`
+
+
+
+ Public https URL of the source image to animate. The image carries the subject. Plain `http://` is refused: Pika fetches the URL on a request sent under Comfy's key.
+
+
+
+ Text describing what to avoid in the video.
+
+
+
+ Text prompt guiding the motion.
+
+
+
+ Output resolution.
+
+ Possible values: `720p`, `1080p`
+
+
+
+ Seed for reproducible generation. Omit it (or send null) for a random seed.
+
+
+Generated from the schema Router serves at `GET /v2/models/pika/pika-2.5-image-to-video/openapi.json`, the same document it validates a call against before the request reaches the provider.
+
+### Output
+
+
+ Pika's structured failure record, set on a `failed` job. Router answers a failed job with its own error response, so a body Router returns does not carry it; it is declared because it is part of Pika's job document.
+
+
+
+ Possible values: `invalid_input`, `content_moderation`, `provider_error`, `provider_timeout`, `provider_unavailable`, `rate_limited`, `insufficient_balance`, `membership_required`, `cycle_limit_exceeded`, `admission_suspended`, `usage_limit_exceeded`, `timed_out`, `internal`
+
+
+
+
+
+
+
+ Pika's job id.
+
+
+
+ The Pika catalog entry the job ran.
+
+
+
+ The finished job's media, set when `status` is `completed`. `media_type` says which field carries it: `image` fills `images`, `video` fills `video`, and `videos` fills `videos`.
+
+
+
+ Generated images, for an image model.
+
+
+
+ MIME type of the file.
+
+
+
+ Height in pixels.
+
+
+
+ Download URL of the generated file.
+
+
+
+ Width in pixels.
+
+
+
+ Possible values: `image`, `video`, `videos`
+
+
+
+ The seed the video job actually used, when reported.
+
+
+
+ One generated media file.
+
+
+
+ MIME type of the file.
+
+
+
+ Height in pixels.
+
+
+
+ Download URL of the generated file.
+
+
+
+ Width in pixels.
+
+
+
+ Generated videos, for a multi-video delivery.
+
+
+
+ MIME type of the file.
+
+
+
+ Height in pixels.
+
+
+
+ Download URL of the generated file.
+
+
+
+ Width in pixels.
+
+
+
+ Seconds the generation took, from dispatch to terminal. Best-effort; may be null.
+
+
+
+ Lifecycle state. A body Router returns always reads `completed`; a `failed` job is answered with Router's error response instead.
+
+ Possible values: `queued`, `running`, `completed`, `failed`
+
+
+
+ Provider-reported units for the job. Keys are sparse; only the units the job reports are present.
+
+
+## Examples
+
+### Input
+
+```json
+{
+ "duration_s": 5,
+ "image": "https://example.invalid/pika/source.png",
+ "prompt": "The camera slowly pushes in as the leaves begin to sway",
+ "resolution": "720p"
+}
+```
+
+### Output
+
+```json
+{
+ "id": "2d8e4b71-9c05-4f3a-b6d2-81e7a5c09f44",
+ "model_id": "pika/pika-2.5/image-to-video",
+ "output": {
+ "media_type": "video",
+ "seed": 42,
+ "video": {
+ "content_type": "video/mp4",
+ "height": 720,
+ "url": "https://example.invalid/pika/pika-2.5-image-to-video/generated.mp4",
+ "width": 1280
+ }
+ },
+ "status": "completed"
+}
+```
+
+
+
diff --git a/development/comfy-router/models/pika/pika-2-5-text-to-video/code.mdx b/development/comfy-router/models/pika/pika-2-5-text-to-video/code.mdx
new file mode 100644
index 0000000000..b1e48bbcdf
--- /dev/null
+++ b/development/comfy-router/models/pika/pika-2-5-text-to-video/code.mdx
@@ -0,0 +1,317 @@
+---
+title: "Use Pika 2.5 Text To Video with Comfy Router"
+description: "Call pika/pika-2.5-text-to-video through Comfy Router: endpoint, request shape and the response Router returns."
+sidebarTitle: "Pika 2.5 Text To Video"
+---
+
+{/* GENERATED FILE. Generated from router-schemas/pika/pika-2.5-text-to-video.json by `pnpm code-pages:gen`. */}
+
+import RouterCodeFooter from "/snippets/comfy-router/model-code-footer.mdx";
+
+API Reference for `pika/pika-2.5-text-to-video`, served by Comfy Router from Pika.
+
+**Input:** Text · **Output:** Video
+
+## Quick start
+
+Create a key in [your Comfy workspace](https://platform.comfy.org/profile/api-keys?onboarding=router) and export it as `COMFY_API_KEY`. The Python and TypeScript snippets use the Comfy SDKs (`pip install comfy-sdk` and `npm install @comfyorg/sdk`); the cURL snippet is the same call over raw HTTP.
+
+**Model ID:** `pika/pika-2.5-text-to-video`
+
+**Endpoint:** `POST https://api.comfy.org/v2/models/pika/pika-2.5-text-to-video`
+
+
+
+
+```python Python
+from comfy_sdk import Comfy
+
+# Reads COMFY_API_KEY from the environment.
+# The SDK automatically creates an idempotency key and reuses it for automatic retries.
+with Comfy() as client:
+ result = client.models.run(
+ "pika/pika-2.5-text-to-video",
+ {
+ "duration_s": 5,
+ "prompt": "A paper boat drifting down a rain-soaked city street at dusk",
+ "resolution": "720p",
+ },
+ )
+
+print(result)
+```
+
+```typescript TypeScript
+import { comfy } from "@comfyorg/sdk";
+
+// Reads COMFY_API_KEY from the environment.
+// The SDK automatically creates an idempotency key and reuses it for automatic retries.
+const { data } = await comfy.models.run("pika/pika-2.5-text-to-video", {
+ duration_s: 5,
+ prompt: "A paper boat drifting down a rain-soaked city street at dusk",
+ resolution: "720p",
+});
+
+console.log(data);
+```
+
+```bash cURL
+curl https://api.comfy.org/v2/models/pika/pika-2.5-text-to-video \
+ -H "X-API-Key: $COMFY_API_KEY" \
+ -H "Idempotency-Key: $(uuidgen)" \
+ -H "Content-Type: application/json" \
+ -d "{\"duration_s\": 5, \"prompt\": \"A paper boat drifting down a rain-soaked city street at dusk\", \"resolution\": \"720p\"}"
+```
+
+
+
+The same body, sent to `POST https://api.comfy.org/v2/models/pika/pika-2.5-text-to-video/requests`. Router answers `201` with a `request_id` as soon as the run is admitted, and the result is collected once it is ready, from this process or another one. [Queued delivery](/development/comfy-router/queue) walks through status, cancellation and collection.
+
+
+```python Python
+import asyncio
+from comfy_sdk import AsyncComfy
+
+# Reads COMFY_API_KEY from the environment.
+# Each submit() call mints its own Idempotency-Key and reuses it for automatic retries.
+async def main():
+ async with AsyncComfy() as client:
+ handle = await client.models.submit(
+ "pika/pika-2.5-text-to-video",
+ {
+ "duration_s": 5,
+ "prompt": "A paper boat drifting down a rain-soaked city street at dusk",
+ "resolution": "720p",
+ },
+ )
+ print("request_id:", handle.request_id) # with the model ID, all another process needs
+
+ # Poll until the request completes, waiting the Retry-After the server names.
+ async for update in handle.iter_events():
+ print(update.status, update.queue_position)
+
+ # The provider's own payload, the same value models.run() returns.
+ # A request that failed or was cancelled raises the typed Router error here.
+ result = await handle.get()
+
+ print(result)
+
+asyncio.run(main())
+```
+
+```typescript TypeScript
+import { comfy } from "@comfyorg/sdk";
+
+// Reads COMFY_API_KEY from the environment.
+// Each submit() call mints its own Idempotency-Key and reuses it for automatic retries.
+const handle = await comfy.models.submit("pika/pika-2.5-text-to-video", {
+ duration_s: 5,
+ prompt: "A paper boat drifting down a rain-soaked city street at dusk",
+ resolution: "720p",
+});
+console.log("requestId:", handle.requestId); // with the model ID, all another process needs
+
+// Poll until the request completes, waiting the Retry-After the server names.
+for await (const update of handle.events()) {
+ console.log(update.status, update.queuePosition);
+}
+
+// The same result models.run() returns. A request that failed or was cancelled rejects here.
+const result = await handle.get();
+
+console.log(result.data);
+```
+
+```bash cURL
+# 1. Submit. Router answers 201 with request_id, status_url, response_url and cancel_url.
+curl https://api.comfy.org/v2/models/pika/pika-2.5-text-to-video/requests \
+ -H "X-API-Key: $COMFY_API_KEY" \
+ -H "Idempotency-Key: $(uuidgen)" \
+ -H "Content-Type: application/json" \
+ -d "{\"duration_s\": 5, \"prompt\": \"A paper boat drifting down a rain-soaked city street at dusk\", \"resolution\": \"720p\"}"
+
+# 2. Poll until status is COMPLETED, waiting the Retry-After seconds each response names.
+REQUEST_ID=""
+curl -i https://api.comfy.org/v2/models/pika/pika-2.5-text-to-video/requests/$REQUEST_ID/status \
+ -H "X-API-Key: $COMFY_API_KEY"
+
+# 3. Collect. 200 with the model's native output, 202 with the status body while it is still running.
+curl https://api.comfy.org/v2/models/pika/pika-2.5-text-to-video/requests/$REQUEST_ID \
+ -H "X-API-Key: $COMFY_API_KEY"
+```
+
+
+
+
+## Schema
+
+### Input
+
+
+ Video length in seconds. Text-to-video produces five-second clips only.
+
+ Possible values: `5`
+
+
+
+ Text describing what to avoid in the video.
+
+
+
+ Text prompt describing the video to generate.
+
+
+
+ Output resolution.
+
+ Possible values: `720p`, `1080p`
+
+
+
+ Seed for reproducible generation. Omit it (or send null) for a random seed.
+
+
+Generated from the schema Router serves at `GET /v2/models/pika/pika-2.5-text-to-video/openapi.json`, the same document it validates a call against before the request reaches the provider.
+
+### Output
+
+
+ Pika's structured failure record, set on a `failed` job. Router answers a failed job with its own error response, so a body Router returns does not carry it; it is declared because it is part of Pika's job document.
+
+
+
+ Possible values: `invalid_input`, `content_moderation`, `provider_error`, `provider_timeout`, `provider_unavailable`, `rate_limited`, `insufficient_balance`, `membership_required`, `cycle_limit_exceeded`, `admission_suspended`, `usage_limit_exceeded`, `timed_out`, `internal`
+
+
+
+
+
+
+
+ Pika's job id.
+
+
+
+ The Pika catalog entry the job ran.
+
+
+
+ The finished job's media, set when `status` is `completed`. `media_type` says which field carries it: `image` fills `images`, `video` fills `video`, and `videos` fills `videos`.
+
+
+
+ Generated images, for an image model.
+
+
+
+ MIME type of the file.
+
+
+
+ Height in pixels.
+
+
+
+ Download URL of the generated file.
+
+
+
+ Width in pixels.
+
+
+
+ Possible values: `image`, `video`, `videos`
+
+
+
+ The seed the video job actually used, when reported.
+
+
+
+ One generated media file.
+
+
+
+ MIME type of the file.
+
+
+
+ Height in pixels.
+
+
+
+ Download URL of the generated file.
+
+
+
+ Width in pixels.
+
+
+
+ Generated videos, for a multi-video delivery.
+
+
+
+ MIME type of the file.
+
+
+
+ Height in pixels.
+
+
+
+ Download URL of the generated file.
+
+
+
+ Width in pixels.
+
+
+
+ Seconds the generation took, from dispatch to terminal. Best-effort; may be null.
+
+
+
+ Lifecycle state. A body Router returns always reads `completed`; a `failed` job is answered with Router's error response instead.
+
+ Possible values: `queued`, `running`, `completed`, `failed`
+
+
+
+ Provider-reported units for the job. Keys are sparse; only the units the job reports are present.
+
+
+## Examples
+
+### Input
+
+```json
+{
+ "duration_s": 5,
+ "prompt": "A paper boat drifting down a rain-soaked city street at dusk",
+ "resolution": "720p"
+}
+```
+
+### Output
+
+```json
+{
+ "id": "6f1c2a9e-3b47-4d8a-9e21-7c5b0f4d8a13",
+ "model_id": "pika/pika-2.5/text-to-video",
+ "output": {
+ "media_type": "video",
+ "seed": 42,
+ "video": {
+ "content_type": "video/mp4",
+ "height": 720,
+ "url": "https://example.invalid/pika/pika-2.5-text-to-video/generated.mp4",
+ "width": 1280
+ }
+ },
+ "status": "completed"
+}
+```
+
+
+
diff --git a/development/comfy-router/models/veo/veo-3-1-fast-generate-001/code.mdx b/development/comfy-router/models/veo/veo-3-1-fast-generate-001/code.mdx
index bb209876bc..12e55c68f7 100644
--- a/development/comfy-router/models/veo/veo-3-1-fast-generate-001/code.mdx
+++ b/development/comfy-router/models/veo/veo-3-1-fast-generate-001/code.mdx
@@ -350,7 +350,7 @@ curl https://api.comfy.org/v2/models/veo/veo-3.1-fast-generate-001/requests/$REQ
- Cloud Pub/Sub topic for progress updates (projects/\{project}/topics/\{topic})
+ Cloud Pub/Sub topic for progress updates (projects/\{project}/topics/\{topic}). Not accepted on Comfy-credentialed (non-BYOK) requests: the generate route answers 400 if it is present, because the operation runs in Comfy's project and Vertex would publish as Comfy's service agent. Accepted only on bring-your-own-key requests, where Vertex evaluates the topic grant in your own project.
diff --git a/development/comfy-router/models/veo/veo-3-1-generate-001/code.mdx b/development/comfy-router/models/veo/veo-3-1-generate-001/code.mdx
index 425d67cb67..4222f161f7 100644
--- a/development/comfy-router/models/veo/veo-3-1-generate-001/code.mdx
+++ b/development/comfy-router/models/veo/veo-3-1-generate-001/code.mdx
@@ -350,7 +350,7 @@ curl https://api.comfy.org/v2/models/veo/veo-3.1-generate-001/requests/$REQUEST_
- Cloud Pub/Sub topic for progress updates (projects/\{project}/topics/\{topic})
+ Cloud Pub/Sub topic for progress updates (projects/\{project}/topics/\{topic}). Not accepted on Comfy-credentialed (non-BYOK) requests: the generate route answers 400 if it is present, because the operation runs in Comfy's project and Vertex would publish as Comfy's service agent. Accepted only on bring-your-own-key requests, where Vertex evaluates the topic grant in your own project.
diff --git a/development/comfy-router/models/veo/veo-3-1-lite-generate-001/code.mdx b/development/comfy-router/models/veo/veo-3-1-lite-generate-001/code.mdx
index 079ed0bc11..0d97ac9913 100644
--- a/development/comfy-router/models/veo/veo-3-1-lite-generate-001/code.mdx
+++ b/development/comfy-router/models/veo/veo-3-1-lite-generate-001/code.mdx
@@ -350,7 +350,7 @@ curl https://api.comfy.org/v2/models/veo/veo-3.1-lite-generate-001/requests/$REQ
- Cloud Pub/Sub topic for progress updates (projects/\{project}/topics/\{topic})
+ Cloud Pub/Sub topic for progress updates (projects/\{project}/topics/\{topic}). Not accepted on Comfy-credentialed (non-BYOK) requests: the generate route answers 400 if it is present, because the operation runs in Comfy's project and Vertex would publish as Comfy's service agent. Accepted only on bring-your-own-key requests, where Vertex evaluates the topic grant in your own project.
diff --git a/development/comfy-router/models/xai/grok-imagine-video-1-5-lite/code.mdx b/development/comfy-router/models/xai/grok-imagine-video-1-5-lite/code.mdx
index 68408281f9..9053133d1c 100644
--- a/development/comfy-router/models/xai/grok-imagine-video-1-5-lite/code.mdx
+++ b/development/comfy-router/models/xai/grok-imagine-video-1-5-lite/code.mdx
@@ -206,7 +206,7 @@ Generated from the schema Router serves at `GET /v2/models/xai/grok-imagine-vide
### Output
- If the request was blocked by input moderation, contains the block reason
+ Never carries a reason on this document: a non-empty block reason is an input-moderation refusal, which Router answers as a Comfy Router error rather than with this body. Absent, null or empty here.
@@ -214,9 +214,9 @@ Generated from the schema Router serves at `GET /v2/models/xai/grok-imagine-vide
- Status of the deferred request: "pending" or "done"
+ Absent or `done` on this document. `pending` is the body of a poll Router has not finished and is never returned as a `200`; Router accepts a missing status when `video.url` is populated, so a caller must not require it.
- Possible values: `pending`, `done`
+ Possible values: `done`
@@ -227,8 +227,8 @@ Generated from the schema Router serves at `GET /v2/models/xai/grok-imagine-vide
The cost of this request expressed in USD ticks. One USD cent equals 100,000,000 ticks, so one US dollar equals 10,000,000,000 ticks.
-
- A generated video from xAI
+
+ The finished generation. Required here, unlike on the poll document: a terminal result with no `video.url` is answered as a Comfy Router error, so a `200` always carries it.
@@ -239,8 +239,8 @@ Generated from the schema Router serves at `GET /v2/models/xai/grok-imagine-vide
Whether the video generated by the model respects moderation rules
-
- Download URL for the generated video. Router re-hosts the video onto Comfy storage and rewrites this field, so it is normally a Comfy-signed URL valid for up to 24 hours - signed for 24 hours when minted and replayed from a 23-hour memo, so a later poll can hand back one with as little as an hour left. When the re-host could not be performed the field keeps xAI's own short-lived URL instead. NULLABLE: a success whose `url` is empty is not a completed generation. Either way the link expires, so download the video rather than storing the URL.
+
+ Non-empty on this document. Router reports success only when this leaf is populated; it is the re-hosted Comfy-signed URL described above, or xAI's own short-lived URL when the re-host could not be performed.
## Examples
diff --git a/development/comfy-router/models/xai/grok-imagine-video-1-5-preview/code.mdx b/development/comfy-router/models/xai/grok-imagine-video-1-5-preview/code.mdx
index ba660f5f7c..34b512d019 100644
--- a/development/comfy-router/models/xai/grok-imagine-video-1-5-preview/code.mdx
+++ b/development/comfy-router/models/xai/grok-imagine-video-1-5-preview/code.mdx
@@ -206,7 +206,7 @@ Generated from the schema Router serves at `GET /v2/models/xai/grok-imagine-vide
### Output
- If the request was blocked by input moderation, contains the block reason
+ Never carries a reason on this document: a non-empty block reason is an input-moderation refusal, which Router answers as a Comfy Router error rather than with this body. Absent, null or empty here.
@@ -214,9 +214,9 @@ Generated from the schema Router serves at `GET /v2/models/xai/grok-imagine-vide
- Status of the deferred request: "pending" or "done"
+ Absent or `done` on this document. `pending` is the body of a poll Router has not finished and is never returned as a `200`; Router accepts a missing status when `video.url` is populated, so a caller must not require it.
- Possible values: `pending`, `done`
+ Possible values: `done`
@@ -227,8 +227,8 @@ Generated from the schema Router serves at `GET /v2/models/xai/grok-imagine-vide
The cost of this request expressed in USD ticks. One USD cent equals 100,000,000 ticks, so one US dollar equals 10,000,000,000 ticks.
-
- A generated video from xAI
+
+ The finished generation. Required here, unlike on the poll document: a terminal result with no `video.url` is answered as a Comfy Router error, so a `200` always carries it.
@@ -239,8 +239,8 @@ Generated from the schema Router serves at `GET /v2/models/xai/grok-imagine-vide
Whether the video generated by the model respects moderation rules
-
- Download URL for the generated video. Router re-hosts the video onto Comfy storage and rewrites this field, so it is normally a Comfy-signed URL valid for up to 24 hours - signed for 24 hours when minted and replayed from a 23-hour memo, so a later poll can hand back one with as little as an hour left. When the re-host could not be performed the field keeps xAI's own short-lived URL instead. NULLABLE: a success whose `url` is empty is not a completed generation. Either way the link expires, so download the video rather than storing the URL.
+
+ Non-empty on this document. Router reports success only when this leaf is populated; it is the re-hosted Comfy-signed URL described above, or xAI's own short-lived URL when the re-host could not be performed.
## Examples
diff --git a/development/comfy-router/models/xai/grok-imagine-video-1-5/code.mdx b/development/comfy-router/models/xai/grok-imagine-video-1-5/code.mdx
index 6e5c0a9854..b33839db21 100644
--- a/development/comfy-router/models/xai/grok-imagine-video-1-5/code.mdx
+++ b/development/comfy-router/models/xai/grok-imagine-video-1-5/code.mdx
@@ -206,7 +206,7 @@ Generated from the schema Router serves at `GET /v2/models/xai/grok-imagine-vide
### Output
- If the request was blocked by input moderation, contains the block reason
+ Never carries a reason on this document: a non-empty block reason is an input-moderation refusal, which Router answers as a Comfy Router error rather than with this body. Absent, null or empty here.
@@ -214,9 +214,9 @@ Generated from the schema Router serves at `GET /v2/models/xai/grok-imagine-vide
- Status of the deferred request: "pending" or "done"
+ Absent or `done` on this document. `pending` is the body of a poll Router has not finished and is never returned as a `200`; Router accepts a missing status when `video.url` is populated, so a caller must not require it.
- Possible values: `pending`, `done`
+ Possible values: `done`
@@ -227,8 +227,8 @@ Generated from the schema Router serves at `GET /v2/models/xai/grok-imagine-vide
The cost of this request expressed in USD ticks. One USD cent equals 100,000,000 ticks, so one US dollar equals 10,000,000,000 ticks.
-
- A generated video from xAI
+
+ The finished generation. Required here, unlike on the poll document: a terminal result with no `video.url` is answered as a Comfy Router error, so a `200` always carries it.
@@ -239,8 +239,8 @@ Generated from the schema Router serves at `GET /v2/models/xai/grok-imagine-vide
Whether the video generated by the model respects moderation rules
-
- Download URL for the generated video. Router re-hosts the video onto Comfy storage and rewrites this field, so it is normally a Comfy-signed URL valid for up to 24 hours - signed for 24 hours when minted and replayed from a 23-hour memo, so a later poll can hand back one with as little as an hour left. When the re-host could not be performed the field keeps xAI's own short-lived URL instead. NULLABLE: a success whose `url` is empty is not a completed generation. Either way the link expires, so download the video rather than storing the URL.
+
+ Non-empty on this document. Router reports success only when this leaf is populated; it is the re-hosted Comfy-signed URL described above, or xAI's own short-lived URL when the re-host could not be performed.
## Examples
diff --git a/development/comfy-router/models/xai/grok-imagine-video/code.mdx b/development/comfy-router/models/xai/grok-imagine-video/code.mdx
index 0b3de9b4de..791024d270 100644
--- a/development/comfy-router/models/xai/grok-imagine-video/code.mdx
+++ b/development/comfy-router/models/xai/grok-imagine-video/code.mdx
@@ -206,7 +206,7 @@ Generated from the schema Router serves at `GET /v2/models/xai/grok-imagine-vide
### Output
- If the request was blocked by input moderation, contains the block reason
+ Never carries a reason on this document: a non-empty block reason is an input-moderation refusal, which Router answers as a Comfy Router error rather than with this body. Absent, null or empty here.
@@ -214,9 +214,9 @@ Generated from the schema Router serves at `GET /v2/models/xai/grok-imagine-vide
- Status of the deferred request: "pending" or "done"
+ Absent or `done` on this document. `pending` is the body of a poll Router has not finished and is never returned as a `200`; Router accepts a missing status when `video.url` is populated, so a caller must not require it.
- Possible values: `pending`, `done`
+ Possible values: `done`
@@ -227,8 +227,8 @@ Generated from the schema Router serves at `GET /v2/models/xai/grok-imagine-vide
The cost of this request expressed in USD ticks. One USD cent equals 100,000,000 ticks, so one US dollar equals 10,000,000,000 ticks.
-
- A generated video from xAI
+
+ The finished generation. Required here, unlike on the poll document: a terminal result with no `video.url` is answered as a Comfy Router error, so a `200` always carries it.
@@ -239,8 +239,8 @@ Generated from the schema Router serves at `GET /v2/models/xai/grok-imagine-vide
Whether the video generated by the model respects moderation rules
-
- Download URL for the generated video. Router re-hosts the video onto Comfy storage and rewrites this field, so it is normally a Comfy-signed URL valid for up to 24 hours - signed for 24 hours when minted and replayed from a 23-hour memo, so a later poll can hand back one with as little as an hour left. When the re-host could not be performed the field keeps xAI's own short-lived URL instead. NULLABLE: a success whose `url` is empty is not a completed generation. Either way the link expires, so download the video rather than storing the URL.
+
+ Non-empty on this document. Router reports success only when this leaf is populated; it is the re-hosted Comfy-signed URL described above, or xAI's own short-lived URL when the re-host could not be performed.
## Examples
diff --git a/development/comfy-router/pricing.mdx b/development/comfy-router/pricing.mdx
index 858f829775..756bb57f80 100644
--- a/development/comfy-router/pricing.mdx
+++ b/development/comfy-router/pricing.mdx
@@ -1,1233 +1,141 @@
---
title: "Comfy Router pricing"
sidebarTitle: "Pricing"
-description: "Compare Comfy Router model prices in credits or USD by serving provider across image, video, text, audio, and 3D."
-mode: "wide"
+description: "What a Router generation costs, how credits convert to dollars, and how to compare the providers serving the same model."
---
-import { PricingCurrencyToggle } from "../../snippets/router-pricing-currency.jsx";
+Router is pay per use. Each generation draws credits from your workspace balance, with no subscription. What a generation costs depends on the model and on which provider serves it.
-{/* Generated pricing page. */}
+## Credits and dollars
-211 credits = $1 USD.
+Comfy prices in credits at a fixed rate:
-
-
-
+**$1.00 USD = 211 credits.** One credit is $0.004739.
-
+Buy credits at [workspace billing](https://platform.comfy.org). A workspace with no balance is refused with `402` and `insufficient_credits` before the request body is validated, so an unfunded workspace can answer `402` even for a malformed request. A call billed to [your own provider key](#bring-your-own-provider-key) does not draw credits and is not refused for an empty balance.
-
+## Read the price off the response
-#### Token rates
+A successful generation can carry an `X-Comfy-Credits-Used` response header giving what that run cost, in credits. It is priced from the same rate card the charge itself is billed against, and on a run that reached the provider through more than one billed call it is their sum.
-| Name | Model ID | Input credits / 1M tokens | Output credits / 1M tokens |
-| --- | --- | ---: | ---: |
-| [Gemini 2.5 Flash Image](/development/comfy-router/models/google/gemini-2-5-flash-image/code) | `vertexai/gemini-2.5-flash-image` | Image / Text / Video: 63.3
Audio: 211 | Text: 527.5
Image: 6330 |
-| [Gemini Nano Banana 2.1](/development/comfy-router/models/google/gemini-nano-banana-2-1/code) | `vertexai/gemini-nano-banana-2.1` | Image / Text / Video: 452.595 | Text / Reasoning: 2262.975
Image: 9051.9 |
-| [GPT Image 2.5 Flare](/development/comfy-router/models/openai/gpt-image-2-5-flare/code) | `openai/gpt-image-2.5-flare` | Text: 1508.65
Image: 2413.84 | Text: 3017.3
Image: 9051.9 |
-| [GPT Image 2.5 Sunburst](/development/comfy-router/models/openai/gpt-image-2-5-sunburst/code) | `openai/gpt-image-2.5-sunburst` | Text: 1508.65
Image: 2413.84 | Text: 3017.3
Image: 9051.9 |
-| [GPT Image 1.5](/development/comfy-router/models/openai/gpt-image-1-5/code) | `openai/gpt-image-1.5` | Text: 1055
Image: 1688 | Text: 2110
Image: 6752 |
-| [GPT Image 1](/development/comfy-router/models/openai/gpt-image-1/code) | `openai/gpt-image-1` | Text: 1055
Image: 2110 | Image: 8440 |
-| [Nano Banana 2](/development/comfy-router/models/google/nano-banana-2/code) | `vertexai/gemini-3.1-flash-image` | Image / Text / Video: 126.6 | Text / Reasoning: 759.6
Image: 15192 |
-| [Nano Banana 2 Lite](/development/comfy-router/models/google/nano-banana-2-lite/code) | `vertexai/gemini-3.1-flash-lite-image` | Image / Text / Video: 63.3 | Text / Reasoning: 379.8
Image: 7596 |
-| [Nano Banana Pro](/development/comfy-router/models/google/nano-banana-pro/code) | `vertexai/gemini-3-pro-image` | Image / Text: 506.4 | Text / Reasoning: 3038.4
Image: 30384 |
+```bash
+curl -sD - -o /dev/null \
+ -H "Authorization: Bearer $COMFY_API_KEY" \
+ -H "Content-Type: application/json" \
+ -d '{"contents":[{"role":"user","parts":[{"text":"a red bicycle"}]}]}' \
+ https://api.comfy.org/v2/models/vertexai/gemini-3.1-flash-image \
+ | grep -i x-comfy-credits-used
+```
-#### Usage-based rates
+Divide by 211 for dollars: a header reading `25.32` means that run cost $0.12. This model's default provider is metered, so that figure is one run's cost, not the model's price — the next request can read differently. The value is rounded to two decimals, so treat it as a figure to display and reconcile, not as an exact ledger amount.
-Comfy usage-based rates are billed per active GPU-second; total cost depends on runtime.
+
+**The header is not present on every response, and absent does not mean free.**
-| Name | Model ID | Credits |
-| --- | --- | ---: |
-| [AI Image Upscaler Precision V2](/development/comfy-router/models/freepik/ai-image-upscaler-precision-v2/code) | `freepik/ai-image-upscaler-precision-v2` | 0.266 / GPU-second |
-| [Erase V1](/development/comfy-router/models/black-forest-labs/erase-v1/code) | `bfl/erase-v1` | 0.266 / GPU-second |
-| [FLUX 2 Max](/development/comfy-router/models/black-forest-labs/flux-2-max/code) | `bfl/flux-2-max` | 0.266 / GPU-second |
-| [FLUX 2 Pro](/development/comfy-router/models/black-forest-labs/flux-2-pro/code) | `bfl/flux-2-pro` | 0.266 / GPU-second |
-| [FLUX 3 Image](/development/comfy-router/models/black-forest-labs/flux-3-image/code) | `bfl/flux-3-image` | 0.266 / GPU-second |
-| [GPT Image 2](/development/comfy-router/models/openai/gpt-image-2/code) | `openai/gpt-image-2` | 0.266 / GPU-second |
-| [Grok Imagine Image](/development/comfy-router/models/xai/grok-imagine-image/code) | `xai/grok-imagine-image` | 0.266 / GPU-second |
-| [Grok Imagine Image 2.0](/development/comfy-router/models/xai/grok-imagine-image-2-0/code) | `xai/grok-imagine-image-2.0` | 0.266 / GPU-second |
-| [Grok Imagine Image Pro](/development/comfy-router/models/xai/grok-imagine-image-pro/code) | `xai/grok-imagine-image-pro` | 0.266 / GPU-second |
-| [Grok Imagine Image Quality](/development/comfy-router/models/xai/grok-imagine-image-quality/code) | `xai/grok-imagine-image-quality` | 0.266 / GPU-second |
-| [VTO V1](/development/comfy-router/models/black-forest-labs/vto-v1/code) | `bfl/vto-v1` | 0.266 / GPU-second |
+- **Coverage is partial.** Not every model reports it yet, and coverage is widening. A missing header means "not reported", never "free" — branch on whether the header is present, not on whether its value is non-zero.
+- **It is absent** on a call billed to your own provider key, on a call whose cost could not be rated, and on every error response — including a charged content-policy refusal.
+- **It is a price, not a receipt.** On a model that submits and then polls, it is the price the charge will settle at, recorded before that charge settles. Reconcile against your workspace's usage and billing records rather than against the header alone.
+- **A rated run that genuinely cost nothing reads `0`** — a reported cost, not a missing one. Never sum absent headers as zero.
+
-#### Image generation and edit rates
+Within a single run the header is the **sum** of every billed call that run made. Across retries it is not: a replayed response — a same-key retry answered with `Idempotent-Replayed: true` — is not charged a second time, and when it carries the header it restates the original run's figure rather than a new charge. **Do not add the header up across retries of one key**, or a cost-tracking integration will overstate spend.
-| Name | Model ID | Credits / generation |
-| --- | --- | ---: |
-| [AI Skin Enhancer Creative](/development/comfy-router/models/freepik/ai-skin-enhancer-creative/code) | `freepik/ai-skin-enhancer-creative` | 61.19 |
-| [AI Skin Enhancer Faithful](/development/comfy-router/models/freepik/ai-skin-enhancer-faithful/code) | `freepik/ai-skin-enhancer-faithful` | 78.07 |
-| [AI Skin Enhancer Flexible](/development/comfy-router/models/freepik/ai-skin-enhancer-flexible/code) | `freepik/ai-skin-enhancer-flexible` | 94.95 |
-| [FLUX 1.1 Pro](/development/comfy-router/models/black-forest-labs/flux-1-1-pro-ultra-image/code) | `bfl/flux-pro-1.1` | 8.44 |
-| [FLUX 1.1 Pro Ultra](/development/comfy-router/models/black-forest-labs/flux-1-1-pro-ultra-image/code) | `bfl/flux-pro-1.1-ultra` | 12.66 |
-| [FLUX Pro 1.0 Expand](/development/comfy-router/models/black-forest-labs/flux-pro-1-0-expand/code) | `bfl/flux-pro-1.0-expand` | 10.55 |
-| [FLUX Pro 1.0 Fill](/development/comfy-router/models/black-forest-labs/flux-pro-1-0-fill/code) | `bfl/flux-pro-1.0-fill` | 10.55 |
-| [FLUX.1 Kontext Max](/development/comfy-router/models/black-forest-labs/flux-1-kontext/code) | `bfl/flux-kontext-max` | 16.88 |
-| [FLUX.1 Kontext Pro](/development/comfy-router/models/black-forest-labs/flux-1-kontext/code) | `bfl/flux-kontext-pro` | 8.44 |
-| [Gen 4 Image](/development/comfy-router/models/runway/gen4-image/code) | `runway/gen4_image` | 24.1384 |
-| [Photon 1](/development/comfy-router/models/luma/photon-1/code) | `luma/photon-1` | 2.2026 |
-| [Photon Flash 1](/development/comfy-router/models/luma/photon-flash-1/code) | `luma/photon-flash-1` | 0.5733 |
-| [Seedream 4.0](/development/comfy-router/models/byteplus/seedream-4-0-250828/code) | `byteplus/seedream-4-0-250828` | 6.33 |
-| [Seedream 4.5](/development/comfy-router/models/byteplus/seedream-4-5-251128/code) | `byteplus/seedream-4-5-251128` | 8.44 |
-| [Seedream 5.0](/development/comfy-router/models/byteplus/seedream-5-0-260128/code) | `byteplus/seedream-5-0-260128` | 7.385 |
-| [Seedream 5.0 Flash](/development/comfy-router/models/byteplus/seedream-5-0-flash-260915/code) | `byteplus/seedream-5-0-flash-260915` | 5.4311 |
-| [Fibo](/development/comfy-router/models/bria/fibo/code) | `bria/fibo` | 8.44 |
-| [Add Object By Text](/development/comfy-router/models/bria/image-edit-add-object-by-text/code) | `bria/image-edit-add-object-by-text` | 12.0692 |
-| [Erase](/development/comfy-router/models/bria/image-edit-erase/code) | `bria/image-edit-erase` | 6.0346 |
-| [Erase By Text](/development/comfy-router/models/bria/image-edit-erase-by-text/code) | `bria/image-edit-erase-by-text` | 12.0692 |
-| [Erase Foreground](/development/comfy-router/models/bria/image-edit-erase-foreground/code) | `bria/image-edit-erase-foreground` | 12.0692 |
-| [Expand](/development/comfy-router/models/bria/image-edit-expand/code) | `bria/image-edit-expand` | 6.0346 |
-| [Generative Fill](/development/comfy-router/models/bria/image-edit-gen-fill/code) | `bria/image-edit-gen-fill` | 9.0519 |
-| [Increase Resolution](/development/comfy-router/models/bria/image-edit-increase-resolution/code) | `bria/image-edit-increase-resolution` | 6.0346 |
-| [Relight](/development/comfy-router/models/bria/image-edit-relight/code) | `bria/image-edit-relight` | 12.0692 |
-| [Remove Background](/development/comfy-router/models/bria/image-edit-remove-background/code) | `bria/image-edit-remove-background` | 3.798 |
-| [Replace Background](/development/comfy-router/models/bria/image-edit-replace-background/code) | `bria/image-edit-replace-background` | 12.0692 |
-| [Replace Object By Text](/development/comfy-router/models/bria/image-edit-replace-object-by-text/code) | `bria/image-edit-replace-object-by-text` | 12.0692 |
-| [Reseason](/development/comfy-router/models/bria/image-edit-reseason/code) | `bria/image-edit-reseason` | 12.0692 |
-| [Restore](/development/comfy-router/models/bria/image-edit-restore/code) | `bria/image-edit-restore` | 12.0692 |
-| [Kling Image O1](/development/comfy-router/models/kling/kling-image-o1/code) | `kling/kling-image-o1` | 5.908 |
-| [Wan 2.5 I2I Preview](/development/comfy-router/models/wan/wan2-5-i2i-preview/code) | `wan/wan2.5-i2i-preview` | 6.33 |
-| [Wan 2.5 T2I Preview](/development/comfy-router/models/wan/wan2-5-t2i-preview/code) | `wan/wan2.5-t2i-preview` | 6.33 |
+
+Where a figure on this page and the header disagree, the header reflects the rate card the charge is billed against — treat this page as a guide to the shape of a model's pricing, not as the rate card.
+
-#### Not published
+## Two ways a model is priced
-| Name | Model ID | Credits |
-| --- | --- | ---: |
-| [Ideogram 4.5](/development/comfy-router/models/ideogram/ideogram-4-5/code) | `ideogram/ideogram-4-5` | Not published |
-| [P Image Ideogram](/development/comfy-router/models/ideogram/p-image-ideogram/code) | `ideogram/p-image-ideogram` | Not published |
-| [Text To Image](/development/comfy-router/models/moonvalley/text-to-image/code) | `moonvalley/text-to-image` | Not published |
+Models do not all price the same way, and the difference is what decides whether a single number can describe a model at all.
-#### Rates per input image
+| Shape | What you pay | How it behaves |
+| --- | --- | --- |
+| **Flat per generation** | One price per successful call, sometimes varying across a small set of named options such as resolution, mode, quality, style, or rendering speed. | Predictable. The same request served by the same provider always costs the same. |
+| **Metered per unit** | A rate applied to what the request actually consumed — image or text tokens, output resolution, video duration. | Varies per call, sometimes by more than an order of magnitude between a small cheap request and a large one. |
-| Name | Model ID | Option | Credits |
-| --- | --- | --- | ---: |
-| [Qwen Image 3.0](/development/comfy-router/models/qwen/qwen-image-3-0/code) | `qwen/qwen-image-3.0` | Input image · 1K / 2K | 0.9052 / input image |
-| [Qwen Image 3.0 Pro](/development/comfy-router/models/qwen/qwen-image-3-0-pro/code) | `qwen/qwen-image-3.0-pro` | Input image · 1K / 2K | 0.9052 / input image |
-| [Seedream 5.0 Pro](/development/comfy-router/models/byteplus/seedream-5-0-pro-260628/code) | `byteplus/seedream-5-0-pro-260628` | Input images after the first | 0.633 / input image |
+Which shape applies is a property of the model and the provider serving it, so the same model can be flat on one provider and metered on another. A flat shape is only predictable while the call stays on the provider you chose — see [provider fallback](#provider-fallback-can-change-the-price).
-#### Rates per Recraft credit
+Most token-priced models — the Gemini, GPT, and Claude families — are metered. Most dedicated image and video generators are flat, or flat within a named option.
-| Name | Model ID | Credits |
-| --- | --- | ---: |
-| [Recraft V2](/development/comfy-router/models/recraft/recraftv2/code) | `recraft/recraftv2` | 0.3017 / Recraft credit |
-| [Recraft V3](/development/comfy-router/models/recraft/recraftv3/code) | `recraft/recraftv3` | 0.3017 / Recraft credit |
-| [Recraft V4](/development/comfy-router/models/recraft/recraftv4/code) | `recraft/recraftv4` | 0.3017 / Recraft credit |
-| [Recraft V4 Pro](/development/comfy-router/models/recraft/recraftv4-pro/code) | `recraft/recraftv4_pro` | 0.3017 / Recraft credit |
-| [Recraft V4 Styles](/development/comfy-router/models/recraft/recraftv4-styles/code) | `recraft/recraftv4_styles` | 0.3017 / Recraft credit |
-| [Recraft V4 Styles Pro](/development/comfy-router/models/recraft/recraftv4-styles-pro/code) | `recraft/recraftv4_styles_pro` | 0.3017 / Recraft credit |
-| [Recraft V4 Styles Pro Vector](/development/comfy-router/models/recraft/recraftv4-styles-pro-vector/code) | `recraft/recraftv4_styles_pro_vector` | 0.3017 / Recraft credit |
-| [Recraft V4 Styles Vector](/development/comfy-router/models/recraft/recraftv4-styles-vector/code) | `recraft/recraftv4_styles_vector` | 0.3017 / Recraft credit |
-| [Recraft V4.1](/development/comfy-router/models/recraft/recraftv4-1/code) | `recraft/recraftv4_1` | 0.3017 / Recraft credit |
-| [Recraft V4.1 Pro](/development/comfy-router/models/recraft/recraftv4-1-pro/code) | `recraft/recraftv4_1_pro` | 0.3017 / Recraft credit |
-| [Recraft V4.1 Pro Vector](/development/comfy-router/models/recraft/recraftv4-1-pro-vector/code) | `recraft/recraftv4_1_pro_vector` | 0.3017 / Recraft credit |
-| [Recraft V4.1 Utility](/development/comfy-router/models/recraft/recraftv4-1-utility/code) | `recraft/recraftv4_1_utility` | 0.3017 / Recraft credit |
-| [Recraft V4.1 Utility Pro](/development/comfy-router/models/recraft/recraftv4-1-utility-pro/code) | `recraft/recraftv4_1_utility_pro` | 0.3017 / Recraft credit |
-| [Recraft V4.1 Utility Pro Vector](/development/comfy-router/models/recraft/recraftv4-1-utility-pro-vector/code) | `recraft/recraftv4_1_utility_pro_vector` | 0.3017 / Recraft credit |
-| [Recraft V4.1 Utility Vector](/development/comfy-router/models/recraft/recraftv4-1-utility-vector/code) | `recraft/recraftv4_1_utility_vector` | 0.3017 / Recraft credit |
-| [Recraft V4.1 Vector](/development/comfy-router/models/recraft/recraftv4-1-vector/code) | `recraft/recraftv4_1_vector` | 0.3017 / Recraft credit |
+## Comparing providers for the same model
-#### Rates per reference image
+Some models are served by more than one provider. The model is the same; the price and the pricing shape are not. Where a model has alternates, they are listed on its page and on [Providers](/development/comfy-router/providers).
-| Name | Model ID | Credits |
-| --- | --- | ---: |
-| [Uni 1](/development/comfy-router/models/luma_2/uni-1/code) | `luma_2/uni-1` | 0.633 / reference image |
-| [Uni 1 Max](/development/comfy-router/models/luma_2/uni-1-max/code) | `luma_2/uni-1-max` | 0.633 / reference image |
+Two image models where the difference is large and worth knowing:
-#### Krea 2 generation rates (credits / generation)
+### `vertexai/gemini-3-pro-image`
-| Name | Model ID | Moodboards Credits | Style references Credits | Text only Credits |
-| --- | --- | ---: | ---: | ---: |
-| [Krea 2](/development/comfy-router/models/krea/krea-2/code) | `krea/krea-2` | 8.44 | 7.385 | 6.33 |
-| [Krea 2 Large](/development/comfy-router/models/krea/krea-2-large/code) | `krea/krea-2-large` | 14.77 | 13.715 | 12.66 |
-| [Krea 2 Medium](/development/comfy-router/models/krea/krea-2-medium/code) | `krea/krea-2-medium` | 8.44 | 7.385 | 6.33 |
-| [Krea 2 Medium Turbo](/development/comfy-router/models/krea/krea-2-medium-turbo/code) | `krea/krea-2-medium-turbo` | 4.22 | 3.6925 | 3.165 |
+| Provider | Call it as | Pricing |
+| --- | --- | --- |
+| Vertex AI (default) | `vertexai/gemini-3-pro-image` | Metered — per token, across input text, image, audio and video plus output text, image and reasoning. Varies per request. |
+| fal | `fal/fal-nano-banana-pro` | **$0.15** flat per generation |
+| Wavespeed | `wavespeed/wavespeed-nano-banana-pro` | **$0.14** per generation at 1K and 2K, **$0.24** at 4K |
+| Runware | `runware/runware-nano-banana-pro` | Metered — varies with what the request consumed |
-#### Qwen Image 3.0 output image rates
+### `vertexai/gemini-3.1-flash-image`
-| Name | Model ID | 1K Credits / generation | 2K Credits / generation |
-| --- | --- | ---: | ---: |
-| [Qwen Image 3.0](/development/comfy-router/models/qwen/qwen-image-3-0/code) | `qwen/qwen-image-3.0` | 9.0519 | 9.0519 |
-| [Qwen Image 3.0 Pro](/development/comfy-router/models/qwen/qwen-image-3-0-pro/code) | `qwen/qwen-image-3.0-pro` | 12.0692 | 22.6297 |
+| Provider | Call it as | Pricing |
+| --- | --- | --- |
+| Vertex AI (default) | `vertexai/gemini-3.1-flash-image` | Metered — per token, on the same basis as above. Varies per request. |
+| fal | `fal/fal-nano-banana-2` | **$0.08** flat per generation |
+| Wavespeed | `wavespeed/wavespeed-nano-banana-2` | **$0.07** flat per generation |
+| Runware | `runware/runware-nano-banana-2` | Metered — varies with what the request consumed |
-#### Seedream 5.0 Pro output image rates
+The default provider for both is metered, and the alternates differ in how predictable they are: fal is a single flat price, Wavespeed is flat within a resolution band, and Runware varies per request. If you need a per-image price you can forecast, fal and Wavespeed give you one. If your requests are small, a metered provider can still come out lower — the only way to know for your own traffic is to measure it with the header.
-| Name | Model ID | Layer separation · Standard Credits / generation | Layer separation · Large Credits / generation | Output image · Standard Credits / generation | Output image · Large Credits / generation |
-| --- | --- | ---: | ---: | ---: | ---: |
-| [Seedream 5.0 Pro](/development/comfy-router/models/byteplus/seedream-5-0-pro-260628/code) | `byteplus/seedream-5-0-pro-260628` | 6.7889 | 13.5778 | 9.495 | 18.99 |
+### `openai/gpt-image-2` on Wavespeed
-#### Image generation and then image edit
+Not every alternate is a single number. Wavespeed prices the GPT image models as a grid of quality against output resolution — flat within each cell, so it is predictable once you know which cell your request lands in:
-| Name | Model ID | Image generation Credits / generation | Image edit Credits / generation |
-| --- | --- | ---: | ---: |
-| [Uni 1](/development/comfy-router/models/luma_2/uni-1/code) | `luma_2/uni-1` | 8.5244 | 9.1574 |
-| [Uni 1 Max](/development/comfy-router/models/luma_2/uni-1-max/code) | `luma_2/uni-1-max` | 21.1 | 21.733 |
+**Text to image** (`wavespeed/wavespeed-gpt-image-2`)
-#### Ideogram image rates
+| Quality | 1K | 2K | 4K |
+| --- | --- | --- | --- |
+| `low` | $0.01 | $0.02 | $0.03 |
+| `medium` | $0.06 | $0.10 | $0.18 |
+| `high` | $0.22 | $0.40 | $0.72 |
-| Name | Model ID | Standard Credits / generation | Quality Credits / generation | Turbo Credits / generation |
-| --- | --- | ---: | ---: | ---: |
-| [Ideogram 4.0](/development/comfy-router/models/ideogram/ideogram-v4/code) | `ideogram/ideogram-v4` | 18.1038 | 30.173 | 9.0519 |
-| [Ideogram V3](/development/comfy-router/models/ideogram/ideogram-v3/code) | `ideogram/ideogram-v3` | 18.1038 | 27.1557 | 9.0519 |
-
+**Edit**
-
+| Quality | 1K | 2K | 4K |
+| --- | --- | --- | --- |
+| `low` | $0.02 | $0.03 | $0.04 |
+| `medium` | $0.07 | $0.11 | $0.19 |
+| `high` | $0.23 | $0.41 | $0.73 |
-#### Not published
+OpenAI's own leg for the same model is token-metered instead, so the two are not comparable cell by cell — pick the row and column your traffic actually uses and compare that against a measured run.
-| Name | Model ID | Credits |
-| --- | --- | ---: |
-| [Crystal Upscaler](/development/comfy-router/models/fal/crystal-upscaler/code) | `fal/crystal-upscaler` | Not published |
+The `gpt-image-2.5` models are priced on the same grid shape with two further quality steps (`xhigh` and `max`). Seedance, Kling and Wan video are metered or multi-tier on every provider that serves them. For all of these, compare measured runs on a request shaped like your real traffic rather than against a single published figure — with `X-Comfy-Credits-Used` where the model reports it, and against your workspace's usage records where it does not.
-#### Usage-based rates
+## Choosing a provider on a request
-| Name | Model ID | Credits |
-| --- | --- | ---: |
-| [GPT Image 2](/development/comfy-router/models/openai/gpt-image-2/code) | `openai/gpt-image-2` | Usage-based
request usage |
-| [GPT Image 2.5 Flare](/development/comfy-router/models/openai/gpt-image-2-5-flare/code) | `openai/gpt-image-2.5-flare` | Usage-based
request usage |
-| [GPT Image 2.5 Sunburst](/development/comfy-router/models/openai/gpt-image-2-5-sunburst/code) | `openai/gpt-image-2.5-sunburst` | Usage-based
request usage |
+Send the model's own id and Router uses its default provider. To run the same model on an alternate, either call the alternate's own id directly, or keep the model id and add `model_provider`:
-#### Image generation and edit rates
+```bash
+curl -X POST \
+ -H "Authorization: Bearer $COMFY_API_KEY" \
+ -H "Content-Type: application/json" \
+ -d '{"contents":[{"role":"user","parts":[{"text":"a red bicycle"}]}]}' \
+ "https://api.comfy.org/v2/models/vertexai/gemini-3-pro-image?model_provider=wavespeed&fallback_provider=false"
+```
-| Name | Model ID | Credits / generation |
-| --- | --- | ---: |
-| [Nano Banana 2](/development/comfy-router/models/google/nano-banana-2/code) | `vertexai/gemini-3.1-flash-image` | 16.88 |
-| [Nano Banana Pro](/development/comfy-router/models/google/nano-banana-pro/code) | `vertexai/gemini-3-pro-image` | 31.65 |
-| [Patina](/development/comfy-router/models/fal/patina/code) | `fal/patina` | 3.0173 |
-
+With `model_provider`, Router translates the response back into the model's native shape where it can. Translation is best-effort: if it cannot be applied, the successful response is returned in the alternate provider's own shape rather than withheld. Calling an alternate's own id directly uses that id's own request and response contract. See [Providers](/development/comfy-router/providers) for the full list and for `strict_mode`.
-
+### Provider fallback can change the price
-| Name | Model ID | Credits |
-| --- | --- | ---: |
-| [GPT Image 2](/development/comfy-router/models/openai/gpt-image-2/code) | `openai/gpt-image-2` | Usage-based
request usage |
-| [GPT Image 2.5 Flare](/development/comfy-router/models/openai/gpt-image-2-5-flare/code) | `openai/gpt-image-2.5-flare` | Usage-based
request usage |
-| [GPT Image 2.5 Sunburst](/development/comfy-router/models/openai/gpt-image-2-5-sunburst/code) | `openai/gpt-image-2.5-sunburst` | Usage-based
request usage |
-| [Nano Banana 2](/development/comfy-router/models/google/nano-banana-2/code) | `vertexai/gemini-3.1-flash-image` | Usage-based
request usage |
-| [Nano Banana Pro](/development/comfy-router/models/google/nano-banana-pro/code) | `vertexai/gemini-3-pro-image` | Usage-based
request usage |
-
+Automatic provider fallback is **on by default**: if the first provider fails for a reason that is not the request's own, Router can retry once on another of the model's providers, and that provider's price applies. The response then carries `X-Comfy-Router-Fallback-Provider`, naming the provider that served it. When you pick a provider for its price, add `fallback_provider=false`, as above, so a failure is returned to you rather than served — and charged — elsewhere.
-
+## What you are charged for
-#### Image generation and edit rates
+| Outcome | Charged |
+| --- | --- |
+| Successful generation | Yes |
+| Replayed response for a reused `Idempotency-Key` | Not again — a replay never adds a charge to whatever the original call was charged |
+| Refused by Router before dispatch — `X-Comfy-Error-Type` of `invalid_input`, `insufficient_credits`, `rate_limited`, `concurrency_limit_exceeded`, `unauthorized`, `forbidden` or `not_enabled` | No |
+| Provider refused the prompt on content policy (`content_policy_violation`) | Sometimes — it depends on the model. The error response carries no `X-Comfy-Credits-Used`, so check your workspace's usage records. |
+| Other provider-side failures (`provider_error`, `provider_timeout`, `model_not_found`) | Can be — the request may have reached the provider |
+| Timeout or lost connection | Not determined by the disconnect. Accepted provider work is not cancelled, and may be charged. See [timeouts and collection](/development/comfy-router/errors#timeouts-and-collection). |
-| Name | Model ID | Text to image Credits / generation | Image edit Credits / generation | Image edit · 1K / 2K Credits / generation | Image edit · 4K Credits / generation |
-| --- | --- | ---: | ---: | ---: | ---: |
-| [GPT Image 2](/development/comfy-router/models/openai/gpt-image-2/code) | `openai/gpt-image-2` | 12.66 | 14.77 | - | - |
-| [GPT Image 2.5 Flare](/development/comfy-router/models/openai/gpt-image-2-5-flare/code) | `openai/gpt-image-2.5-flare` | 5.064 | 8.229 | - | - |
-| [GPT Image 2.5 Sunburst](/development/comfy-router/models/openai/gpt-image-2-5-sunburst/code) | `openai/gpt-image-2.5-sunburst` | 5.064 | 8.229 | - | - |
-| [Nano Banana 2](/development/comfy-router/models/google/nano-banana-2/code) | `vertexai/gemini-3.1-flash-image` | 14.77 | 14.77 | - | - |
-| [Nano Banana Pro](/development/comfy-router/models/google/nano-banana-pro/code) | `vertexai/gemini-3-pro-image` | 29.54 | - | 29.54 | 50.64 |
+## Bring your own provider key
-| Name | Model ID | Credits / generation |
-| --- | --- | ---: |
-| [Seedvr 2](/development/comfy-router/models/wavespeed/seedvr2/code) | `wavespeed/seedvr2` | 2.11 |
-| [Ultimate Image Upscaler](/development/comfy-router/models/wavespeed/ultimate-image-upscaler/code) | `wavespeed/ultimate-image-upscaler` | 12.66 |
-
-#### Image quality and resolution
-
-| Name | Model ID | Operation | Quality | 1K Credits / generation | 2K Credits / generation | 4K Credits / generation |
-| --- | --- | --- | --- | ---: | ---: | ---: |
-| [GPT Image 2](/development/comfy-router/models/openai/gpt-image-2/code) | `openai/gpt-image-2` | Text to image | Low | 2.11 | 4.22 | 6.33 |
-| | | | Medium | 12.66 | 21.1 | 37.98 |
-| | | | High | 46.42 | 84.4 | 151.92 |
-| | | Image edit | Low | 4.22 | 6.33 | 8.44 |
-| | | | Medium | 14.77 | 23.21 | 40.09 |
-| | | | High | 48.53 | 86.51 | 154.03 |
-| [GPT Image 2.5 Flare](/development/comfy-router/models/openai/gpt-image-2-5-flare/code) | `openai/gpt-image-2.5-flare` | Text to image | Low | 2.11 | 4.22 | 6.33 |
-| | | | Medium | 5.064 | 8.44 | 14.77 |
-| | | | High | 18.99 | 31.65 | 56.97 |
-| | | | Extra high | 33.76 | 56.97 | 101.28 |
-| | | | Max | 75.96 | 126.6 | 211 |
-| | | Image edit | Low | 5.275 | 7.385 | 9.495 |
-| | | | Medium | 8.229 | 11.605 | 17.935 |
-| | | | High | 22.155 | 34.815 | 60.135 |
-| | | | Extra high | 36.925 | 60.135 | 104.445 |
-| | | | Max | 79.125 | 129.765 | 214.165 |
-| [GPT Image 2.5 Sunburst](/development/comfy-router/models/openai/gpt-image-2-5-sunburst/code) | `openai/gpt-image-2.5-sunburst` | Text to image | Low | 2.11 | 4.22 | 6.33 |
-| | | | Medium | 5.064 | 8.44 | 14.77 |
-| | | | High | 18.99 | 31.65 | 56.97 |
-| | | | Extra high | 33.76 | 56.97 | 101.28 |
-| | | | Max | 75.96 | 126.6 | 211 |
-| | | Image edit | Low | 5.275 | 7.385 | 9.495 |
-| | | | Medium | 8.229 | 11.605 | 17.935 |
-| | | | High | 22.155 | 34.815 | 60.135 |
-| | | | Extra high | 36.925 | 60.135 | 104.445 |
-| | | | Max | 79.125 | 129.765 | 214.165 |
-
-
-
-
-
-
-
-
-
-
-
-
-Video rates apply to output plus reference-video duration.
-
-#### Per 1M tokens by resolution
-
-| Name | Model ID | Input type | 480p Credits | 720p Credits | 1080p Credits | 4K Credits |
-| --- | --- | --- | ---: | ---: | ---: | ---: |
-| [Dreamina Seedance 2.0](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | `byteplus/dreamina-seedance-2-0-260128` | Image to video | 2112.11 / 1M tokens | 2112.11 / 1M tokens | 2323.321 / 1M tokens | 1206.92 / 1M tokens |
-| | | Text to video | 2112.11 / 1M tokens | 2112.11 / 1M tokens | 2323.321 / 1M tokens | 1206.92 / 1M tokens |
-| | | Video to video | 1297.439 / 1M tokens | 1297.439 / 1M tokens | 1418.131 / 1M tokens | 724.152 / 1M tokens |
-| [Dreamina Seedance 2.5](/development/comfy-router/models/byteplus/dreamina-seedance-2-5-260628/code) | `byteplus/dreamina-seedance-2-5-260628` | Image to video | 3228.511 / 1M tokens | 3228.511 / 1M tokens | 3530.241 / 1M tokens | - |
-| | | Text to video | 3228.511 / 1M tokens | 3228.511 / 1M tokens | 3530.241 / 1M tokens | - |
-| | | Video to video | 1931.072 / 1M tokens | 1931.072 / 1M tokens | 2112.11 / 1M tokens | - |
-
-#### Per second by resolution
-
-| Name | Model ID | 480p Credits | 720p Credits | 1080p Credits | 1440p Credits | 4K Credits |
-| --- | --- | ---: | ---: | ---: | ---: | ---: |
-| [HappyHorse 1.0 I2V](/development/comfy-router/models/wan/happyhorse-1-0-i2v/code) | `wan/happyhorse-1.0-i2v` | - | 29.54 / s | 50.64 / s | - | - |
-| [HappyHorse 1.0 R2V](/development/comfy-router/models/wan/happyhorse-1-0-r2v/code) | `wan/happyhorse-1.0-r2v` | - | 29.54 / s | 50.64 / s | - | - |
-| [HappyHorse 1.0 T2V](/development/comfy-router/models/wan/happyhorse-1-0-t2v/code) | `wan/happyhorse-1.0-t2v` | - | 29.54 / s | 50.64 / s | - | - |
-| [HappyHorse 1.0 Video Edit](/development/comfy-router/models/wan/happyhorse-1-0-video-edit/code) | `wan/happyhorse-1.0-video-edit` | - | 29.54 / s | 50.64 / s | - | - |
-| [HappyHorse 1.1 I2V](/development/comfy-router/models/wan/happyhorse-1-1-i2v/code) | `wan/happyhorse-1.1-i2v` | - | 42.2422 / s | 54.3114 / s | - | - |
-| [HappyHorse 1.1 R2V](/development/comfy-router/models/wan/happyhorse-1-1-r2v/code) | `wan/happyhorse-1.1-r2v` | - | 42.2422 / s | 54.3114 / s | - | - |
-| [HappyHorse 1.1 T2V](/development/comfy-router/models/wan/happyhorse-1-1-t2v/code) | `wan/happyhorse-1.1-t2v` | - | 42.2422 / s | 54.3114 / s | - | - |
-| [LTX 2.5 Fast](/development/comfy-router/models/ltx/ltx-2-5-fast/code) | `ltx/ltx-2-5-fast` | - | 27.1557 / s | 39.2249 / s | 57.3287 / s | 90.519 / s |
-| [LTX 2.5 Pro](/development/comfy-router/models/ltx/ltx-2-5-pro/code) | `ltx/ltx-2-5-pro` | - | 36.2076 / s | 51.2941 / s | - | - |
-| [P Video 2 Standard](/development/comfy-router/models/pruna/p-video-2/code) | `pruna/p-video-2` | - | 7.5432 / s | 15.0865 / s | - | - |
-| [P Video 2 Draft](/development/comfy-router/models/pruna/p-video-2/code) | `pruna/p-video-2` | - | 4.5259 / s | 9.0519 / s | - | - |
-| [Wan 2.5 I2V Preview](/development/comfy-router/models/wan/wan2-5-i2v-preview/code) | `wan/wan2.5-i2v-preview` | 10.55 / s | 21.1 / s | 31.65 / s | - | - |
-| [Wan 2.5 T2V Preview](/development/comfy-router/models/wan/wan2-5-t2v-preview/code) | `wan/wan2.5-t2v-preview` | 10.55 / s | 21.1 / s | 31.65 / s | - | - |
-| [Wan 2.6 I2V](/development/comfy-router/models/wan/wan2-6-i2v/code) | `wan/wan2.6-i2v` | - | 21.1 / s | 31.65 / s | - | - |
-| [Wan 2.6 R2V](/development/comfy-router/models/wan/wan2-6-r2v/code) | `wan/wan2.6-r2v` | - | 21.1 / s | 31.65 / s | - | - |
-| [Wan 2.6 T2V](/development/comfy-router/models/wan/wan2-6-t2v/code) | `wan/wan2.6-t2v` | - | 21.1 / s | 31.65 / s | - | - |
-| [Wan 2.7 I2V](/development/comfy-router/models/wan/wan2-7-i2v/code) | `wan/wan2.7-i2v` | - | 21.1 / s | 31.65 / s | - | - |
-| [Wan 2.7 R2V](/development/comfy-router/models/wan/wan2-7-r2v/code) | `wan/wan2.7-r2v` | - | 21.1 / s | 31.65 / s | - | - |
-| [Wan 2.7 T2V](/development/comfy-router/models/wan/wan2-7-t2v/code) | `wan/wan2.7-t2v` | - | 21.1 / s | 31.65 / s | - | - |
-| [Wan 2.7 Video Edit](/development/comfy-router/models/wan/wan2-7-videoedit/code) | `wan/wan2.7-videoedit` | - | 21.1 / s | 31.65 / s | - | - |
-| [Wan 3.0 Video](/development/comfy-router/models/wan/wan3-0-video/code) | `wan/wan3.0-video` | 10.55 / s | 21.1 / s | 42.2 / s | - | - |
-| [Wan 3.0 Video Prime](/development/comfy-router/models/wan/wan3-0-video-prime/code) | `wan/wan3.0-video-prime` | 14.348 / s | 29.54 / s | 59.08 / s | - | - |
-
-| Name | Model ID | 768p Credits | 2K Credits |
-| --- | --- | ---: | ---: |
-| [MiniMax H3](/development/comfy-router/models/minimax/minimax-h3/code) | `minimax/minimax-h3` | 27.1557 / s | 39.2249 / s |
-
-| Name | Model ID | Audio | 720p Credits | 1080p Credits | 4K Credits |
-| --- | --- | --- | ---: | ---: | ---: |
-| [Veo 3.1 Fast Generate 001](/development/comfy-router/models/veo/veo-3-1-fast-generate-001/code) | `veo/veo-3.1-fast-generate-001` | No audio | 16.88 / s | 21.1 / s | 52.75 / s |
-| | | Audio | 21.1 / s | 25.32 / s | 63.3 / s |
-| [Veo 3.1 Generate 001](/development/comfy-router/models/veo/veo-3-1-generate-001/code) | `veo/veo-3.1-generate-001` | No audio | 42.2 / s | 42.2 / s | 84.4 / s |
-| | | Audio | 84.4 / s | 84.4 / s | 126.6 / s |
-| [Veo 3.1 Lite Generate 001](/development/comfy-router/models/veo/veo-3-1-lite-generate-001/code) | `veo/veo-3.1-lite-generate-001` | No audio | 6.33 / s | 10.55 / s | - |
-| | | Audio | 10.55 / s | 16.88 / s | - |
-
-#### SwitchX image and video rates
-
-Images are billed per output image. Videos are billed per 30 output frames, rounded up.
-
-| Name | Model ID | 720p Credits | 1080p Credits |
-| --- | --- | ---: | ---: |
-| [SwitchX](/development/comfy-router/models/beeble/switchx/code) | `beeble/switchx` | Image: 30.173
Video: 30.173 | Image: 90.519
Video: 90.519 |
-
-#### Rates per second
-
-| Name | Model ID | Credits |
-| --- | --- | ---: |
-| [Aleph 2](/development/comfy-router/models/runway/aleph2/code) | `runway/aleph2` | 84.4844 / s |
-| [Gen 4 Turbo](/development/comfy-router/models/runway/gen4-turbo/code) | `runway/gen4_turbo` | 15.0865 / s |
-| [Sync 3](/development/comfy-router/models/synclabs/sync-3/code) | `synclabs/sync-3` | 40.1301 / s |
-| [Erase](/development/comfy-router/models/bria/video-edit-erase/code) | `bria/video-edit-erase` | 13.5778 / s |
-| [Green Screen](/development/comfy-router/models/bria/video-edit-green-screen/code) | `bria/video-edit-green-screen` | 10.55 / s |
-| [Remove Background](/development/comfy-router/models/bria/video-edit-remove-background/code) | `bria/video-edit-remove-background` | 10.55 / s |
-| [Replace Background](/development/comfy-router/models/bria/video-edit-replace-background/code) | `bria/video-edit-replace-background` | 10.55 / s |
-
-#### Usage-based rates
-
-Comfy usage-based rates are billed per active GPU-second; total cost depends on runtime.
-
-| Name | Model ID | Credits |
-| --- | --- | ---: |
-| [FLUX 3 Video](/development/comfy-router/models/black-forest-labs/flux-3-video/code) | `bfl/flux-3-video` | 0.266 / GPU-second |
-| [FLUX Video Upscale](/development/comfy-router/models/black-forest-labs/flux-video-upscale/code) | `bfl/video-upscale-v1` | 0.266 / GPU-second |
-| [Grok Imagine Video](/development/comfy-router/models/xai/grok-imagine-video/code) | `xai/grok-imagine-video` | 0.266 / GPU-second |
-| [Grok Imagine Video 1.5](/development/comfy-router/models/xai/grok-imagine-video-1-5/code) | `xai/grok-imagine-video-1.5` | 0.266 / GPU-second |
-| [Grok Imagine Video 1.5 Preview](/development/comfy-router/models/xai/grok-imagine-video-1-5-preview/code) | `xai/grok-imagine-video-1.5-preview` | 0.266 / GPU-second |
-| [Ray 2](/development/comfy-router/models/luma/ray-2/code) | `luma/ray-2` | 0.266 / GPU-second |
-| [Ray Flash 2](/development/comfy-router/models/luma/ray-flash-2/code) | `luma/ray-flash-2` | 0.266 / GPU-second |
-| [Video Edit V1](/development/comfy-router/models/black-forest-labs/video-edit-v1/code) | `bfl/video-edit-v1` | 0.266 / GPU-second |
-
-#### Not published
-
-| Name | Model ID | Credits |
-| --- | --- | ---: |
-| [Grok Imagine Video 1.5 Lite](/development/comfy-router/models/xai/grok-imagine-video-1-5-lite/code) | `xai/grok-imagine-video-1.5-lite` | Not published |
-| [Video To Video Resize](/development/comfy-router/models/moonvalley/video-to-video-resize/code) | `moonvalley/video-to-video-resize` | Not published |
-
-#### Rates per Kling credit
-
-| Name | Model ID | Credits |
-| --- | --- | ---: |
-| [Kling 3.0 Turbo](/development/comfy-router/models/kling/kling-3-0-turbo/code) | `kling/kling-3.0-turbo` | 29.54 / Kling credit |
-| [Kling V2.5 Turbo](/development/comfy-router/models/kling/kling-v2-5-turbo/code) | `kling/kling-v2-5-turbo` | 29.54 / Kling credit |
-| [Kling V2.6](/development/comfy-router/models/kling/kling-v2-6/code) | `kling/kling-v2-6` | 29.54 / Kling credit |
-| [Kling V3](/development/comfy-router/models/kling/kling-v3/code) | `kling/kling-v3` | 29.54 / Kling credit |
-| [Kling V3 Omni](/development/comfy-router/models/kling/kling-v3-omni/code) | `kling/kling-v3-omni` | 29.54 / Kling credit |
-| [Kling Video O1](/development/comfy-router/models/kling/kling-video-o1/code) | `kling/kling-video-o1` | 29.54 / Kling credit |
-| [Videos Avatar Image 2video](/development/comfy-router/models/kling/videos-avatar-image2video/code) | `kling/videos-avatar-image2video` | 29.54 / Kling credit |
-| [Videos Lip Sync](/development/comfy-router/models/kling/videos-lip-sync/code) | `kling/videos-lip-sync` | 29.54 / Kling credit |
-| [Videos Video Extend](/development/comfy-router/models/kling/videos-video-extend/code) | `kling/videos-video-extend` | 29.54 / Kling credit |
-
-#### Rates per extra reference image
-
-| Name | Model ID | Option | Credits |
-| --- | --- | --- | ---: |
-| [MiniMax H3](/development/comfy-router/models/minimax/minimax-h3/code) | `minimax/minimax-h3` | References beyond 5 | 12.0692 / extra reference image |
-
-#### Rates per request
-
-| Name | Model ID | 5s Credits / request | 10s Credits / request |
-| --- | --- | ---: | ---: |
-| [Image To Video](/development/comfy-router/models/moonvalley/image-to-video/code) | `moonvalley/image-to-video` | 316.5 | 633 |
-| [Text To Video](/development/comfy-router/models/moonvalley/text-to-video/code) | `moonvalley/text-to-video` | 316.5 | 633 |
-| [Video To Video](/development/comfy-router/models/moonvalley/video-to-video/code) | `moonvalley/video-to-video` | 474.75 | 844 |
-
-#### Seedance video token rates
-
-| Name | Model ID | Image to video / Text to video Credits / 1M tokens | Video to video Credits / 1M tokens |
-| --- | --- | ---: | ---: |
-| [Dreamina Seedance 2.0 Fast](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-fast-260128/code) | `byteplus/dreamina-seedance-2-0-fast-260128` | 1689.688 | 995.709 |
-| [Dreamina Seedance 2.0 Mini](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-mini/code) | `byteplus/dreamina-seedance-2-0-mini` | 1056.055 | 633.633 |
-| [Seedance 1.0 Pro](/development/comfy-router/models/byteplus/seedance-1-0-pro-250528/code) | `byteplus/seedance-1-0-pro-250528` | 527.5 | - |
-| [Seedance 1.0 Pro Fast](/development/comfy-router/models/byteplus/seedance-1-0-pro-fast-251015/code) | `byteplus/seedance-1-0-pro-fast-251015` | 211 | - |
-
-#### Seedance 1.5 Pro audio rates
-
-| Name | Model ID | No audio Credits / 1M tokens | Audio Credits / 1M tokens |
-| --- | --- | ---: | ---: |
-| [Seedance 1.5 Pro](/development/comfy-router/models/byteplus/seedance-1-5-pro-251215/code) | `byteplus/seedance-1-5-pro-251215` | 253.2 | 506.4 |
-
-
-
-
-#### Per 1K video tokens by resolution
-
-| Name | Model ID | 480p Credits | 720p Credits | 1080p Credits | 4K Credits |
-| --- | --- | ---: | ---: | ---: | ---: |
-| [Dreamina Seedance 2.0](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | `byteplus/dreamina-seedance-2-0-260128` | 2.954 | 2.954 | 2.954 | 1.688 |
-| [Dreamina Seedance 2.5](/development/comfy-router/models/byteplus/dreamina-seedance-2-5-260628/code) | `byteplus/dreamina-seedance-2-5-260628` | 4.5154 | 4.5154 | 4.9374 | - |
-
-#### Rates per generation
-
-| Name | Model ID | Credits / generation |
-| --- | --- | ---: |
-| [H3 Max](/development/comfy-router/models/fal/h3-max/code) | `fal/h3-max` | 15.0865 |
-| [H3 Max Turbo](/development/comfy-router/models/fal/h3-max-turbo/code) | `fal/h3-max-turbo` | 7.5432 |
-
-
-
-
-#### Per 1K video tokens by resolution
-
-| Name | Model ID | 480p Credits | 720p Credits |
-| --- | --- | ---: | ---: |
-| [Dreamina Seedance 2.0](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | `byteplus/dreamina-seedance-2-0-260128` | 2.954 | 2.954 |
-| [Dreamina Seedance 2.5](/development/comfy-router/models/byteplus/dreamina-seedance-2-5-260628/code) | `byteplus/dreamina-seedance-2-5-260628` | 4.5154 | 4.5154 |
-
-#### Per second by resolution
-
-| Name | Model ID | 480p Credits | 720p Credits | 1080p Credits | 4K Credits |
-| --- | --- | ---: | ---: | ---: | ---: |
-| [Kling 3.0 Turbo](/development/comfy-router/models/kling/kling-3-0-turbo/code) | `kling/kling-3.0-turbo` | - | 23.632 / s | 29.54 / s | - |
-| [Wan 3.0 Video](/development/comfy-router/models/wan/wan3-0-video/code) | `wan/wan3.0-video` | 10.55 / s | 21.1 / s | 42.2 / s | - |
-| [Higgsfield Kling 3.4k](/development/comfy-router/models/higgsfield/higgsfield-kling-3-4k/code) | `higgsfield/higgsfield-kling-3-4k` | - | - | - | 88.62 / s |
-
-#### Rates per second
-
-| Name | Model ID | Credits |
-| --- | --- | ---: |
-| [Higgsfield Kling 3 Pro](/development/comfy-router/models/higgsfield/higgsfield-kling-3-pro/code) | `higgsfield/higgsfield-kling-3-pro` | 35.448 / s |
-| [Kling V3 Standard](/development/comfy-router/models/kling/kling-v3/code) | `kling/kling-v3` | 26.6282 / s |
-
-
-
-
-| Name | Model ID | Credits |
-| --- | --- | ---: |
-| [Dreamina Seedance 2.0](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | `byteplus/dreamina-seedance-2-0-260128` | Usage-based
request usage |
-| [Dreamina Seedance 2.5](/development/comfy-router/models/byteplus/dreamina-seedance-2-5-260628/code) | `byteplus/dreamina-seedance-2-5-260628` | Usage-based
request usage |
-
-
-
-
-#### Rates per request
-
-| Name | Model ID | Credits / request |
-| --- | --- | ---: |
-| [Dreamina Seedance 2.0](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | `byteplus/dreamina-seedance-2-0-260128` | 126.6 |
-| [Dreamina Seedance 2.5](/development/comfy-router/models/byteplus/dreamina-seedance-2-5-260628/code) | `byteplus/dreamina-seedance-2-5-260628` | 189.9 |
-
-#### Per 5 s by resolution
-
-| Name | Model ID | 720p Credits | 1080p Credits | 2K Credits | 4K Credits |
-| --- | --- | ---: | ---: | ---: | ---: |
-| [Flashvsr](/development/comfy-router/models/wavespeed/flashvsr/code) | `wavespeed/flashvsr` | 12.66 / 5 s | 18.99 / 5 s | 25.32 / 5 s | 33.76 / 5 s |
-
-
-
-
-
-
-
-
-
-
-
-
-#### Token rates
-
-| Name | Model ID | Cached input credits / 1M tokens | Input credits / 1M tokens | Output credits / 1M tokens |
-| --- | --- | ---: | ---: | ---: |
-| [Claude Fable 5.1](/development/comfy-router/models/anthropic/claude-fable-5-1/code) | `anthropic/claude-fable-5-1` | Read: 75.4325
Write (1h): 6034.6
Write (5m): 3771.625 | Text: 3017.3 | Text: 15086.5 |
-| [Claude Fable 5](/development/comfy-router/models/anthropic/claude-fable-5/code) | `anthropic/claude-fable-5` | Read: 301.73
Write (1h): 6034.6
Write (5m): 3771.625 | Text: 3017.3 | Text: 15086.5 |
-| [Claude Haiku 4.5](/development/comfy-router/models/anthropic/claude-haiku-4-5-20251001/code) | `anthropic/claude-haiku-4-5-20251001` | Read: 21.1
Write (1h): 422
Write (5m): 263.75 | Text: 211 | Text: 1055 |
-| [Claude Opus 5.5](/development/comfy-router/models/anthropic/claude-opus-5-5/code) | `anthropic/claude-opus-5-5` | Read: 60.346
Write (1h): 2413.84
Write (5m): 1508.65 | Text: 1206.92 | Text: 6034.6 |
-| [Claude Opus 5](/development/comfy-router/models/anthropic/claude-opus-5/code) | `anthropic/claude-opus-5` | Read: 150.865
Write (1h): 3017.3
Write (5m): 1885.8125 | Text: 1508.65 | Text: 7543.25 |
-| [Claude Opus 4.8](/development/comfy-router/models/anthropic/claude-opus-4-8/code) | `anthropic/claude-opus-4-8` | Read: 150.865
Write (1h): 3017.3
Write (5m): 1885.8125 | Text: 1508.65 | Text: 7543.25 |
-| [Claude Opus 4.7](/development/comfy-router/models/anthropic/claude-opus-4-7/code) | `anthropic/claude-opus-4-7` | Read: 105.5
Write (1h): 2110
Write (5m): 1318.75 | Text: 1055 | Text: 5275 |
-| [Claude Opus 4.6](/development/comfy-router/models/anthropic/claude-opus-4-6/code) | `anthropic/claude-opus-4-6` | Read: 105.5
Write (1h): 2110
Write (5m): 1318.75 | Text: 1055 | Text: 5275 |
-| [Claude Sonnet 5.5](/development/comfy-router/models/anthropic/claude-sonnet-5-5/code) | `anthropic/claude-sonnet-5-5` | Read: 60.346
Write (1h): 1206.92
Write (5m): 754.325 | Text: 603.46 | Text: 3017.3 |
-| [Claude Sonnet 5](/development/comfy-router/models/anthropic/claude-sonnet-5/code) | `anthropic/claude-sonnet-5` | Read: 60.346
Write (1h): 1206.92
Write (5m): 754.325 | Text: 603.46 | Text: 3017.3 |
-| [Claude Sonnet 4.6](/development/comfy-router/models/anthropic/claude-sonnet-4-6/code) | `anthropic/claude-sonnet-4-6` | Read: 63.3
Write (1h): 1266
Write (5m): 791.25 | Text: 633 | Text: 3165 |
-| [Claude Sonnet 4.5](/development/comfy-router/models/anthropic/claude-sonnet-4-5-20250929/code) | `anthropic/claude-sonnet-4-5-20250929` | Read: 63.3
Write (1h): 1266
Write (5m): 791.25 | Text: 633 | Text: 3165 |
-| [Gemini 3.8 Flash](/development/comfy-router/models/google/gemini-3-8-flash/code) | `vertexai/gemini-3.8-flash` | - | Audio / Image / Text / Video: 452.595 | Text / Reasoning: 2262.975 |
-| [Gemini 3.7 Flash](/development/comfy-router/models/google/gemini-3-7-flash/code) | `vertexai/gemini-3.7-flash` | - | Audio / Image / Text / Video: 452.595 | Text / Reasoning: 2262.975 |
-| [Gemini 3.1 Flash Lite](/development/comfy-router/models/google/gemini-3-1-flash-lite/code) | `vertexai/gemini-3.1-flash-lite` | - | Image / Text / Video: 52.75
Audio: 105.5 | Text / Reasoning: 316.5 |
-| [Gemini Omni 1.1 Flash](/development/comfy-router/models/gemini-interactions/gemini-omni-1-1-flash/code) | `gemini-interactions/gemini-omni-1.1-flash` | - | Audio / Image / Text / Video: 452.595 | Text / Reasoning: 2715.57
Video: 5280.275 |
-| [Gemini Omni Flash Preview](/development/comfy-router/models/gemini-interactions/gemini-omni-flash-preview/code) | `gemini-interactions/gemini-omni-flash-preview` | - | Audio / Image / Text / Video: 452.595 | Text / Reasoning: 2715.57
Video: 5280.275 |
-| [Google Gemini](/development/comfy-router/models/google/gemini/code) | `vertexai/gemini-2.5-flash` | - | Image / Text / Video: 63.3
Audio: 211 | Text: 527.5 |
-| [Google Gemini](/development/comfy-router/models/google/gemini/code) | `vertexai/gemini-2.5-pro` | - | Audio / Image / Text / Video: 263.75 | Text: 2110 |
-| [Google Gemini](/development/comfy-router/models/google/gemini/code) | `vertexai/gemini-3.1-pro-preview` | - | Audio / Image / Text / Video: 422 | Text / Reasoning: 2532
Image: 25320 |
-| [Google Gemini](/development/comfy-router/models/google/gemini/code) | `vertexai/gemini-3.5-flash` | - | Audio / Image / Text / Video: 316.5 | Text / Reasoning: 1899 |
-| [GPT 6 Astra](/development/comfy-router/models/openai/gpt-6-astra/code) | `openai/gpt-6-astra` | Read: 301.73
Write: 3771.625 | Text: 3017.3 | Text: 15086.5 |
-| [GPT 6 Luna](/development/comfy-router/models/openai/gpt-6-luna/code) | `openai/gpt-6-luna` | Read: 3.0173
Write: 37.7162 | Text: 30.173 | Text: 150.865 |
-| [GPT 6 Sol](/development/comfy-router/models/openai/gpt-6-sol/code) | `openai/gpt-6-sol` | Read: 60.346
Write: 754.325 | Text: 603.46 | Text: 3017.3 |
-| [GPT 5.6 Luna](/development/comfy-router/models/openai/gpt-5-6-luna/code) | `openai/gpt-5.6-luna` | Read: 21.1
Write: 263.75 | Text: 211 | Text: 1266 |
-| [GPT 5.6 Sol](/development/comfy-router/models/openai/gpt-5-6-sol/code) | `openai/gpt-5.6-sol` | Read: 105.5
Write: 1318.75 | Text: 1055 | Text: 6330 |
-| [GPT 5.6 Terra](/development/comfy-router/models/openai/gpt-5-6-terra/code) | `openai/gpt-5.6-terra` | Read: 52.75
Write: 659.375 | Text: 527.5 | Text: 3165 |
-| [GPT 5.5](/development/comfy-router/models/openai/gpt-5-5/code) | `openai/gpt-5.5` | 105.5 | Text: 1055 | Text: 6330 |
-| [GPT 5.5 Pro](/development/comfy-router/models/openai/gpt-5-5-pro/code) | `openai/gpt-5.5-pro` | - | Text: 6330 | Text: 37980 |
-| [GPT 5](/development/comfy-router/models/openai/gpt-5/code) | `openai/gpt-5` | 26.375 | Text: 263.75 | Text: 2110 |
-| [GPT 5 Mini](/development/comfy-router/models/openai/gpt-5-mini/code) | `openai/gpt-5-mini` | 5.275 | Text: 52.75 | Text: 422 |
-| [GPT 5 Nano](/development/comfy-router/models/openai/gpt-5-nano/code) | `openai/gpt-5-nano` | 1.055 | Text: 10.55 | Text: 84.4 |
-| [GPT 4.1](/development/comfy-router/models/openai/gpt-4-1/code) | `openai/gpt-4.1` | 105.5 | Text: 422 | Text: 1688 |
-| [GPT 4.1 Mini](/development/comfy-router/models/openai/gpt-4-1-mini/code) | `openai/gpt-4.1-mini` | 21.1 | Text: 84.4 | Text: 337.6 |
-| [GPT 4.1 Nano](/development/comfy-router/models/openai/gpt-4-1-nano/code) | `openai/gpt-4.1-nano` | 5.275 | Text: 21.1 | Text: 84.4 |
-| [GPT 4o](/development/comfy-router/models/openai/gpt-4o/code) | `openai/gpt-4o` | 263.75 | Text: 527.5 | Text: 2110 |
-| [o4-mini](/development/comfy-router/models/openai/o4-mini/code) | `openai/o4-mini` | 58.025 | Text: 232.1 | Text: 928.4 |
-| [o3](/development/comfy-router/models/openai/o3/code) | `openai/o3` | 105.5 | Text: 422 | Text: 1688 |
-| [o1](/development/comfy-router/models/openai/o1/code) | `openai/o1` | 1582.5 | Text: 3165 | Text: 12660 |
-| [o1-pro](/development/comfy-router/models/openai/o1-pro/code) | `openai/o1-pro` | - | Text: 31650 | Text: 126600 |
-| [Seed 2.0 Lite](/development/comfy-router/models/byteplus/seed-2-0-lite-260228/code) | `byteplus/seed-2-0-lite-260228` | 10.55 | Text: 52.75 | Text: 422 |
-| [Seed 2.0 Mini](/development/comfy-router/models/byteplus/seed-2-0-mini-260215/code) | `byteplus/seed-2-0-mini-260215` | 4.22 | Text: 21.1 | Text: 84.4 |
-| [Seed 2.0 Pro](/development/comfy-router/models/byteplus/seed-2-0-pro-260328/code) | `byteplus/seed-2-0-pro-260328` | 21.1 | Text: 105.5 | Text: 633 |
-
-#### Usage-based rates
-
-Comfy usage-based rates are billed per active GPU-second; total cost depends on runtime.
-
-| Name | Model ID | Credits |
-| --- | --- | ---: |
-| [Chat Completions](/development/comfy-router/models/openrouter/chat-completions/code) | `openrouter/chat-completions` | 0.266 / GPU-second |
-
-#### Not published
-
-| Name | Model ID | Credits |
-| --- | --- | ---: |
-| [Structured Instruction](/development/comfy-router/models/bria/structured-instruction/code) | `bria/structured-instruction` | Not published |
-
-
-
-
-
-
-
-
-
-
-
-
-#### Rates by duration
-
-| Name | Model ID | Credits |
-| --- | --- | ---: |
-| [Eleven Sfx V2](/development/comfy-router/models/elevenlabs/eleven-sfx-v2/code) | `elevenlabs/eleven_sfx_v2` | 29.54 / minute |
-| [Seed Audio 1.0](/development/comfy-router/models/byteplus/seed-audio-1-0/code) | `byteplus/seed-audio-1.0` | 0.7543 / s |
-| [Seed Audio 1.0 Multilingual](/development/comfy-router/models/byteplus/seed-audio-1-0-multilingual/code) | `byteplus/seed-audio-1.0-multilingual` | 0.7543 / s |
-| [Starfish](/development/comfy-router/models/heygen/starfish/code) | `heygen/starfish` | 0.2013 / s |
-
-#### Rates per 1K characters
-
-| Name | Model ID | Option | Credits |
-| --- | --- | --- | ---: |
-| [Eleven V3](/development/comfy-router/models/elevenlabs/eleven-v3/code) | `elevenlabs/eleven_v3` | Dialogue / Text to speech | 21.1 / 1K characters |
-
-#### Not published
-
-| Name | Model ID | Credits |
-| --- | --- | ---: |
-| [Eleven V4](/development/comfy-router/models/elevenlabs/eleven-v4/code) | `elevenlabs/eleven_v4` | Not published |
-| [Eleven V4 Turbo](/development/comfy-router/models/elevenlabs/eleven-v4-turbo/code) | `elevenlabs/eleven_v4_turbo` | Not published |
-
-
-
-
-
-
-
-
-
-
-
-
-#### Rates per Meshy credit
-
-| Name | Model ID | Credits |
-| --- | --- | ---: |
-| [Animations](/development/comfy-router/models/meshy/animations/code) | `meshy/animations` | 12.0692 / Meshy credit |
-| [Meshy 5](/development/comfy-router/models/meshy/meshy-5/code) | `meshy/meshy-5` | 12.0692 / Meshy credit |
-| [Meshy 6](/development/comfy-router/models/meshy/meshy-6/code) | `meshy/meshy-6` | 12.0692 / Meshy credit |
-| [Meshy 7](/development/comfy-router/models/meshy/meshy-7/code) | `meshy/meshy-7` | 12.0692 / Meshy credit |
-| [Meshy 7.1](/development/comfy-router/models/meshy/meshy-7-1/code) | `meshy/meshy-7.1` | 12.0692 / Meshy credit |
-| [Remesh](/development/comfy-router/models/meshy/remesh/code) | `meshy/remesh` | 12.0692 / Meshy credit |
-| [Rigging](/development/comfy-router/models/meshy/rigging/code) | `meshy/rigging` | 12.0692 / Meshy credit |
-
-#### Rates per operation
-
-| Name | Model ID | Credits |
-| --- | --- | ---: |
-| [Hunyuan 3D Part](/development/comfy-router/models/tencent/hunyuan-3d-part/code) | `tencent/hunyuan-3d-part` | 126.6 / operation |
-| [Hunyuan 3D Texture Edit](/development/comfy-router/models/tencent/hunyuan-3d-texture-edit/code) | `tencent/hunyuan-3d-texture-edit` | 126.6 / operation |
-| [Hunyuan 3D Uv](/development/comfy-router/models/tencent/hunyuan-3d-uv/code) | `tencent/hunyuan-3d-uv` | 42.2 / operation |
-
-#### Usage-based rates
-
-Comfy usage-based rates are billed per active GPU-second; total cost depends on runtime.
-
-| Name | Model ID | Credits |
-| --- | --- | ---: |
-| [Hunyuan 3D Smart Topology](/development/comfy-router/models/tencent/hunyuan-3d-smart-topology/code) | `tencent/hunyuan-3d-smart-topology` | 0.266 / GPU-second |
-
-
-
-
-
-
-
-
-
-
-
-
-
-#### Token rates
-
-| Name | Model ID | Input USD / 1M tokens | Output USD / 1M tokens |
-| --- | --- | ---: | ---: |
-| [Gemini 2.5 Flash Image](/development/comfy-router/models/google/gemini-2-5-flash-image/code) | `vertexai/gemini-2.5-flash-image` | Image / Text / Video: $0.30
Audio: $1.00 | Text: $2.50
Image: $30.00 |
-| [Gemini Nano Banana 2.1](/development/comfy-router/models/google/gemini-nano-banana-2-1/code) | `vertexai/gemini-nano-banana-2.1` | Image / Text / Video: $2.145 | Text / Reasoning: $10.725
Image: $42.90 |
-| [GPT Image 2.5 Flare](/development/comfy-router/models/openai/gpt-image-2-5-flare/code) | `openai/gpt-image-2.5-flare` | Text: $7.15
Image: $11.44 | Text: $14.30
Image: $42.90 |
-| [GPT Image 2.5 Sunburst](/development/comfy-router/models/openai/gpt-image-2-5-sunburst/code) | `openai/gpt-image-2.5-sunburst` | Text: $7.15
Image: $11.44 | Text: $14.30
Image: $42.90 |
-| [GPT Image 1.5](/development/comfy-router/models/openai/gpt-image-1-5/code) | `openai/gpt-image-1.5` | Text: $5.00
Image: $8.00 | Text: $10.00
Image: $32.00 |
-| [GPT Image 1](/development/comfy-router/models/openai/gpt-image-1/code) | `openai/gpt-image-1` | Text: $5.00
Image: $10.00 | Image: $40.00 |
-| [Nano Banana 2](/development/comfy-router/models/google/nano-banana-2/code) | `vertexai/gemini-3.1-flash-image` | Image / Text / Video: $0.60 | Text / Reasoning: $3.60
Image: $72.00 |
-| [Nano Banana 2 Lite](/development/comfy-router/models/google/nano-banana-2-lite/code) | `vertexai/gemini-3.1-flash-lite-image` | Image / Text / Video: $0.30 | Text / Reasoning: $1.80
Image: $36.00 |
-| [Nano Banana Pro](/development/comfy-router/models/google/nano-banana-pro/code) | `vertexai/gemini-3-pro-image` | Image / Text: $2.40 | Text / Reasoning: $14.40
Image: $144.00 |
-
-#### Usage-based rates
-
-Comfy usage-based rates are billed per active GPU-second; total cost depends on runtime.
-
-| Name | Model ID | USD |
-| --- | --- | ---: |
-| [AI Image Upscaler Precision V2](/development/comfy-router/models/freepik/ai-image-upscaler-precision-v2/code) | `freepik/ai-image-upscaler-precision-v2` | $0.00126066 / GPU-second |
-| [Erase V1](/development/comfy-router/models/black-forest-labs/erase-v1/code) | `bfl/erase-v1` | $0.00126066 / GPU-second |
-| [FLUX 2 Max](/development/comfy-router/models/black-forest-labs/flux-2-max/code) | `bfl/flux-2-max` | $0.00126066 / GPU-second |
-| [FLUX 2 Pro](/development/comfy-router/models/black-forest-labs/flux-2-pro/code) | `bfl/flux-2-pro` | $0.00126066 / GPU-second |
-| [FLUX 3 Image](/development/comfy-router/models/black-forest-labs/flux-3-image/code) | `bfl/flux-3-image` | $0.00126066 / GPU-second |
-| [GPT Image 2](/development/comfy-router/models/openai/gpt-image-2/code) | `openai/gpt-image-2` | $0.00126066 / GPU-second |
-| [Grok Imagine Image](/development/comfy-router/models/xai/grok-imagine-image/code) | `xai/grok-imagine-image` | $0.00126066 / GPU-second |
-| [Grok Imagine Image 2.0](/development/comfy-router/models/xai/grok-imagine-image-2-0/code) | `xai/grok-imagine-image-2.0` | $0.00126066 / GPU-second |
-| [Grok Imagine Image Pro](/development/comfy-router/models/xai/grok-imagine-image-pro/code) | `xai/grok-imagine-image-pro` | $0.00126066 / GPU-second |
-| [Grok Imagine Image Quality](/development/comfy-router/models/xai/grok-imagine-image-quality/code) | `xai/grok-imagine-image-quality` | $0.00126066 / GPU-second |
-| [VTO V1](/development/comfy-router/models/black-forest-labs/vto-v1/code) | `bfl/vto-v1` | $0.00126066 / GPU-second |
-
-#### Image generation and edit rates
-
-| Name | Model ID | USD / generation |
-| --- | --- | ---: |
-| [AI Skin Enhancer Creative](/development/comfy-router/models/freepik/ai-skin-enhancer-creative/code) | `freepik/ai-skin-enhancer-creative` | $0.29 |
-| [AI Skin Enhancer Faithful](/development/comfy-router/models/freepik/ai-skin-enhancer-faithful/code) | `freepik/ai-skin-enhancer-faithful` | $0.37 |
-| [AI Skin Enhancer Flexible](/development/comfy-router/models/freepik/ai-skin-enhancer-flexible/code) | `freepik/ai-skin-enhancer-flexible` | $0.45 |
-| [FLUX 1.1 Pro](/development/comfy-router/models/black-forest-labs/flux-1-1-pro-ultra-image/code) | `bfl/flux-pro-1.1` | $0.04 |
-| [FLUX 1.1 Pro Ultra](/development/comfy-router/models/black-forest-labs/flux-1-1-pro-ultra-image/code) | `bfl/flux-pro-1.1-ultra` | $0.06 |
-| [FLUX Pro 1.0 Expand](/development/comfy-router/models/black-forest-labs/flux-pro-1-0-expand/code) | `bfl/flux-pro-1.0-expand` | $0.05 |
-| [FLUX Pro 1.0 Fill](/development/comfy-router/models/black-forest-labs/flux-pro-1-0-fill/code) | `bfl/flux-pro-1.0-fill` | $0.05 |
-| [FLUX.1 Kontext Max](/development/comfy-router/models/black-forest-labs/flux-1-kontext/code) | `bfl/flux-kontext-max` | $0.08 |
-| [FLUX.1 Kontext Pro](/development/comfy-router/models/black-forest-labs/flux-1-kontext/code) | `bfl/flux-kontext-pro` | $0.04 |
-| [Gen 4 Image](/development/comfy-router/models/runway/gen4-image/code) | `runway/gen4_image` | $0.1144 |
-| [Photon 1](/development/comfy-router/models/luma/photon-1/code) | `luma/photon-1` | $0.010439 |
-| [Photon Flash 1](/development/comfy-router/models/luma/photon-flash-1/code) | `luma/photon-flash-1` | $0.002717 |
-| [Seedream 4.0](/development/comfy-router/models/byteplus/seedream-4-0-250828/code) | `byteplus/seedream-4-0-250828` | $0.03 |
-| [Seedream 4.5](/development/comfy-router/models/byteplus/seedream-4-5-251128/code) | `byteplus/seedream-4-5-251128` | $0.04 |
-| [Seedream 5.0](/development/comfy-router/models/byteplus/seedream-5-0-260128/code) | `byteplus/seedream-5-0-260128` | $0.035 |
-| [Seedream 5.0 Flash](/development/comfy-router/models/byteplus/seedream-5-0-flash-260915/code) | `byteplus/seedream-5-0-flash-260915` | $0.02574 |
-| [Fibo](/development/comfy-router/models/bria/fibo/code) | `bria/fibo` | $0.04 |
-| [Add Object By Text](/development/comfy-router/models/bria/image-edit-add-object-by-text/code) | `bria/image-edit-add-object-by-text` | $0.0572 |
-| [Erase](/development/comfy-router/models/bria/image-edit-erase/code) | `bria/image-edit-erase` | $0.0286 |
-| [Erase By Text](/development/comfy-router/models/bria/image-edit-erase-by-text/code) | `bria/image-edit-erase-by-text` | $0.0572 |
-| [Erase Foreground](/development/comfy-router/models/bria/image-edit-erase-foreground/code) | `bria/image-edit-erase-foreground` | $0.0572 |
-| [Expand](/development/comfy-router/models/bria/image-edit-expand/code) | `bria/image-edit-expand` | $0.0286 |
-| [Generative Fill](/development/comfy-router/models/bria/image-edit-gen-fill/code) | `bria/image-edit-gen-fill` | $0.0429 |
-| [Increase Resolution](/development/comfy-router/models/bria/image-edit-increase-resolution/code) | `bria/image-edit-increase-resolution` | $0.0286 |
-| [Relight](/development/comfy-router/models/bria/image-edit-relight/code) | `bria/image-edit-relight` | $0.0572 |
-| [Remove Background](/development/comfy-router/models/bria/image-edit-remove-background/code) | `bria/image-edit-remove-background` | $0.018 |
-| [Replace Background](/development/comfy-router/models/bria/image-edit-replace-background/code) | `bria/image-edit-replace-background` | $0.0572 |
-| [Replace Object By Text](/development/comfy-router/models/bria/image-edit-replace-object-by-text/code) | `bria/image-edit-replace-object-by-text` | $0.0572 |
-| [Reseason](/development/comfy-router/models/bria/image-edit-reseason/code) | `bria/image-edit-reseason` | $0.0572 |
-| [Restore](/development/comfy-router/models/bria/image-edit-restore/code) | `bria/image-edit-restore` | $0.0572 |
-| [Kling Image O1](/development/comfy-router/models/kling/kling-image-o1/code) | `kling/kling-image-o1` | $0.028 |
-| [Wan 2.5 I2I Preview](/development/comfy-router/models/wan/wan2-5-i2i-preview/code) | `wan/wan2.5-i2i-preview` | $0.03 |
-| [Wan 2.5 T2I Preview](/development/comfy-router/models/wan/wan2-5-t2i-preview/code) | `wan/wan2.5-t2i-preview` | $0.03 |
-
-#### Not published
-
-| Name | Model ID | USD |
-| --- | --- | ---: |
-| [Ideogram 4.5](/development/comfy-router/models/ideogram/ideogram-4-5/code) | `ideogram/ideogram-4-5` | Not published |
-| [P Image Ideogram](/development/comfy-router/models/ideogram/p-image-ideogram/code) | `ideogram/p-image-ideogram` | Not published |
-| [Text To Image](/development/comfy-router/models/moonvalley/text-to-image/code) | `moonvalley/text-to-image` | Not published |
-
-#### Rates per input image
-
-| Name | Model ID | Option | USD |
-| --- | --- | --- | ---: |
-| [Qwen Image 3.0](/development/comfy-router/models/qwen/qwen-image-3-0/code) | `qwen/qwen-image-3.0` | Input image · 1K / 2K | $0.00429 / input image |
-| [Qwen Image 3.0 Pro](/development/comfy-router/models/qwen/qwen-image-3-0-pro/code) | `qwen/qwen-image-3.0-pro` | Input image · 1K / 2K | $0.00429 / input image |
-| [Seedream 5.0 Pro](/development/comfy-router/models/byteplus/seedream-5-0-pro-260628/code) | `byteplus/seedream-5-0-pro-260628` | Input images after the first | $0.003 / input image |
-
-#### Rates per Recraft credit
-
-| Name | Model ID | USD |
-| --- | --- | ---: |
-| [Recraft V2](/development/comfy-router/models/recraft/recraftv2/code) | `recraft/recraftv2` | $0.00143 / Recraft credit |
-| [Recraft V3](/development/comfy-router/models/recraft/recraftv3/code) | `recraft/recraftv3` | $0.00143 / Recraft credit |
-| [Recraft V4](/development/comfy-router/models/recraft/recraftv4/code) | `recraft/recraftv4` | $0.00143 / Recraft credit |
-| [Recraft V4 Pro](/development/comfy-router/models/recraft/recraftv4-pro/code) | `recraft/recraftv4_pro` | $0.00143 / Recraft credit |
-| [Recraft V4 Styles](/development/comfy-router/models/recraft/recraftv4-styles/code) | `recraft/recraftv4_styles` | $0.00143 / Recraft credit |
-| [Recraft V4 Styles Pro](/development/comfy-router/models/recraft/recraftv4-styles-pro/code) | `recraft/recraftv4_styles_pro` | $0.00143 / Recraft credit |
-| [Recraft V4 Styles Pro Vector](/development/comfy-router/models/recraft/recraftv4-styles-pro-vector/code) | `recraft/recraftv4_styles_pro_vector` | $0.00143 / Recraft credit |
-| [Recraft V4 Styles Vector](/development/comfy-router/models/recraft/recraftv4-styles-vector/code) | `recraft/recraftv4_styles_vector` | $0.00143 / Recraft credit |
-| [Recraft V4.1](/development/comfy-router/models/recraft/recraftv4-1/code) | `recraft/recraftv4_1` | $0.00143 / Recraft credit |
-| [Recraft V4.1 Pro](/development/comfy-router/models/recraft/recraftv4-1-pro/code) | `recraft/recraftv4_1_pro` | $0.00143 / Recraft credit |
-| [Recraft V4.1 Pro Vector](/development/comfy-router/models/recraft/recraftv4-1-pro-vector/code) | `recraft/recraftv4_1_pro_vector` | $0.00143 / Recraft credit |
-| [Recraft V4.1 Utility](/development/comfy-router/models/recraft/recraftv4-1-utility/code) | `recraft/recraftv4_1_utility` | $0.00143 / Recraft credit |
-| [Recraft V4.1 Utility Pro](/development/comfy-router/models/recraft/recraftv4-1-utility-pro/code) | `recraft/recraftv4_1_utility_pro` | $0.00143 / Recraft credit |
-| [Recraft V4.1 Utility Pro Vector](/development/comfy-router/models/recraft/recraftv4-1-utility-pro-vector/code) | `recraft/recraftv4_1_utility_pro_vector` | $0.00143 / Recraft credit |
-| [Recraft V4.1 Utility Vector](/development/comfy-router/models/recraft/recraftv4-1-utility-vector/code) | `recraft/recraftv4_1_utility_vector` | $0.00143 / Recraft credit |
-| [Recraft V4.1 Vector](/development/comfy-router/models/recraft/recraftv4-1-vector/code) | `recraft/recraftv4_1_vector` | $0.00143 / Recraft credit |
-
-#### Rates per reference image
-
-| Name | Model ID | USD |
-| --- | --- | ---: |
-| [Uni 1](/development/comfy-router/models/luma_2/uni-1/code) | `luma_2/uni-1` | $0.003 / reference image |
-| [Uni 1 Max](/development/comfy-router/models/luma_2/uni-1-max/code) | `luma_2/uni-1-max` | $0.003 / reference image |
-
-#### Krea 2 generation rates (USD / generation)
-
-| Name | Model ID | Moodboards USD | Style references USD | Text only USD |
-| --- | --- | ---: | ---: | ---: |
-| [Krea 2](/development/comfy-router/models/krea/krea-2/code) | `krea/krea-2` | $0.04 | $0.035 | $0.03 |
-| [Krea 2 Large](/development/comfy-router/models/krea/krea-2-large/code) | `krea/krea-2-large` | $0.07 | $0.065 | $0.06 |
-| [Krea 2 Medium](/development/comfy-router/models/krea/krea-2-medium/code) | `krea/krea-2-medium` | $0.04 | $0.035 | $0.03 |
-| [Krea 2 Medium Turbo](/development/comfy-router/models/krea/krea-2-medium-turbo/code) | `krea/krea-2-medium-turbo` | $0.02 | $0.0175 | $0.015 |
-
-#### Qwen Image 3.0 output image rates
-
-| Name | Model ID | 1K USD / generation | 2K USD / generation |
-| --- | --- | ---: | ---: |
-| [Qwen Image 3.0](/development/comfy-router/models/qwen/qwen-image-3-0/code) | `qwen/qwen-image-3.0` | $0.0429 | $0.0429 |
-| [Qwen Image 3.0 Pro](/development/comfy-router/models/qwen/qwen-image-3-0-pro/code) | `qwen/qwen-image-3.0-pro` | $0.0572 | $0.10725 |
-
-#### Seedream 5.0 Pro output image rates
-
-| Name | Model ID | Layer separation · Standard USD / generation | Layer separation · Large USD / generation | Output image · Standard USD / generation | Output image · Large USD / generation |
-| --- | --- | ---: | ---: | ---: | ---: |
-| [Seedream 5.0 Pro](/development/comfy-router/models/byteplus/seedream-5-0-pro-260628/code) | `byteplus/seedream-5-0-pro-260628` | $0.032175 | $0.06435 | $0.045 | $0.09 |
-
-#### Image generation and then image edit
-
-| Name | Model ID | Image generation USD / generation | Image edit USD / generation |
-| --- | --- | ---: | ---: |
-| [Uni 1](/development/comfy-router/models/luma_2/uni-1/code) | `luma_2/uni-1` | $0.0404 | $0.0434 |
-| [Uni 1 Max](/development/comfy-router/models/luma_2/uni-1-max/code) | `luma_2/uni-1-max` | $0.10 | $0.103 |
-
-#### Ideogram image rates
-
-| Name | Model ID | Standard USD / generation | Quality USD / generation | Turbo USD / generation |
-| --- | --- | ---: | ---: | ---: |
-| [Ideogram 4.0](/development/comfy-router/models/ideogram/ideogram-v4/code) | `ideogram/ideogram-v4` | $0.0858 | $0.143 | $0.0429 |
-| [Ideogram V3](/development/comfy-router/models/ideogram/ideogram-v3/code) | `ideogram/ideogram-v3` | $0.0858 | $0.1287 | $0.0429 |
-
-
-
-
-#### Not published
-
-| Name | Model ID | USD |
-| --- | --- | ---: |
-| [Crystal Upscaler](/development/comfy-router/models/fal/crystal-upscaler/code) | `fal/crystal-upscaler` | Not published |
-
-#### Usage-based rates
-
-| Name | Model ID | USD |
-| --- | --- | ---: |
-| [GPT Image 2](/development/comfy-router/models/openai/gpt-image-2/code) | `openai/gpt-image-2` | Usage-based
request usage |
-| [GPT Image 2.5 Flare](/development/comfy-router/models/openai/gpt-image-2-5-flare/code) | `openai/gpt-image-2.5-flare` | Usage-based
request usage |
-| [GPT Image 2.5 Sunburst](/development/comfy-router/models/openai/gpt-image-2-5-sunburst/code) | `openai/gpt-image-2.5-sunburst` | Usage-based
request usage |
-
-#### Image generation and edit rates
-
-| Name | Model ID | USD / generation |
-| --- | --- | ---: |
-| [Nano Banana 2](/development/comfy-router/models/google/nano-banana-2/code) | `vertexai/gemini-3.1-flash-image` | $0.08 |
-| [Nano Banana Pro](/development/comfy-router/models/google/nano-banana-pro/code) | `vertexai/gemini-3-pro-image` | $0.15 |
-| [Patina](/development/comfy-router/models/fal/patina/code) | `fal/patina` | $0.0143 |
-
-
-
-
-| Name | Model ID | USD |
-| --- | --- | ---: |
-| [GPT Image 2](/development/comfy-router/models/openai/gpt-image-2/code) | `openai/gpt-image-2` | Usage-based
request usage |
-| [GPT Image 2.5 Flare](/development/comfy-router/models/openai/gpt-image-2-5-flare/code) | `openai/gpt-image-2.5-flare` | Usage-based
request usage |
-| [GPT Image 2.5 Sunburst](/development/comfy-router/models/openai/gpt-image-2-5-sunburst/code) | `openai/gpt-image-2.5-sunburst` | Usage-based
request usage |
-| [Nano Banana 2](/development/comfy-router/models/google/nano-banana-2/code) | `vertexai/gemini-3.1-flash-image` | Usage-based
request usage |
-| [Nano Banana Pro](/development/comfy-router/models/google/nano-banana-pro/code) | `vertexai/gemini-3-pro-image` | Usage-based
request usage |
-
-
-
-
-#### Image generation and edit rates
-
-| Name | Model ID | Text to image USD / generation | Image edit USD / generation | Image edit · 1K / 2K USD / generation | Image edit · 4K USD / generation |
-| --- | --- | ---: | ---: | ---: | ---: |
-| [GPT Image 2](/development/comfy-router/models/openai/gpt-image-2/code) | `openai/gpt-image-2` | $0.06 | $0.07 | - | - |
-| [GPT Image 2.5 Flare](/development/comfy-router/models/openai/gpt-image-2-5-flare/code) | `openai/gpt-image-2.5-flare` | $0.024 | $0.039 | - | - |
-| [GPT Image 2.5 Sunburst](/development/comfy-router/models/openai/gpt-image-2-5-sunburst/code) | `openai/gpt-image-2.5-sunburst` | $0.024 | $0.039 | - | - |
-| [Nano Banana 2](/development/comfy-router/models/google/nano-banana-2/code) | `vertexai/gemini-3.1-flash-image` | $0.07 | $0.07 | - | - |
-| [Nano Banana Pro](/development/comfy-router/models/google/nano-banana-pro/code) | `vertexai/gemini-3-pro-image` | $0.14 | - | $0.14 | $0.24 |
-
-| Name | Model ID | USD / generation |
-| --- | --- | ---: |
-| [Seedvr 2](/development/comfy-router/models/wavespeed/seedvr2/code) | `wavespeed/seedvr2` | $0.01 |
-| [Ultimate Image Upscaler](/development/comfy-router/models/wavespeed/ultimate-image-upscaler/code) | `wavespeed/ultimate-image-upscaler` | $0.06 |
-
-#### Image quality and resolution
-
-| Name | Model ID | Operation | Quality | 1K USD / generation | 2K USD / generation | 4K USD / generation |
-| --- | --- | --- | --- | ---: | ---: | ---: |
-| [GPT Image 2](/development/comfy-router/models/openai/gpt-image-2/code) | `openai/gpt-image-2` | Text to image | Low | $0.01 | $0.02 | $0.03 |
-| | | | Medium | $0.06 | $0.10 | $0.18 |
-| | | | High | $0.22 | $0.40 | $0.72 |
-| | | Image edit | Low | $0.02 | $0.03 | $0.04 |
-| | | | Medium | $0.07 | $0.11 | $0.19 |
-| | | | High | $0.23 | $0.41 | $0.73 |
-| [GPT Image 2.5 Flare](/development/comfy-router/models/openai/gpt-image-2-5-flare/code) | `openai/gpt-image-2.5-flare` | Text to image | Low | $0.01 | $0.02 | $0.03 |
-| | | | Medium | $0.024 | $0.04 | $0.07 |
-| | | | High | $0.09 | $0.15 | $0.27 |
-| | | | Extra high | $0.16 | $0.27 | $0.48 |
-| | | | Max | $0.36 | $0.60 | $1.00 |
-| | | Image edit | Low | $0.025 | $0.035 | $0.045 |
-| | | | Medium | $0.039 | $0.055 | $0.085 |
-| | | | High | $0.105 | $0.165 | $0.285 |
-| | | | Extra high | $0.175 | $0.285 | $0.495 |
-| | | | Max | $0.375 | $0.615 | $1.015 |
-| [GPT Image 2.5 Sunburst](/development/comfy-router/models/openai/gpt-image-2-5-sunburst/code) | `openai/gpt-image-2.5-sunburst` | Text to image | Low | $0.01 | $0.02 | $0.03 |
-| | | | Medium | $0.024 | $0.04 | $0.07 |
-| | | | High | $0.09 | $0.15 | $0.27 |
-| | | | Extra high | $0.16 | $0.27 | $0.48 |
-| | | | Max | $0.36 | $0.60 | $1.00 |
-| | | Image edit | Low | $0.025 | $0.035 | $0.045 |
-| | | | Medium | $0.039 | $0.055 | $0.085 |
-| | | | High | $0.105 | $0.165 | $0.285 |
-| | | | Extra high | $0.175 | $0.285 | $0.495 |
-| | | | Max | $0.375 | $0.615 | $1.015 |
-
-
-
-
-
-
-
-
-
-
-
-
-Video rates apply to output plus reference-video duration.
-
-#### Per 1M tokens by resolution
-
-| Name | Model ID | Input type | 480p USD | 720p USD | 1080p USD | 4K USD |
-| --- | --- | --- | ---: | ---: | ---: | ---: |
-| [Dreamina Seedance 2.0](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | `byteplus/dreamina-seedance-2-0-260128` | Image to video | $10.01 / 1M tokens | $10.01 / 1M tokens | $11.011 / 1M tokens | $5.72 / 1M tokens |
-| | | Text to video | $10.01 / 1M tokens | $10.01 / 1M tokens | $11.011 / 1M tokens | $5.72 / 1M tokens |
-| | | Video to video | $6.149 / 1M tokens | $6.149 / 1M tokens | $6.721 / 1M tokens | $3.432 / 1M tokens |
-| [Dreamina Seedance 2.5](/development/comfy-router/models/byteplus/dreamina-seedance-2-5-260628/code) | `byteplus/dreamina-seedance-2-5-260628` | Image to video | $15.301 / 1M tokens | $15.301 / 1M tokens | $16.731 / 1M tokens | - |
-| | | Text to video | $15.301 / 1M tokens | $15.301 / 1M tokens | $16.731 / 1M tokens | - |
-| | | Video to video | $9.152 / 1M tokens | $9.152 / 1M tokens | $10.01 / 1M tokens | - |
-
-#### Per second by resolution
-
-| Name | Model ID | 480p USD | 720p USD | 1080p USD | 1440p USD | 4K USD |
-| --- | --- | ---: | ---: | ---: | ---: | ---: |
-| [HappyHorse 1.0 I2V](/development/comfy-router/models/wan/happyhorse-1-0-i2v/code) | `wan/happyhorse-1.0-i2v` | - | $0.14 / s | $0.24 / s | - | - |
-| [HappyHorse 1.0 R2V](/development/comfy-router/models/wan/happyhorse-1-0-r2v/code) | `wan/happyhorse-1.0-r2v` | - | $0.14 / s | $0.24 / s | - | - |
-| [HappyHorse 1.0 T2V](/development/comfy-router/models/wan/happyhorse-1-0-t2v/code) | `wan/happyhorse-1.0-t2v` | - | $0.14 / s | $0.24 / s | - | - |
-| [HappyHorse 1.0 Video Edit](/development/comfy-router/models/wan/happyhorse-1-0-video-edit/code) | `wan/happyhorse-1.0-video-edit` | - | $0.14 / s | $0.24 / s | - | - |
-| [HappyHorse 1.1 I2V](/development/comfy-router/models/wan/happyhorse-1-1-i2v/code) | `wan/happyhorse-1.1-i2v` | - | $0.2002 / s | $0.2574 / s | - | - |
-| [HappyHorse 1.1 R2V](/development/comfy-router/models/wan/happyhorse-1-1-r2v/code) | `wan/happyhorse-1.1-r2v` | - | $0.2002 / s | $0.2574 / s | - | - |
-| [HappyHorse 1.1 T2V](/development/comfy-router/models/wan/happyhorse-1-1-t2v/code) | `wan/happyhorse-1.1-t2v` | - | $0.2002 / s | $0.2574 / s | - | - |
-| [LTX 2.5 Fast](/development/comfy-router/models/ltx/ltx-2-5-fast/code) | `ltx/ltx-2-5-fast` | - | $0.1287 / s | $0.1859 / s | $0.2717 / s | $0.429 / s |
-| [LTX 2.5 Pro](/development/comfy-router/models/ltx/ltx-2-5-pro/code) | `ltx/ltx-2-5-pro` | - | $0.1716 / s | $0.2431 / s | - | - |
-| [P Video 2 Standard](/development/comfy-router/models/pruna/p-video-2/code) | `pruna/p-video-2` | - | $0.03575 / s | $0.0715 / s | - | - |
-| [P Video 2 Draft](/development/comfy-router/models/pruna/p-video-2/code) | `pruna/p-video-2` | - | $0.02145 / s | $0.0429 / s | - | - |
-| [Wan 2.5 I2V Preview](/development/comfy-router/models/wan/wan2-5-i2v-preview/code) | `wan/wan2.5-i2v-preview` | $0.05 / s | $0.10 / s | $0.15 / s | - | - |
-| [Wan 2.5 T2V Preview](/development/comfy-router/models/wan/wan2-5-t2v-preview/code) | `wan/wan2.5-t2v-preview` | $0.05 / s | $0.10 / s | $0.15 / s | - | - |
-| [Wan 2.6 I2V](/development/comfy-router/models/wan/wan2-6-i2v/code) | `wan/wan2.6-i2v` | - | $0.10 / s | $0.15 / s | - | - |
-| [Wan 2.6 R2V](/development/comfy-router/models/wan/wan2-6-r2v/code) | `wan/wan2.6-r2v` | - | $0.10 / s | $0.15 / s | - | - |
-| [Wan 2.6 T2V](/development/comfy-router/models/wan/wan2-6-t2v/code) | `wan/wan2.6-t2v` | - | $0.10 / s | $0.15 / s | - | - |
-| [Wan 2.7 I2V](/development/comfy-router/models/wan/wan2-7-i2v/code) | `wan/wan2.7-i2v` | - | $0.10 / s | $0.15 / s | - | - |
-| [Wan 2.7 R2V](/development/comfy-router/models/wan/wan2-7-r2v/code) | `wan/wan2.7-r2v` | - | $0.10 / s | $0.15 / s | - | - |
-| [Wan 2.7 T2V](/development/comfy-router/models/wan/wan2-7-t2v/code) | `wan/wan2.7-t2v` | - | $0.10 / s | $0.15 / s | - | - |
-| [Wan 2.7 Video Edit](/development/comfy-router/models/wan/wan2-7-videoedit/code) | `wan/wan2.7-videoedit` | - | $0.10 / s | $0.15 / s | - | - |
-| [Wan 3.0 Video](/development/comfy-router/models/wan/wan3-0-video/code) | `wan/wan3.0-video` | $0.05 / s | $0.10 / s | $0.20 / s | - | - |
-| [Wan 3.0 Video Prime](/development/comfy-router/models/wan/wan3-0-video-prime/code) | `wan/wan3.0-video-prime` | $0.068 / s | $0.14 / s | $0.28 / s | - | - |
-
-| Name | Model ID | 768p USD | 2K USD |
-| --- | --- | ---: | ---: |
-| [MiniMax H3](/development/comfy-router/models/minimax/minimax-h3/code) | `minimax/minimax-h3` | $0.1287 / s | $0.1859 / s |
-
-| Name | Model ID | Audio | 720p USD | 1080p USD | 4K USD |
-| --- | --- | --- | ---: | ---: | ---: |
-| [Veo 3.1 Fast Generate 001](/development/comfy-router/models/veo/veo-3-1-fast-generate-001/code) | `veo/veo-3.1-fast-generate-001` | No audio | $0.08 / s | $0.10 / s | $0.25 / s |
-| | | Audio | $0.10 / s | $0.12 / s | $0.30 / s |
-| [Veo 3.1 Generate 001](/development/comfy-router/models/veo/veo-3-1-generate-001/code) | `veo/veo-3.1-generate-001` | No audio | $0.20 / s | $0.20 / s | $0.40 / s |
-| | | Audio | $0.40 / s | $0.40 / s | $0.60 / s |
-| [Veo 3.1 Lite Generate 001](/development/comfy-router/models/veo/veo-3-1-lite-generate-001/code) | `veo/veo-3.1-lite-generate-001` | No audio | $0.03 / s | $0.05 / s | - |
-| | | Audio | $0.05 / s | $0.08 / s | - |
-
-#### SwitchX image and video rates
-
-Images are billed per output image. Videos are billed per 30 output frames, rounded up.
-
-| Name | Model ID | 720p USD | 1080p USD |
-| --- | --- | ---: | ---: |
-| [SwitchX](/development/comfy-router/models/beeble/switchx/code) | `beeble/switchx` | Image: $0.143
Video: $0.143 | Image: $0.429
Video: $0.429 |
-
-#### Rates per second
-
-| Name | Model ID | USD |
-| --- | --- | ---: |
-| [Aleph 2](/development/comfy-router/models/runway/aleph2/code) | `runway/aleph2` | $0.4004 / s |
-| [Gen 4 Turbo](/development/comfy-router/models/runway/gen4-turbo/code) | `runway/gen4_turbo` | $0.0715 / s |
-| [Sync 3](/development/comfy-router/models/synclabs/sync-3/code) | `synclabs/sync-3` | $0.19019 / s |
-| [Erase](/development/comfy-router/models/bria/video-edit-erase/code) | `bria/video-edit-erase` | $0.06435 / s |
-| [Green Screen](/development/comfy-router/models/bria/video-edit-green-screen/code) | `bria/video-edit-green-screen` | $0.05 / s |
-| [Remove Background](/development/comfy-router/models/bria/video-edit-remove-background/code) | `bria/video-edit-remove-background` | $0.05 / s |
-| [Replace Background](/development/comfy-router/models/bria/video-edit-replace-background/code) | `bria/video-edit-replace-background` | $0.05 / s |
-
-#### Usage-based rates
-
-Comfy usage-based rates are billed per active GPU-second; total cost depends on runtime.
-
-| Name | Model ID | USD |
-| --- | --- | ---: |
-| [FLUX 3 Video](/development/comfy-router/models/black-forest-labs/flux-3-video/code) | `bfl/flux-3-video` | $0.00126066 / GPU-second |
-| [FLUX Video Upscale](/development/comfy-router/models/black-forest-labs/flux-video-upscale/code) | `bfl/video-upscale-v1` | $0.00126066 / GPU-second |
-| [Grok Imagine Video](/development/comfy-router/models/xai/grok-imagine-video/code) | `xai/grok-imagine-video` | $0.00126066 / GPU-second |
-| [Grok Imagine Video 1.5](/development/comfy-router/models/xai/grok-imagine-video-1-5/code) | `xai/grok-imagine-video-1.5` | $0.00126066 / GPU-second |
-| [Grok Imagine Video 1.5 Preview](/development/comfy-router/models/xai/grok-imagine-video-1-5-preview/code) | `xai/grok-imagine-video-1.5-preview` | $0.00126066 / GPU-second |
-| [Ray 2](/development/comfy-router/models/luma/ray-2/code) | `luma/ray-2` | $0.00126066 / GPU-second |
-| [Ray Flash 2](/development/comfy-router/models/luma/ray-flash-2/code) | `luma/ray-flash-2` | $0.00126066 / GPU-second |
-| [Video Edit V1](/development/comfy-router/models/black-forest-labs/video-edit-v1/code) | `bfl/video-edit-v1` | $0.00126066 / GPU-second |
-
-#### Not published
-
-| Name | Model ID | USD |
-| --- | --- | ---: |
-| [Grok Imagine Video 1.5 Lite](/development/comfy-router/models/xai/grok-imagine-video-1-5-lite/code) | `xai/grok-imagine-video-1.5-lite` | Not published |
-| [Video To Video Resize](/development/comfy-router/models/moonvalley/video-to-video-resize/code) | `moonvalley/video-to-video-resize` | Not published |
-
-#### Rates per Kling credit
-
-| Name | Model ID | USD |
-| --- | --- | ---: |
-| [Kling 3.0 Turbo](/development/comfy-router/models/kling/kling-3-0-turbo/code) | `kling/kling-3.0-turbo` | $0.14 / Kling credit |
-| [Kling V2.5 Turbo](/development/comfy-router/models/kling/kling-v2-5-turbo/code) | `kling/kling-v2-5-turbo` | $0.14 / Kling credit |
-| [Kling V2.6](/development/comfy-router/models/kling/kling-v2-6/code) | `kling/kling-v2-6` | $0.14 / Kling credit |
-| [Kling V3](/development/comfy-router/models/kling/kling-v3/code) | `kling/kling-v3` | $0.14 / Kling credit |
-| [Kling V3 Omni](/development/comfy-router/models/kling/kling-v3-omni/code) | `kling/kling-v3-omni` | $0.14 / Kling credit |
-| [Kling Video O1](/development/comfy-router/models/kling/kling-video-o1/code) | `kling/kling-video-o1` | $0.14 / Kling credit |
-| [Videos Avatar Image 2video](/development/comfy-router/models/kling/videos-avatar-image2video/code) | `kling/videos-avatar-image2video` | $0.14 / Kling credit |
-| [Videos Lip Sync](/development/comfy-router/models/kling/videos-lip-sync/code) | `kling/videos-lip-sync` | $0.14 / Kling credit |
-| [Videos Video Extend](/development/comfy-router/models/kling/videos-video-extend/code) | `kling/videos-video-extend` | $0.14 / Kling credit |
-
-#### Rates per extra reference image
-
-| Name | Model ID | Option | USD |
-| --- | --- | --- | ---: |
-| [MiniMax H3](/development/comfy-router/models/minimax/minimax-h3/code) | `minimax/minimax-h3` | References beyond 5 | $0.0572 / extra reference image |
-
-#### Rates per request
-
-| Name | Model ID | 5s USD / request | 10s USD / request |
-| --- | --- | ---: | ---: |
-| [Image To Video](/development/comfy-router/models/moonvalley/image-to-video/code) | `moonvalley/image-to-video` | $1.50 | $3.00 |
-| [Text To Video](/development/comfy-router/models/moonvalley/text-to-video/code) | `moonvalley/text-to-video` | $1.50 | $3.00 |
-| [Video To Video](/development/comfy-router/models/moonvalley/video-to-video/code) | `moonvalley/video-to-video` | $2.25 | $4.00 |
-
-#### Seedance video token rates
-
-| Name | Model ID | Image to video / Text to video USD / 1M tokens | Video to video USD / 1M tokens |
-| --- | --- | ---: | ---: |
-| [Dreamina Seedance 2.0 Fast](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-fast-260128/code) | `byteplus/dreamina-seedance-2-0-fast-260128` | $8.008 | $4.719 |
-| [Dreamina Seedance 2.0 Mini](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-mini/code) | `byteplus/dreamina-seedance-2-0-mini` | $5.005 | $3.003 |
-| [Seedance 1.0 Pro](/development/comfy-router/models/byteplus/seedance-1-0-pro-250528/code) | `byteplus/seedance-1-0-pro-250528` | $2.50 | - |
-| [Seedance 1.0 Pro Fast](/development/comfy-router/models/byteplus/seedance-1-0-pro-fast-251015/code) | `byteplus/seedance-1-0-pro-fast-251015` | $1.00 | - |
-
-#### Seedance 1.5 Pro audio rates
-
-| Name | Model ID | No audio USD / 1M tokens | Audio USD / 1M tokens |
-| --- | --- | ---: | ---: |
-| [Seedance 1.5 Pro](/development/comfy-router/models/byteplus/seedance-1-5-pro-251215/code) | `byteplus/seedance-1-5-pro-251215` | $1.20 | $2.40 |
-
-
-
-
-#### Per 1K video tokens by resolution
-
-| Name | Model ID | 480p USD | 720p USD | 1080p USD | 4K USD |
-| --- | --- | ---: | ---: | ---: | ---: |
-| [Dreamina Seedance 2.0](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | `byteplus/dreamina-seedance-2-0-260128` | $0.014 | $0.014 | $0.014 | $0.008 |
-| [Dreamina Seedance 2.5](/development/comfy-router/models/byteplus/dreamina-seedance-2-5-260628/code) | `byteplus/dreamina-seedance-2-5-260628` | $0.0214 | $0.0214 | $0.0234 | - |
-
-#### Rates per generation
-
-| Name | Model ID | USD / generation |
-| --- | --- | ---: |
-| [H3 Max](/development/comfy-router/models/fal/h3-max/code) | `fal/h3-max` | $0.0715 |
-| [H3 Max Turbo](/development/comfy-router/models/fal/h3-max-turbo/code) | `fal/h3-max-turbo` | $0.03575 |
-
-
-
-
-#### Per 1K video tokens by resolution
-
-| Name | Model ID | 480p USD | 720p USD |
-| --- | --- | ---: | ---: |
-| [Dreamina Seedance 2.0](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | `byteplus/dreamina-seedance-2-0-260128` | $0.014 | $0.014 |
-| [Dreamina Seedance 2.5](/development/comfy-router/models/byteplus/dreamina-seedance-2-5-260628/code) | `byteplus/dreamina-seedance-2-5-260628` | $0.0214 | $0.0214 |
-
-#### Per second by resolution
-
-| Name | Model ID | 480p USD | 720p USD | 1080p USD | 4K USD |
-| --- | --- | ---: | ---: | ---: | ---: |
-| [Kling 3.0 Turbo](/development/comfy-router/models/kling/kling-3-0-turbo/code) | `kling/kling-3.0-turbo` | - | $0.112 / s | $0.14 / s | - |
-| [Wan 3.0 Video](/development/comfy-router/models/wan/wan3-0-video/code) | `wan/wan3.0-video` | $0.05 / s | $0.10 / s | $0.20 / s | - |
-| [Higgsfield Kling 3.4k](/development/comfy-router/models/higgsfield/higgsfield-kling-3-4k/code) | `higgsfield/higgsfield-kling-3-4k` | - | - | - | $0.42 / s |
-
-#### Rates per second
-
-| Name | Model ID | USD |
-| --- | --- | ---: |
-| [Higgsfield Kling 3 Pro](/development/comfy-router/models/higgsfield/higgsfield-kling-3-pro/code) | `higgsfield/higgsfield-kling-3-pro` | $0.168 / s |
-| [Kling V3 Standard](/development/comfy-router/models/kling/kling-v3/code) | `kling/kling-v3` | $0.1262 / s |
-
-
-
-
-| Name | Model ID | USD |
-| --- | --- | ---: |
-| [Dreamina Seedance 2.0](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | `byteplus/dreamina-seedance-2-0-260128` | Usage-based
request usage |
-| [Dreamina Seedance 2.5](/development/comfy-router/models/byteplus/dreamina-seedance-2-5-260628/code) | `byteplus/dreamina-seedance-2-5-260628` | Usage-based
request usage |
-
-
-
-
-#### Rates per request
-
-| Name | Model ID | USD / request |
-| --- | --- | ---: |
-| [Dreamina Seedance 2.0](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | `byteplus/dreamina-seedance-2-0-260128` | $0.60 |
-| [Dreamina Seedance 2.5](/development/comfy-router/models/byteplus/dreamina-seedance-2-5-260628/code) | `byteplus/dreamina-seedance-2-5-260628` | $0.90 |
-
-#### Per 5 s by resolution
-
-| Name | Model ID | 720p USD | 1080p USD | 2K USD | 4K USD |
-| --- | --- | ---: | ---: | ---: | ---: |
-| [Flashvsr](/development/comfy-router/models/wavespeed/flashvsr/code) | `wavespeed/flashvsr` | $0.06 / 5 s | $0.09 / 5 s | $0.12 / 5 s | $0.16 / 5 s |
-
-
-
-
-
-
-
-
-
-
-
-
-#### Token rates
-
-| Name | Model ID | Cached input USD / 1M tokens | Input USD / 1M tokens | Output USD / 1M tokens |
-| --- | --- | ---: | ---: | ---: |
-| [Claude Fable 5.1](/development/comfy-router/models/anthropic/claude-fable-5-1/code) | `anthropic/claude-fable-5-1` | Read: $0.3575
Write (1h): $28.60
Write (5m): $17.875 | Text: $14.30 | Text: $71.50 |
-| [Claude Fable 5](/development/comfy-router/models/anthropic/claude-fable-5/code) | `anthropic/claude-fable-5` | Read: $1.43
Write (1h): $28.60
Write (5m): $17.875 | Text: $14.30 | Text: $71.50 |
-| [Claude Haiku 4.5](/development/comfy-router/models/anthropic/claude-haiku-4-5-20251001/code) | `anthropic/claude-haiku-4-5-20251001` | Read: $0.10
Write (1h): $2.00
Write (5m): $1.25 | Text: $1.00 | Text: $5.00 |
-| [Claude Opus 5.5](/development/comfy-router/models/anthropic/claude-opus-5-5/code) | `anthropic/claude-opus-5-5` | Read: $0.286
Write (1h): $11.44
Write (5m): $7.15 | Text: $5.72 | Text: $28.60 |
-| [Claude Opus 5](/development/comfy-router/models/anthropic/claude-opus-5/code) | `anthropic/claude-opus-5` | Read: $0.715
Write (1h): $14.30
Write (5m): $8.9375 | Text: $7.15 | Text: $35.75 |
-| [Claude Opus 4.8](/development/comfy-router/models/anthropic/claude-opus-4-8/code) | `anthropic/claude-opus-4-8` | Read: $0.715
Write (1h): $14.30
Write (5m): $8.9375 | Text: $7.15 | Text: $35.75 |
-| [Claude Opus 4.7](/development/comfy-router/models/anthropic/claude-opus-4-7/code) | `anthropic/claude-opus-4-7` | Read: $0.50
Write (1h): $10.00
Write (5m): $6.25 | Text: $5.00 | Text: $25.00 |
-| [Claude Opus 4.6](/development/comfy-router/models/anthropic/claude-opus-4-6/code) | `anthropic/claude-opus-4-6` | Read: $0.50
Write (1h): $10.00
Write (5m): $6.25 | Text: $5.00 | Text: $25.00 |
-| [Claude Sonnet 5.5](/development/comfy-router/models/anthropic/claude-sonnet-5-5/code) | `anthropic/claude-sonnet-5-5` | Read: $0.286
Write (1h): $5.72
Write (5m): $3.575 | Text: $2.86 | Text: $14.30 |
-| [Claude Sonnet 5](/development/comfy-router/models/anthropic/claude-sonnet-5/code) | `anthropic/claude-sonnet-5` | Read: $0.286
Write (1h): $5.72
Write (5m): $3.575 | Text: $2.86 | Text: $14.30 |
-| [Claude Sonnet 4.6](/development/comfy-router/models/anthropic/claude-sonnet-4-6/code) | `anthropic/claude-sonnet-4-6` | Read: $0.30
Write (1h): $6.00
Write (5m): $3.75 | Text: $3.00 | Text: $15.00 |
-| [Claude Sonnet 4.5](/development/comfy-router/models/anthropic/claude-sonnet-4-5-20250929/code) | `anthropic/claude-sonnet-4-5-20250929` | Read: $0.30
Write (1h): $6.00
Write (5m): $3.75 | Text: $3.00 | Text: $15.00 |
-| [Gemini 3.8 Flash](/development/comfy-router/models/google/gemini-3-8-flash/code) | `vertexai/gemini-3.8-flash` | - | Audio / Image / Text / Video: $2.145 | Text / Reasoning: $10.725 |
-| [Gemini 3.7 Flash](/development/comfy-router/models/google/gemini-3-7-flash/code) | `vertexai/gemini-3.7-flash` | - | Audio / Image / Text / Video: $2.145 | Text / Reasoning: $10.725 |
-| [Gemini 3.1 Flash Lite](/development/comfy-router/models/google/gemini-3-1-flash-lite/code) | `vertexai/gemini-3.1-flash-lite` | - | Image / Text / Video: $0.25
Audio: $0.50 | Text / Reasoning: $1.50 |
-| [Gemini Omni 1.1 Flash](/development/comfy-router/models/gemini-interactions/gemini-omni-1-1-flash/code) | `gemini-interactions/gemini-omni-1.1-flash` | - | Audio / Image / Text / Video: $2.145 | Text / Reasoning: $12.87
Video: $25.025 |
-| [Gemini Omni Flash Preview](/development/comfy-router/models/gemini-interactions/gemini-omni-flash-preview/code) | `gemini-interactions/gemini-omni-flash-preview` | - | Audio / Image / Text / Video: $2.145 | Text / Reasoning: $12.87
Video: $25.025 |
-| [Google Gemini](/development/comfy-router/models/google/gemini/code) | `vertexai/gemini-2.5-flash` | - | Image / Text / Video: $0.30
Audio: $1.00 | Text: $2.50 |
-| [Google Gemini](/development/comfy-router/models/google/gemini/code) | `vertexai/gemini-2.5-pro` | - | Audio / Image / Text / Video: $1.25 | Text: $10.00 |
-| [Google Gemini](/development/comfy-router/models/google/gemini/code) | `vertexai/gemini-3.1-pro-preview` | - | Audio / Image / Text / Video: $2.00 | Text / Reasoning: $12.00
Image: $120.00 |
-| [Google Gemini](/development/comfy-router/models/google/gemini/code) | `vertexai/gemini-3.5-flash` | - | Audio / Image / Text / Video: $1.50 | Text / Reasoning: $9.00 |
-| [GPT 6 Astra](/development/comfy-router/models/openai/gpt-6-astra/code) | `openai/gpt-6-astra` | Read: $1.43
Write: $17.875 | Text: $14.30 | Text: $71.50 |
-| [GPT 6 Luna](/development/comfy-router/models/openai/gpt-6-luna/code) | `openai/gpt-6-luna` | Read: $0.0143
Write: $0.17875 | Text: $0.143 | Text: $0.715 |
-| [GPT 6 Sol](/development/comfy-router/models/openai/gpt-6-sol/code) | `openai/gpt-6-sol` | Read: $0.286
Write: $3.575 | Text: $2.86 | Text: $14.30 |
-| [GPT 5.6 Luna](/development/comfy-router/models/openai/gpt-5-6-luna/code) | `openai/gpt-5.6-luna` | Read: $0.10
Write: $1.25 | Text: $1.00 | Text: $6.00 |
-| [GPT 5.6 Sol](/development/comfy-router/models/openai/gpt-5-6-sol/code) | `openai/gpt-5.6-sol` | Read: $0.50
Write: $6.25 | Text: $5.00 | Text: $30.00 |
-| [GPT 5.6 Terra](/development/comfy-router/models/openai/gpt-5-6-terra/code) | `openai/gpt-5.6-terra` | Read: $0.25
Write: $3.125 | Text: $2.50 | Text: $15.00 |
-| [GPT 5.5](/development/comfy-router/models/openai/gpt-5-5/code) | `openai/gpt-5.5` | $0.50 | Text: $5.00 | Text: $30.00 |
-| [GPT 5.5 Pro](/development/comfy-router/models/openai/gpt-5-5-pro/code) | `openai/gpt-5.5-pro` | - | Text: $30.00 | Text: $180.00 |
-| [GPT 5](/development/comfy-router/models/openai/gpt-5/code) | `openai/gpt-5` | $0.125 | Text: $1.25 | Text: $10.00 |
-| [GPT 5 Mini](/development/comfy-router/models/openai/gpt-5-mini/code) | `openai/gpt-5-mini` | $0.025 | Text: $0.25 | Text: $2.00 |
-| [GPT 5 Nano](/development/comfy-router/models/openai/gpt-5-nano/code) | `openai/gpt-5-nano` | $0.005 | Text: $0.05 | Text: $0.40 |
-| [GPT 4.1](/development/comfy-router/models/openai/gpt-4-1/code) | `openai/gpt-4.1` | $0.50 | Text: $2.00 | Text: $8.00 |
-| [GPT 4.1 Mini](/development/comfy-router/models/openai/gpt-4-1-mini/code) | `openai/gpt-4.1-mini` | $0.10 | Text: $0.40 | Text: $1.60 |
-| [GPT 4.1 Nano](/development/comfy-router/models/openai/gpt-4-1-nano/code) | `openai/gpt-4.1-nano` | $0.025 | Text: $0.10 | Text: $0.40 |
-| [GPT 4o](/development/comfy-router/models/openai/gpt-4o/code) | `openai/gpt-4o` | $1.25 | Text: $2.50 | Text: $10.00 |
-| [o4-mini](/development/comfy-router/models/openai/o4-mini/code) | `openai/o4-mini` | $0.275 | Text: $1.10 | Text: $4.40 |
-| [o3](/development/comfy-router/models/openai/o3/code) | `openai/o3` | $0.50 | Text: $2.00 | Text: $8.00 |
-| [o1](/development/comfy-router/models/openai/o1/code) | `openai/o1` | $7.50 | Text: $15.00 | Text: $60.00 |
-| [o1-pro](/development/comfy-router/models/openai/o1-pro/code) | `openai/o1-pro` | - | Text: $150.00 | Text: $600.00 |
-| [Seed 2.0 Lite](/development/comfy-router/models/byteplus/seed-2-0-lite-260228/code) | `byteplus/seed-2-0-lite-260228` | $0.05 | Text: $0.25 | Text: $2.00 |
-| [Seed 2.0 Mini](/development/comfy-router/models/byteplus/seed-2-0-mini-260215/code) | `byteplus/seed-2-0-mini-260215` | $0.02 | Text: $0.10 | Text: $0.40 |
-| [Seed 2.0 Pro](/development/comfy-router/models/byteplus/seed-2-0-pro-260328/code) | `byteplus/seed-2-0-pro-260328` | $0.10 | Text: $0.50 | Text: $3.00 |
-
-#### Usage-based rates
-
-Comfy usage-based rates are billed per active GPU-second; total cost depends on runtime.
-
-| Name | Model ID | USD |
-| --- | --- | ---: |
-| [Chat Completions](/development/comfy-router/models/openrouter/chat-completions/code) | `openrouter/chat-completions` | $0.00126066 / GPU-second |
-
-#### Not published
-
-| Name | Model ID | USD |
-| --- | --- | ---: |
-| [Structured Instruction](/development/comfy-router/models/bria/structured-instruction/code) | `bria/structured-instruction` | Not published |
-
-
-
-
-
-
-
-
-
-
-
-
-#### Rates by duration
-
-| Name | Model ID | USD |
-| --- | --- | ---: |
-| [Eleven Sfx V2](/development/comfy-router/models/elevenlabs/eleven-sfx-v2/code) | `elevenlabs/eleven_sfx_v2` | $0.14 / minute |
-| [Seed Audio 1.0](/development/comfy-router/models/byteplus/seed-audio-1-0/code) | `byteplus/seed-audio-1.0` | $0.003575 / s |
-| [Seed Audio 1.0 Multilingual](/development/comfy-router/models/byteplus/seed-audio-1-0-multilingual/code) | `byteplus/seed-audio-1.0-multilingual` | $0.003575 / s |
-| [Starfish](/development/comfy-router/models/heygen/starfish/code) | `heygen/starfish` | $0.000954 / s |
-
-#### Rates per 1K characters
-
-| Name | Model ID | Option | USD |
-| --- | --- | --- | ---: |
-| [Eleven V3](/development/comfy-router/models/elevenlabs/eleven-v3/code) | `elevenlabs/eleven_v3` | Dialogue / Text to speech | $0.10 / 1K characters |
-
-#### Not published
-
-| Name | Model ID | USD |
-| --- | --- | ---: |
-| [Eleven V4](/development/comfy-router/models/elevenlabs/eleven-v4/code) | `elevenlabs/eleven_v4` | Not published |
-| [Eleven V4 Turbo](/development/comfy-router/models/elevenlabs/eleven-v4-turbo/code) | `elevenlabs/eleven_v4_turbo` | Not published |
-
-
-
-
-
-
-
-
-
-
-
-
-#### Rates per Meshy credit
-
-| Name | Model ID | USD |
-| --- | --- | ---: |
-| [Animations](/development/comfy-router/models/meshy/animations/code) | `meshy/animations` | $0.0572 / Meshy credit |
-| [Meshy 5](/development/comfy-router/models/meshy/meshy-5/code) | `meshy/meshy-5` | $0.0572 / Meshy credit |
-| [Meshy 6](/development/comfy-router/models/meshy/meshy-6/code) | `meshy/meshy-6` | $0.0572 / Meshy credit |
-| [Meshy 7](/development/comfy-router/models/meshy/meshy-7/code) | `meshy/meshy-7` | $0.0572 / Meshy credit |
-| [Meshy 7.1](/development/comfy-router/models/meshy/meshy-7-1/code) | `meshy/meshy-7.1` | $0.0572 / Meshy credit |
-| [Remesh](/development/comfy-router/models/meshy/remesh/code) | `meshy/remesh` | $0.0572 / Meshy credit |
-| [Rigging](/development/comfy-router/models/meshy/rigging/code) | `meshy/rigging` | $0.0572 / Meshy credit |
-
-#### Rates per operation
-
-| Name | Model ID | USD |
-| --- | --- | ---: |
-| [Hunyuan 3D Part](/development/comfy-router/models/tencent/hunyuan-3d-part/code) | `tencent/hunyuan-3d-part` | $0.60 / operation |
-| [Hunyuan 3D Texture Edit](/development/comfy-router/models/tencent/hunyuan-3d-texture-edit/code) | `tencent/hunyuan-3d-texture-edit` | $0.60 / operation |
-| [Hunyuan 3D Uv](/development/comfy-router/models/tencent/hunyuan-3d-uv/code) | `tencent/hunyuan-3d-uv` | $0.20 / operation |
-
-#### Usage-based rates
-
-Comfy usage-based rates are billed per active GPU-second; total cost depends on runtime.
-
-| Name | Model ID | USD |
-| --- | --- | ---: |
-| [Hunyuan 3D Smart Topology](/development/comfy-router/models/tencent/hunyuan-3d-smart-topology/code) | `tencent/hunyuan-3d-smart-topology` | $0.00126066 / GPU-second |
-
-
-
-
-
-
-
-
-Usage-based rates vary by request. Unpublished prices do not mean free usage.
-
-Prices updated: Oct 1, 2026.
+Saving your own Higgsfield key bills those generations to your Higgsfield account rather than your Comfy credits, through the same endpoint. Those runs carry no `X-Comfy-Credits-Used` header. See [Higgsfield with your own key](/development/comfy-router/higgsfield-byok).
diff --git a/development/comfy-router/providers.mdx b/development/comfy-router/providers.mdx
index c2ace261cb..ad816f7903 100644
--- a/development/comfy-router/providers.mdx
+++ b/development/comfy-router/providers.mdx
@@ -12,17 +12,17 @@ Every model uses `POST /v2/models/{provider}/{model}`. Comfy is the default prov
## Provider coverage
-| Model / provider | **Comfy (default)** | **fal** | **Higgsfield** | **Runware** | **WaveSpeed** |
-| --- | --- | --- | --- | --- | --- |
-| [Dreamina Seedance 2.0 260128](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | ✓ | ✓ | ✓ | ✓ | ✓ |
-| [Dreamina Seedance 2.5 260628](/development/comfy-router/models/byteplus/dreamina-seedance-2-5-260628/code) | ✓ | ✓ | ✓ | ✓ | ✓ |
-| [GPT Image 2](/development/comfy-router/models/openai/gpt-image-2/code) | ✓ | ✓ | - | ✓ | ✓ |
-| [GPT Image 2.5 Flare](/development/comfy-router/models/openai/gpt-image-2-5-flare/code) | ✓ | ✓ | - | ✓ | ✓ |
-| [GPT Image 2.5 Sunburst](/development/comfy-router/models/openai/gpt-image-2-5-sunburst/code) | ✓ | ✓ | - | ✓ | ✓ |
-| [Kling 3.0 Turbo](/development/comfy-router/models/kling/kling-3-0-turbo/code) | ✓ | - | ✓ | - | - |
-| [Kling V3](/development/comfy-router/models/kling/kling-v3/code) | ✓ | - | ✓ | - | - |
-| [Nano Banana 2](/development/comfy-router/models/google/nano-banana-2/code) | ✓ | ✓ | - | ✓ | ✓ |
-| [Nano Banana Pro](/development/comfy-router/models/google/nano-banana-pro/code) | ✓ | ✓ | - | ✓ | ✓ |
-| [Wan 3.0 Video](/development/comfy-router/models/wan/wan3-0-video/code) | ✓ | - | ✓ | - | - |
+| Model / provider | **Comfy (default)** | **fal** | **Higgsfield** | **Pika** | **Runware** | **WaveSpeed** |
+| --- | --- | --- | --- | --- | --- | --- |
+| [Dreamina Seedance 2.0 260128](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
+| [Dreamina Seedance 2.5 260628](/development/comfy-router/models/byteplus/dreamina-seedance-2-5-260628/code) | ✓ | ✓ | ✓ | - | ✓ | ✓ |
+| [GPT Image 2](/development/comfy-router/models/openai/gpt-image-2/code) | ✓ | ✓ | - | ✓ | ✓ | ✓ |
+| [GPT Image 2.5 Flare](/development/comfy-router/models/openai/gpt-image-2-5-flare/code) | ✓ | ✓ | - | - | ✓ | ✓ |
+| [GPT Image 2.5 Sunburst](/development/comfy-router/models/openai/gpt-image-2-5-sunburst/code) | ✓ | ✓ | - | - | ✓ | ✓ |
+| [Kling 3.0 Turbo](/development/comfy-router/models/kling/kling-3-0-turbo/code) | ✓ | - | ✓ | - | - | - |
+| [Kling V3](/development/comfy-router/models/kling/kling-v3/code) | ✓ | - | ✓ | - | - | - |
+| [Nano Banana 2](/development/comfy-router/models/google/nano-banana-2/code) | ✓ | ✓ | - | ✓ | ✓ | ✓ |
+| [Nano Banana Pro](/development/comfy-router/models/google/nano-banana-pro/code) | ✓ | ✓ | - | ✓ | ✓ | ✓ |
+| [Wan 3.0 Video](/development/comfy-router/models/wan/wan3-0-video/code) | ✓ | - | ✓ | - | - | - |
`strict_mode=false` (the default) translates the native request and response for the selected provider. Fields that cannot be translated are listed in the `X-Comfy-Router-Dropped-Params` response header.
diff --git a/development/comfy-router/quickstart.mdx b/development/comfy-router/quickstart.mdx
index aa35d0026c..03dcf90b4c 100644
--- a/development/comfy-router/quickstart.mdx
+++ b/development/comfy-router/quickstart.mdx
@@ -133,6 +133,53 @@ Comfy Router gives you one API for hosted image and video models at `https://api
+## Image-to-video and reference-to-video
+
+The same Seedance request takes image, video, and audio inputs. Add items to `content` and set each one's `role`. The roles you send decide the operation Router runs. Pricing follows the media kinds in `content`, not the roles: a request with any video input bills as video-to-video, otherwise one with any image bills as image-to-video, and a text-only request bills as text-to-video. Some models charge the same rate for text-to-video and image-to-video.
+
+| Operation | `content` items |
+|---|---|
+| Text-to-video | one `text` item |
+| Image-to-video | a `text` item plus an `image_url` item with `role: first_frame` |
+| First-and-last-frame | the above plus a second `image_url` item with `role: last_frame` |
+| Reference-to-video | a `text` item plus `image_url` items with `role: reference_image`, and on Seedance 2.0 and 2.5, `video_url` items with `role: reference_video` and `audio_url` items with `role: reference_audio` |
+
+An image `url` can be a public URL or a `data:image/;base64,` string. Submit, poll, and collect exactly as in the quickstart above. Only the body changes.
+
+```bash Image-to-video
+curl https://api.comfy.org/v2/models/byteplus/dreamina-seedance-2-5-260628/requests \
+ -H "X-API-Key: $COMFY_API_KEY" \
+ -H "Idempotency-Key: $(uuidgen)" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "content": [
+ {"type": "text", "text": "The fox turns and runs into the trees"},
+ {"type": "image_url", "image_url": {"url": "https://example.com/fox.png"}, "role": "first_frame"}
+ ],
+ "duration": 5,
+ "resolution": "720p"
+ }'
+```
+
+```bash Reference-to-video
+curl https://api.comfy.org/v2/models/byteplus/dreamina-seedance-2-5-260628/requests \
+ -H "X-API-Key: $COMFY_API_KEY" \
+ -H "Idempotency-Key: $(uuidgen)" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "content": [
+ {"type": "text", "text": "The character from the reference walks through a neon-lit market"},
+ {"type": "image_url", "image_url": {"url": "https://example.com/character.png"}, "role": "reference_image"},
+ {"type": "image_url", "image_url": {"url": "https://example.com/outfit.png"}, "role": "reference_image"}
+ ],
+ "duration": 5,
+ "ratio": "16:9",
+ "resolution": "720p"
+ }'
+```
+
+On Seedance 2.5, first-frame and first-and-last-frame requests take the output aspect ratio from the first frame. Leave `ratio` out or send `adaptive`, because a concrete ratio is refused with a `400`. The request schema is shared across the Seedance family and can list roles a model ignores, so check the `x-comfy-router-input-modalities` field in each model's [input schema](/development/comfy-router/models) for the input kinds that model accepts. For example, `reference_video` and `reference_audio` need a model that lists `video` or `audio` there.
+
## Next steps
diff --git a/development/comfy-router/reference.mdx b/development/comfy-router/reference.mdx
index 19c41aed77..bbc181280f 100644
--- a/development/comfy-router/reference.mdx
+++ b/development/comfy-router/reference.mdx
@@ -178,7 +178,7 @@ Run a model and receive its finished result in the same response.
`application/json` -- [`RouterModelInput`](#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.
+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, which may be metered on its own rate; for a Seedance video model, `content[].role` selects the operation (a first-frame image selects image-to-video, a reference image or clip selects reference-to-video). On native dispatch, Seedance billing follows the media kinds in `content`, not the roles: a request with any video input bills as video-to-video, otherwise one with any image bills as image-to-video, otherwise text-to-video, except that a final render from a `draft_task` item bills as the operation of the Draft it renders; some models charge the same rate for text-to-video and image-to-video. With `model_provider` selecting an alternate provider, each conditioned operation may be metered on that provider's own rate.
**Responses**
@@ -260,6 +260,86 @@ The partner model's native JSON input. Without `model_provider` the request runs
Body: [`RouterErrorResponse`](#routererrorresponse) -- Headers: `X-Comfy-Error-Type`, `X-Comfy-Request-Id`, `X-Comfy-Upstream-Status`, `Retry-After`
+### `POST /v2/models/{provider}/{model}/estimate`
+
+**Price a model call before running it, without running it.**
+
+Returns what Comfy will charge for running this body on this model, in US dollars, priced from the same rate card the charge is billed against. The body is validated against the model's input schema first, as a default run (no `strict_mode`, no `reject_unknown_fields`) validates it, so a body that fails the model's schema is the same `422` here as on the run; nothing is dispatched to a provider and nothing is charged. The answer is not a price lock: it is priced at the rates in force at `pricing_as_of`, and a run is charged at the rates in force when it is rated. While the feature is rolling out the route answers `403` with `error_type: not_enabled`.
+
+**Parameters**
+
+
+ Lowercase provider segment of the canonical `{provider}/{model}` model ID - the partner whose model is being run.
+
+ Type: [`RouterProviderSegment`](#routerprovidersegment) -- Alphanumeric slug, e.g. `anthropic`, Up to 64 characters
+
+
+
+ Lowercase model segment of the canonical `{provider}/{model}` model ID - the model to run within that provider.
+
+ Type: [`RouterModelSegment`](#routermodelsegment) -- Alphanumeric slug, e.g. `claude-opus-4-6`, Up to 128 characters
+
+
+
+ Selects an alternate provider to serve this model, instead of its current default.
+
+
+**Request body**
+
+`application/json` -- [`RouterModelInput`](#routermodelinput) (required)
+
+The partner model's native JSON input, identical to the body the synchronous route accepts for this model. It is validated against the model's own input schema as a default run validates it (this route takes no `strict_mode` or `reject_unknown_fields`), and its own fields (the output count, the operation an input image selects) decide what is priced.
+
+**Responses**
+
+
+ The quote.
+
+ Body: [`RouterCostEstimate`](#routercostestimate) -- Headers: `X-Comfy-Request-Id`
+
+
+
+ Invalid request. Check the error type and request body.
+
+ Body: [`RouterErrorResponse`](#routererrorresponse) -- Headers: `X-Comfy-Error-Type`, `X-Comfy-Request-Id`
+
+
+
+ Missing or invalid credentials.
+
+ Body: [`RouterErrorResponse`](#routererrorresponse) -- Headers: `X-Comfy-Error-Type`, `X-Comfy-Request-Id`
+
+
+
+ The request is not allowed for this caller or model.
+
+ Body: [`RouterErrorResponse`](#routererrorresponse) -- Headers: `X-Comfy-Error-Type`, `X-Comfy-Request-Id`
+
+
+
+ The model ID was not found.
+
+ Body: [`RouterErrorResponse`](#routererrorresponse) -- Headers: `X-Comfy-Error-Type`, `X-Comfy-Request-Id`
+
+
+
+ The request body is too large.
+
+ Body: [`RouterErrorResponse`](#routererrorresponse) -- Headers: `X-Comfy-Error-Type`, `X-Comfy-Request-Id`
+
+
+
+ The estimate's body was rejected against the model's schema, or named a different model than the path.
+
+ Body: [`RouterValidationErrorResponse`](#routervalidationerrorresponse) -- Headers: `X-Comfy-Error-Type`, `X-Comfy-Request-Id`
+
+
+
+ Router is temporarily unavailable. Retry with backoff.
+
+ Body: [`RouterErrorResponse`](#routererrorresponse) -- Headers: `X-Comfy-Error-Type`, `X-Comfy-Request-Id`, `Retry-After` (capacity refusals only)
+
+
### `GET /v2/models/{provider}/{model}/openapi.json`
**Read one partner model's input and output schemas as an OpenAPI document.**
@@ -374,7 +454,7 @@ Comfy Router's queued delivery mode. The request body is the same partner-native
`application/json` -- [`RouterModelInput`](#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`).
+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, which may be metered on its own rate; a Seedance first-frame image selects image-to-video and a reference image or clip selects reference-to-video). On native dispatch a Seedance request bills by the media kinds in `content`, not the roles: any video input bills as video-to-video, otherwise any image bills as image-to-video, otherwise text-to-video, except that a final render from a `draft_task` item bills as the operation of the Draft it renders, with some models charging the same rate for text-to-video and image-to-video; with `model_provider` selecting an alternate provider, each conditioned operation may be metered on that provider's own 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**
@@ -799,6 +879,40 @@ Raised by Router itself, before or around the call to the model.
Always `nosniff`, on every successful run of a Router model.
+## Pre-flight cost estimates
+
+`POST /v2/models/{provider}/{model}/estimate` prices a body without running it, and a queued submit (`POST /v2/models/{provider}/{model}/requests`) returns the same [`RouterCostEstimate`](#routercostestimate) as `estimate` on its `201`. Both are priced from the rate card Comfy bills the run against; a run on your own provider key is not billed by Comfy (see below). While the estimate is rolling out, the route answers `403` with `error_type: not_enabled` and the submit response omits `estimate`. The catalog still carries no prices.
+
+Read `source` before any amount. A missing figure never means free.
+
+| `source` | Carries | How to read it |
+| --- | --- | --- |
+| `exact` | `amount`, `amount_cents`, `credits` | The charge for this body at the rates in `pricing_as_of`. |
+| `estimated` | `min_amount`, `max_amount` and their `_cents` forms, no `amount` | The charge depends on a quantity the provider reports back, and the body bounds it; it is expected between the two bounds. |
+| `unknown` | `reason`, no figure | No price could be given before the run. Treat any other `source` value as `unknown`. |
+
+An `unknown` estimate's `reason` is one of:
+
+- `not_quotable`: Router cannot price this call before it runs, because it is billed on a quantity the provider reports back that the body does not bound, or the body cannot be priced as sent. Treat any unrecognized `reason` as `not_quotable`.
+- `unpriced_model`: the rate card does not price this request.
+- `byok` (queued submit only): the run uses your own provider key, so Comfy bills nothing for it.
+- `pricing_unavailable` (queued submit only): the rate card could not be read when the request was admitted. The submission is still accepted and the run is charged as usual.
+
+Where the estimate fails closed:
+
+- When a figure needs the rate card and the card is not loaded or is stale, `/estimate` answers `503` rather than a guess. The queued submit is never refused because of its estimate; it carries `source: unknown` with `reason: pricing_unavailable` instead. A call that cannot be priced before it runs at all (`not_quotable`), or a queued submit on your own provider key (`byok`), is answered without reading the card, so that `200` says nothing about whether the card is fresh.
+- `/estimate` validates the body against the model's own input schema as a default run does, so a body that fails the model's schema gets the same `422`, with the same `detail[]` shape, that the run would answer. Nothing is priced or dispatched.
+- An idempotent replay of a queued submit (`Idempotent-Replayed: true`) omits `estimate`, because the original quote is not stored.
+
+Limits of the estimate. A `200` figure is not a promise that the run is accepted, or that it is charged exactly that:
+
+- `/estimate` takes no `strict_mode` or `reject_unknown_fields`, so it checks the model's native contract only: a body a `strict_mode=true` run accepts in the alternate provider's own schema can get a `422` here, and an undeclared top-level field a `reject_unknown_fields=true` run would refuse is priced here.
+- `pricing_as_of` is when the rate card was fetched (or, when none was read, when the estimate was answered). It is not a price lock: the run is charged at the rates in force when it is rated.
+- `/estimate` quotes the leg `model_provider` names, or the native provider. It takes no `fallback_provider` or `strict_mode` parameter. A synchronous run whose fallback retry lands on another provider is charged at that provider's rate, which the estimate did not quote; `X-Comfy-Router-Fallback-Provider` on the run's response says it happened. A queued submit has no fallback, so its `estimate` quotes the leg that runs.
+- `/estimate` and a queued submit's `estimate` can price slightly different documents for the same body on a `model_provider` leg, or for a body that runs on a sibling operation. The submit's `estimate` is priced on the document the run sends.
+- `/estimate` always quotes the platform charge. A run on your own provider key is not billed by Comfy, but `/estimate` does not detect that and still returns a figure; only a queued submit answers `reason: byok`.
+- Dollars are rendered from unrounded cents, with up to 8 decimals, and a chargeable sub-cent `amount` is never shown as `0`. `credits` is `round(amount × 211, 2)`, the same conversion `X-Comfy-Credits-Used` applies to a charge, so `credits` can round to `0` for a very small non-zero price; budget on `amount` or `amount_cents`, not `credits`, when that matters. That header remains the post-hoc figure for a run; balances live on the Comfy platform.
+
## 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.
@@ -836,6 +950,76 @@ Whether a content-policy refusal is charged for this model. Treat an unknown val
Type: `string`
+### RouterCostEstimate
+
+What Comfy will charge for this call, priced before dispatch from the same rate card the charge is billed against, in US dollars. `source` says how much to trust the number: `exact` carries `amount`; `estimated` carries `min_amount` and `max_amount` and no `amount`; `unknown` carries neither and a `reason`. Never read an absent `amount` as free. Not a price lock: see `pricing_as_of`.
+
+**Fields**
+
+
+ `exact` | `estimated` | `unknown`; read any other value as `unknown`.
+
+
+
+ Always `USD`.
+
+
+
+ Dollars as a decimal string, e.g. "0.04", with up to 8 decimals and no exponent. A chargeable sub-cent price is never rendered as "0". Present only when `source` is `exact`.
+
+
+
+ The lower bound in dollars as a decimal string. Present only when `source` is `estimated`.
+
+
+
+ The upper bound in dollars as a decimal string. Present only when `source` is `estimated`.
+
+
+
+ The same figure in US cents, unrounded, for arithmetic. Present with `amount`.
+
+ double
+
+
+
+ `min_amount` in US cents, unrounded. Present with `min_amount`.
+
+ double
+
+
+
+ `max_amount` in US cents. Present with `max_amount`.
+
+ double
+
+
+
+ `amount` in Comfy credits using the platform conversion ($1 = 211 credits), rounded to 2 decimals - the figure `X-Comfy-Credits-Used` will carry for an `exact` quote.
+
+ double
+
+
+
+ The provider leg this quote is for. On a queued submit it is the leg that will run.
+
+
+
+ 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`](#routermodelid) -- Model ID, e.g. `anthropic/claude-opus-4-6`, Up to 193 characters
+
+
+
+ When the rate card this quote was priced from was fetched. The run is charged at the rates in force when it is rated, not at this instant. Whenever no rate card was read (`reason: pricing_unavailable`, `byok`, or a `not_quotable` call that cannot be priced from any card), this is when the quote was answered.
+
+ date-time
+
+
+
+ Present only when `source` is `unknown`: `not_quotable` (Router cannot price this call before it runs - its price depends on what the provider reports back, or the body does not bound a billed quantity), `unpriced_model` (the rate card does not price this request), `byok` (queued submit only: the run uses your own provider key, so Comfy bills nothing for it), or `pricing_unavailable` (queued submit only: the rate card could not be read when the request was admitted; this is transient, and the run is still charged as usual). Read any other value as `not_quotable`.
+
+
### RouterErrorResponse
Error body for authentication, access, model lookup, quota, and provider transport failures.
@@ -843,7 +1027,7 @@ Error body for authentication, access, model lookup, quota, and provider transpo
**Fields**
- 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.
+ Human-readable description of the failure, safe to surface to an end user. Not machine-parsed - branch on `error_type` instead. For `model_not_found` on a retired model, `detail` reads ` was retired on ; use ` when a successor is recorded and visible to the caller, or just ` was retired on ` when it is not, instead of the usual suggestion list. A successor named here is always a live catalog ID the call can be re-pointed at. A model whose sunset date is still in the future is not retired: it stays listed by `GET /v2/models` and stays runnable until that date. Like the rest of `detail`, this retirement wording is for display and carries no compatibility guarantee. Two documented exceptions to the not-machine-parsed rule: 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.
@@ -1102,6 +1286,12 @@ The half of `RouterQueueSubmitResponse` that is not the URL block: the new reque
Type: [`RouterQueuePosition`](#routerqueueposition) -- At least 0
+
+ The pre-flight cost estimate for this request, present on a fresh admission when the cost estimate is enabled for the caller. It quotes the provider leg this request was pinned to (the queue has no fallback loop, so `fallback_provider` never changes the leg that runs). When no figure can be given it is still present, with `source: unknown`; the submission never fails because of the estimate. Omitted on an idempotent replay (`Idempotent-Replayed: true`), because the original quote is not stored. It is not a price lock: the run is charged at the rates in force when it is rated.
+
+ Type: [`RouterCostEstimate`](#routercostestimate)
+
+
### 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.
diff --git a/docs.json b/docs.json
index 42a6536345..f9891471a6 100644
--- a/docs.json
+++ b/docs.json
@@ -3531,6 +3531,13 @@
"development/comfy-router/models/openrouter/chat-completions/code"
]
},
+ {
+ "group": "Pika",
+ "pages": [
+ "development/comfy-router/models/pika/pika-2-5-image-to-video/code",
+ "development/comfy-router/models/pika/pika-2-5-text-to-video/code"
+ ]
+ },
{
"group": "Pruna",
"pages": [
diff --git a/openapi-v2.yaml b/openapi-v2.yaml
index 43d30c03fb..be721c4382 100644
--- a/openapi-v2.yaml
+++ b/openapi-v2.yaml
@@ -454,7 +454,7 @@ paths:
description: Session bearer token for partner (API) nodes — the equivalent of `api_key_comfy_org` for a caller authenticated by session rather than by key.
metadata:
type: object
- description: 'Your own labels for the job, such as which of your customers it ran for. At most 16 pairs. A key is 1 to 40 characters from `A-Z a-z 0-9 _ - .`; a value is a string of at most 256 bytes in UTF-8 (bytes, not characters: 200 `é` is 400 bytes) with no control character (U+0000 to U+001F, tab and newline included, and U+007F to U+009F) and no bidirectional embedding, override or isolate (U+202A to U+202E, U+2066 to U+2069). Anything else is refused `422` `metadata_invalid` and no job is created: a bad key or value is named in the message and in `details.key`, while more than 16 pairs is refused with the count and no `details`. A `metadata` that is not an object is refused `400` `invalid_request`; `null` is the same as none. Stored with the job, returned on it, and filterable on `GET /api/v2/jobs`. Set once, here: nothing changes it later. Only jobs sent to a deployment keep it today: Comfy Cloud refuses a non-empty `metadata` with `422` `metadata_not_supported`, and runs a job with empty or absent `metadata` as before. The public demo deployment, which takes no credential, refuses it the same way, so an anonymous caller cannot label jobs in its owner''s workspace.'
+ description: 'Your own labels for the job, such as which of your customers it ran for. At most 16 pairs. A key is 1 to 40 characters from `A-Z a-z 0-9 _ - .`; a value is a string of at most 256 bytes in UTF-8 (bytes, not characters: 200 `é` is 400 bytes) with no control character (U+0000 to U+001F, tab and newline included, and U+007F to U+009F) and no bidirectional embedding, override or isolate (U+202A to U+202E, U+2066 to U+2069). Anything else is refused `422` `metadata_invalid` and no job is created: a bad key or value is named in the message and in `details.key`, while more than 16 pairs is refused with the count and no `details`. A `metadata` that is not an object is refused `400` `invalid_request`; `null` is the same as none. Stored with the job, returned on it, and filterable on `GET /api/v2/jobs`. Set once, here: nothing changes it later. Only jobs sent to a deployment keep it today: Comfy Cloud refuses a non-empty `metadata` with `422` `metadata_not_supported`, and runs a job with empty or absent `metadata` as before.'
maxProperties: 16
additionalProperties:
type: string
@@ -494,7 +494,7 @@ paths:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
'422':
- description: '`invalid_workflow` (with per-node details), `workflow_format_ui`, `missing_asset`, `idempotency_key_reuse`, `metadata_invalid` (for a bad key or value, the message and `details.key` name the key; for more than 16 pairs, the message gives the count and there are no `details`), or `metadata_not_supported` (a surface that keeps no job labels yet, or the public demo deployment).'
+ description: '`invalid_workflow` (with per-node details), `workflow_format_ui`, `missing_asset`, `idempotency_key_reuse`, `metadata_invalid` (for a bad key or value, the message and `details.key` name the key; for more than 16 pairs, the message gives the count and there are no `details`), or `metadata_not_supported` (a surface that keeps no job labels yet).'
content:
application/json:
schema:
@@ -599,11 +599,7 @@ paths:
'401':
$ref: '#/components/responses/Unauthorized'
'403':
- description: '`public_deployment_no_list`: the public demo deployment''s address serves no job list, whatever credential is sent; its owner lists its jobs at the workspace address. Also `sso_required`, as on every operation (see `Forbidden`).'
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ErrorEnvelope'
+ $ref: '#/components/responses/Forbidden'
'404':
description: '`not_found`: on a deployment''s address, no deployment there that the caller''s workspace owns.'
content:
@@ -728,17 +724,9 @@ paths:
capture logs at all, the job has not finished, the job predates log
- capture, the run was killed before the worker could report one,
-
- capture was attempted and failed, or the job ran on the public demo
-
- deployment, which captures and stores the log like every other
+ capture, the run was killed before the worker could report one, or
- serverless deployment but withholds it on read, because that surface
-
- takes callers with no credential and a job id would otherwise be the
-
- only thing between one anonymous caller and another''s run.
+ capture was attempted and failed.
Because a `204` never says which of those it is, do not branch on the
@@ -1017,7 +1005,7 @@ components:
schemas:
Asset:
type: object
- description: 'A user-owned record identified by a server-assigned UUID, backing an immutable blob whose content carries a server-computed blake3 hash. `hash` may be computed lazily: an asset record (and its retrievable bytes) can exist before its hash is filled in. On an output, `job_metadata` carries the labels its producing job was submitted with, so code holding only the asset knows which run it came from. It is absent for assets no job produced, for outputs of a job sent without labels, where the serving surface keeps no job labels, at the public demo deployment''s own address, where no credential is checked, and when the labels could not be read: so `job_id` without `job_metadata` does not prove the job had no labels. A create whose file path and bytes match an existing output answers with that output, so its `job_id` and `job_metadata` are the output''s.'
+ description: 'A user-owned record identified by a server-assigned UUID, backing an immutable blob whose content carries a server-computed blake3 hash. `hash` may be computed lazily: an asset record (and its retrievable bytes) can exist before its hash is filled in. On an output, `job_metadata` carries the labels its producing job was submitted with, so code holding only the asset knows which run it came from. It is absent for assets no job produced, for outputs of a job sent without labels, where the serving surface keeps no job labels, and when the labels could not be read: so `job_id` without `job_metadata` does not prove the job had no labels. A create whose file path and bytes match an existing output answers with that output, so its `job_id` and `job_metadata` are the output''s.'
required:
- id
- hash
@@ -1071,7 +1059,7 @@ components:
job_metadata:
allOf:
- $ref: '#/components/schemas/JobMetadata'
- description: 'The labels the producing job was submitted with, read through `job_id` on each read. Absent for assets no job produced, for outputs of a job sent without labels, where the serving surface keeps no job labels, at the public demo deployment''s own address, where no credential is checked, when the job is gone, and when the labels could not be read: so `job_id` without `job_metadata` does not prove the job had no labels.'
+ description: 'The labels the producing job was submitted with, read through `job_id` on each read. Absent for assets no job produced, for outputs of a job sent without labels, where the serving surface keeps no job labels, when the job is gone, and when the labels could not be read: so `job_id` without `job_metadata` does not prove the job had no labels.'
Job:
type: object
description: One execution of a workflow. Durable from creation until `expires_at`; `outputs` populates incrementally during execution.
@@ -1143,6 +1131,11 @@ components:
type: string
description: The release of the deployment's build that ran the job, which can differ from the release the deployment runs now. Absent where the serving surface does not report it.
example: 7c1e9a40-3b2d-4f6a-9e81-0c5d2a7b4f13
+ release_version:
+ type: integer
+ minimum: 1
+ description: The version of the release in `release_id`, the number its build cut it as. Absent where the serving surface does not report it or could not look it up at the time of the answer.
+ example: 3
metadata:
$ref: '#/components/schemas/JobMetadata'
JobMetadata:
@@ -1186,6 +1179,11 @@ components:
update_time:
type: string
format: date-time
+ release_version:
+ type: integer
+ minimum: 1
+ description: The version of the release that ran the job. Absent where the serving surface does not report it or could not look it up at the time of the answer.
+ example: 3
metadata:
$ref: '#/components/schemas/JobMetadata'
JobLogs:
@@ -1416,7 +1414,7 @@ components:
$ref: '#/components/schemas/JobNodeErrorReason'
JobNodeErrorReason:
type: object
- description: 'One problem found with a node. `type` is the code for it: ComfyUI''s (for example `value_not_in_list`, `value_bigger_than_max`, `dependency_cycle`), or the gateway''s `unknown_node_class` (a node class the deployment''s build does not contain). ComfyUI''s `missing_node_type` means the same as `unknown_node_class`, found after dispatch rather than at submit. For either, `message` ends naming the node pack that provides the class where the Comfy node registry knows one. `details` usually starts with the input it is about.'
+ description: 'One problem found with a node. `type` is the code for it: ComfyUI''s (for example `value_not_in_list`, `value_bigger_than_max`, `dependency_cycle`), or one of the gateway''s, sent where its submit checks are switched on: `unknown_node_class` (a node class the deployment''s build does not contain), `waits_for_browser` (a node the workflow would run whose class waits for a browser tab to answer while the job runs, which a deployment never has), `invalid_node` (the node is not a JSON object), `invalid_inputs` (its `inputs` is present but not an object, refused only where the deployment''s ComfyUI would fail such a node anyway, so no workflow that runs today is refused), or `invalid_class_type` (its `class_type` is missing or is not a non-empty string). ComfyUI''s `missing_node_type` means the same as `unknown_node_class`, found after dispatch rather than at submit. For either, `message` ends naming the node pack that provides the class where the Comfy node registry knows one. `details` usually starts with the input it is about.'
required:
- type
- message
@@ -1473,9 +1471,7 @@ components:
has no `details`), `metadata_not_supported` (422:
- this surface keeps no job labels yet, or the public demo deployment,
-
- which takes no credential), `invalid_metadata_filter` (400)
+ this surface keeps no job labels yet), `invalid_metadata_filter` (400)
and `invalid_cursor` (400). A surface that does not list jobs yet
@@ -1509,7 +1505,7 @@ components:
type: object
nullable: true
additionalProperties: true
- description: Machine-readable detail for the code. When it carries `node_errors`, that is keyed by node id and each value is a `JobNodeError`, the same shape as a job's `error.node_errors`, whether the refusal came at submit (for example `unknown_node_class`, a node class the deployment's build does not contain) or from ComfyUI after dispatch.
+ description: Machine-readable detail for the code. When it carries `node_errors`, that is keyed by node id and each value is a `JobNodeError`, the same shape as a job's `error.node_errors`, whether the refusal came at submit (for example `unknown_node_class`, a node class the deployment's build does not contain) or from ComfyUI after dispatch. With `unknown_node_class` errors it carries `unknown_node_classes`, the missing classes; with `waits_for_browser` errors, `browser_wait_node_classes`, the classes that wait for a browser. A submit refusal names at most fifty nodes. For `invalid_node`, `invalid_inputs` and `invalid_class_type`, when it found more than fifty, `node_errors_truncated` is true and `malformed_node_count` carries the full count; for `waits_for_browser`, `browser_wait_node_count` does. For `unknown_node_class` it names only nodes whose class is one of the at most ten listed in `unknown_node_classes`; when those nodes number more than fifty, `node_errors_truncated` is true and `unknown_node_count` is how many nodes have a listed class. Nodes whose class is past those ten are neither named nor counted.
example:
node_errors:
'12':
diff --git a/router-openapi.yaml b/router-openapi.yaml
index dadc9c1cd1..3e2c6b2f0d 100644
--- a/router-openapi.yaml
+++ b/router-openapi.yaml
@@ -96,7 +96,7 @@ paths:
- $ref: '#/components/parameters/RejectUnknownFields'
requestBody:
required: true
- description: '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.'
+ description: '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, which may be metered on its own rate; for a Seedance video model, `content[].role` selects the operation (a first-frame image selects image-to-video, a reference image or clip selects reference-to-video). On native dispatch, Seedance billing follows the media kinds in `content`, not the roles: a request with any video input bills as video-to-video, otherwise one with any image bills as image-to-video, otherwise text-to-video, except that a final render from a `draft_task` item bills as the operation of the Draft it renders; some models charge the same rate for text-to-video and image-to-video. With `model_provider` selecting an alternate provider, each conditioned operation may be metered on that provider''s own rate.'
content:
application/json:
schema:
@@ -155,6 +155,51 @@ paths:
$ref: '#/components/responses/RouterRequestUnavailable'
'504':
$ref: '#/components/responses/RouterDeadlineExceeded'
+ /v2/models/{provider}/{model}/estimate:
+ post:
+ summary: Price a model call before running it, without running it.
+ description: 'Returns what Comfy will charge for running this body on this model, in US dollars, priced from the same rate card the charge is billed against. The body is validated against the model''s input schema first, as a default run (no `strict_mode`, no `reject_unknown_fields`) validates it, so a body that fails the model''s schema is the same `422` here as on the run; nothing is dispatched to a provider and nothing is charged. The answer is not a price lock: it is priced at the rates in force at `pricing_as_of`, and a run is charged at the rates in force when it is rated. While the feature is rolling out the route answers `403` with `error_type: not_enabled`.'
+ operationId: estimateRouterModelCost
+ tags:
+ - Comfy Router
+ security:
+ - BearerAuth: []
+ - ApiKeyAuth: []
+ parameters:
+ - $ref: '#/components/parameters/RouterProvider'
+ - $ref: '#/components/parameters/RouterModel'
+ - $ref: '#/components/parameters/ModelProvider'
+ requestBody:
+ required: true
+ description: The partner model's native JSON input, identical to the body the synchronous route accepts for this model. It is validated against the model's own input schema as a default run validates it (this route takes no `strict_mode` or `reject_unknown_fields`), and its own fields (the output count, the operation an input image selects) decide what is priced.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/RouterModelInput'
+ responses:
+ '200':
+ description: 'The quote. Read `source` before reading any amount: only `exact` carries `amount`, and an `unknown` quote carries a `reason` instead of a figure.'
+ headers:
+ X-Comfy-Request-Id:
+ $ref: '#/components/headers/RouterRequestIdHeader'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/RouterCostEstimate'
+ '400':
+ $ref: '#/components/responses/RouterRequestError'
+ '401':
+ $ref: '#/components/responses/RouterRequestError'
+ '403':
+ $ref: '#/components/responses/RouterRequestError'
+ '404':
+ $ref: '#/components/responses/RouterRequestError'
+ '413':
+ $ref: '#/components/responses/RouterRequestError'
+ '422':
+ $ref: '#/components/responses/RouterEstimateValidationError'
+ '503':
+ $ref: '#/components/responses/RouterRequestUnavailable'
/v2/models/{provider}/{model}/openapi.json:
get:
summary: Read one partner model's input and output schemas as an OpenAPI document.
@@ -226,7 +271,7 @@ paths:
- $ref: '#/components/parameters/RejectUnknownFields'
requestBody:
required: true
- description: '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`).'
+ description: '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, which may be metered on its own rate; a Seedance first-frame image selects image-to-video and a reference image or clip selects reference-to-video). On native dispatch a Seedance request bills by the media kinds in `content`, not the roles: any video input bills as video-to-video, otherwise any image bills as image-to-video, otherwise text-to-video, except that a final render from a `draft_task` item bills as the operation of the Draft it renders, with some models charging the same rate for text-to-video and image-to-video; with `model_provider` selecting an alternate provider, each conditioned operation may be metered on that provider''s own 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`).'
content:
application/json:
schema:
@@ -409,13 +454,66 @@ components:
type: string
description: 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.
example: unknown
+ RouterCostEstimate:
+ type: object
+ description: 'What Comfy will charge for this call, priced before dispatch from the same rate card the charge is billed against, in US dollars. `source` says how much to trust the number: `exact` carries `amount`; `estimated` carries `min_amount` and `max_amount` and no `amount`; `unknown` carries neither and a `reason`. Never read an absent `amount` as free. Not a price lock: see `pricing_as_of`.'
+ required:
+ - source
+ - currency
+ - provider
+ - model
+ - pricing_as_of
+ properties:
+ source:
+ type: string
+ description: '`exact` | `estimated` | `unknown`; read any other value as `unknown`.'
+ currency:
+ type: string
+ description: Always `USD`.
+ amount:
+ type: string
+ description: Dollars as a decimal string, e.g. "0.04", with up to 8 decimals and no exponent. A chargeable sub-cent price is never rendered as "0". Present only when `source` is `exact`.
+ min_amount:
+ type: string
+ description: The lower bound in dollars as a decimal string. Present only when `source` is `estimated`.
+ max_amount:
+ type: string
+ description: The upper bound in dollars as a decimal string. Present only when `source` is `estimated`.
+ amount_cents:
+ type: number
+ format: double
+ description: The same figure in US cents, unrounded, for arithmetic. Present with `amount`.
+ min_amount_cents:
+ type: number
+ format: double
+ description: '`min_amount` in US cents, unrounded. Present with `min_amount`.'
+ max_amount_cents:
+ type: number
+ format: double
+ description: '`max_amount` in US cents. Present with `max_amount`.'
+ credits:
+ type: number
+ format: double
+ description: '`amount` in Comfy credits using the platform conversion ($1 = 211 credits), rounded to 2 decimals - the figure `X-Comfy-Credits-Used` will carry for an `exact` quote.'
+ provider:
+ type: string
+ description: The provider leg this quote is for. On a queued submit it is the leg that will run.
+ model:
+ $ref: '#/components/schemas/RouterModelId'
+ pricing_as_of:
+ type: string
+ format: date-time
+ description: 'When the rate card this quote was priced from was fetched. The run is charged at the rates in force when it is rated, not at this instant. Whenever no rate card was read (`reason: pricing_unavailable`, `byok`, or a `not_quotable` call that cannot be priced from any card), this is when the quote was answered.'
+ reason:
+ type: string
+ description: 'Present only when `source` is `unknown`: `not_quotable` (Router cannot price this call before it runs - its price depends on what the provider reports back, or the body does not bound a billed quantity), `unpriced_model` (the rate card does not price this request), `byok` (queued submit only: the run uses your own provider key, so Comfy bills nothing for it), or `pricing_unavailable` (queued submit only: the rate card could not be read when the request was admitted; this is transient, and the run is still charged as usual). Read any other value as `not_quotable`.'
RouterErrorResponse:
type: object
description: 'Router''s request-level error body: what is returned when the request never reached the model, or failed for a reason the model itself did not report - auth, quota, an unknown model ID, or provider transport. A model-level validation failure has its own shape, `RouterValidationErrorResponse`, because flattening a FastAPI `detail[]` array into this `detail` string would destroy the per-field granularity an SDK branches on.'
properties:
detail:
type: string
- description: Human-readable description of the failure, safe to surface to an end user. Not machine-parsed - branch on `error_type` instead.
+ description: 'Human-readable description of the failure, safe to surface to an end user. Not machine-parsed - branch on `error_type` instead. For `model_not_found` on a retired model, `detail` reads ` was retired on ; use ` when a successor is recorded and visible to the caller, or just ` was retired on ` when it is not, instead of the usual suggestion list. A successor named here is always a live catalog ID the call can be re-pointed at. A model whose sunset date is still in the future is not retired: it stays listed by `GET /v2/models` and stays runnable until that date. Like the rest of `detail`, this retirement wording is for display and carries no compatibility guarantee.'
error_type:
$ref: '#/components/schemas/RouterErrorType'
upstream_detail:
@@ -663,6 +761,10 @@ components:
$ref: '#/components/schemas/RouterQueueStatus'
queue_position:
$ref: '#/components/schemas/RouterQueuePosition'
+ estimate:
+ allOf:
+ - $ref: '#/components/schemas/RouterCostEstimate'
+ description: 'The pre-flight cost estimate for this request, present on a fresh admission when the cost estimate is enabled for the caller. It quotes the provider leg this request was pinned to (the queue has no fallback loop, so `fallback_provider` never changes the leg that runs). When no figure can be given it is still present, with `source: unknown`; the submission never fails because of the estimate. Omitted on an idempotent replay (`Idempotent-Replayed: true`), because the original quote is not stored. It is not a price lock: the run is charged at the rates in force when it is rated.'
required:
- request_id
- status
@@ -771,6 +873,17 @@ components:
application/json:
schema:
$ref: '#/components/schemas/RouterErrorResponse'
+ RouterEstimateValidationError:
+ description: 'The estimate''s body was rejected against the model''s schema, or named a different model than the path. The body is `RouterValidationErrorResponse`, the same FastAPI `detail[]` shape the run route''s `422` carries for the same body. Nothing was priced. An estimate reserves no `Idempotency-Key`, so there is nothing to release or replay: correct the body and re-send.'
+ headers:
+ X-Comfy-Error-Type:
+ $ref: '#/components/headers/RouterErrorTypeHeader'
+ X-Comfy-Request-Id:
+ $ref: '#/components/headers/RouterRequestIdHeader'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/RouterValidationErrorResponse'
RouterIdempotencyConflict:
description: 'This request cannot be served as sent, and on `POST /v2/models/{provider}/{model}` three unrelated conditions answer this status. The first, which every route declaring this status can raise: the `Idempotency-Key` on this request is already held, and this request cannot be answered from its record. Two buckets share the status there and `X-Comfy-Error-Type` is what separates them, because they are acted on in opposite ways. `concurrency_limit_exceeded` means the original call for this key is still running: wait `Retry-After` seconds and re-send the same key, which collects that call''s result rather than starting a second one. `invalid_input` means the key cannot serve this request at all - it was already used for a different request (the method, the path and query, or the body differ from the original), or the original completed (and, if it succeeded, was charged) and Router holds no copy of its response it can still stand behind - for example it was too large to store, or it names an asset Comfy does not host and so cannot promise still resolves, which on a direct-return model is replayed for a few minutes after the original call and refused after that - or the copy it holds is content-encoded in a way this request did not accept - and the answer for the key is always a new key, never a re-send of this one. There is no `Retry-After` on any of these, because waiting changes nothing. `detail` says which case it is; the different-request case says nothing about how the call that does own the key turned out. The second and third conditions are raised only by `POST /v2/models/{provider}/{model}`, are not about the `Idempotency-Key` at all, and are both reachable on a request that carries no key. Router checks the third before the second, so a request that would trip both is refused for the third: an explicit `model_provider` naming an alternate provider on a request whose body selects a multipart operation (an edit, for example gpt-image''s `image` field), when that provider''s translator for this model cannot itself serve the operation - the swap is refused rather than silently billing a plain generation for the edit the caller actually asked for. A leg whose translator does carry the media is not refused and proceeds normally, so this refusal is per-leg rather than blanket. This one is not raised by the automatic on-failure retry `fallback_provider` controls, which is simply skipped (inert, not refused) on any multipart body, whether or not a leg could have served it - see the `model_provider` parameter. Its `detail` begins "this request''s `image` selects the edit operation". The second: `model_provider` named an alternate provider on a request that had already resolved a bring-your-own-key credential for the provider in the path. The three are mutually exclusive - the multipart case is decided from the request body and the named leg''s own translator, before BYOK is even considered - and the BYOK case is exclusive with the key case because that credential was resolved for the path''s provider while every alternate leg dispatches on Comfy''s own key for its own provider, so the call is refused before anything is dispatched and nothing is charged. Both the second and third carry `invalid_input`, the same bucket as the terminal key case above, so `X-Comfy-Error-Type` does not separate any of the three and `detail` is what a client branches on: the BYOK case begins `this request resolved a BYOK credential`, and a new key does not help - drop `model_provider`, or send the request without the BYOK credential. See the `model_provider` parameter, which also records that `fallback_provider` is inert rather than refused on a BYOK request. The body is `RouterErrorResponse` and the bucket is repeated on `X-Comfy-Error-Type`.'
headers:
diff --git a/router-schemas/byteplus/dreamina-seedance-2-0-260128.json b/router-schemas/byteplus/dreamina-seedance-2-0-260128.json
index 74b138c529..db990ab3db 100644
--- a/router-schemas/byteplus/dreamina-seedance-2-0-260128.json
+++ b/router-schemas/byteplus/dreamina-seedance-2-0-260128.json
@@ -1 +1 @@
-{"openapi":"3.0.2","info":{"title":"byteplus/dreamina-seedance-2-0-260128","description":"The request body Comfy Router accepts for the model \"byteplus/dreamina-seedance-2-0-260128\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"ea6dda283563"},"paths":{"/v2/models/byteplus/dreamina-seedance-2-0-260128":{"post":{"operationId":"runRouterModel","summary":"Run byteplus/dreamina-seedance-2-0-260128 synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"description":"Request body for a BytePlus Seedance / Dreamina video generation task. The `example` below is the COMFY ROUTER form and deliberately omits `model`, which Router fills from the `{model}` path segment; a direct v1 call to POST /proxy/byteplus/api/v3/contents/generations/tasks must add `model` to it. This text-to-video example omits the `content` item with `type: image_url` and an `image_url.url` that an image-to-video call supplies; text-only models must omit that image item.\n`content` is the whole `required` floor, and it is the floor the v1 surface already published and videoGenerationProxy's Rewrite already refuses an empty value for. Everything else BytePlus accepts varies by model — the per-model resolutions, durations, ratios and input kinds are documented on the individual fields — so the enums and bounds below are the union BytePlus admits across the family and BytePlus stays the authority on which member accepts which value.","example":{"content":[{"text":"A red fox trotting through a snowy pine forest","type":"text"}],"duration":5,"ratio":"16:9","resolution":"720p"},"properties":{"callback_url":{"description":"Callback notification address for the result of this generation task","format":"uri","type":"string"},"content":{"description":"The input content for the model to generate a video","items":{"$ref":"#/components/schemas/BytePlusVideoGenerationContent"},"minItems":1,"type":"array"},"draft":{"default":false,"description":"Seedance 2.5 only. Generates a 480p Draft video; resolution must be 480p.\nPass the returned task ID in a `draft_task` content item to render the final 1080p video. The Draft task ID stays valid for 7 days.\n","type":"boolean"},"duration":{"anyOf":[{"enum":[-1]},{"maximum":30,"minimum":2}],"description":"Video duration in seconds. Seedance 2.5: [4,30] or -1 (auto; video editing tasks support only -1). Seedance 2.0, 2.0 fast \u0026 2.0 mini: [4,15] or -1 (auto). Seedance 1.5 pro: [4,12] or -1. Seedance 1.0: [2,12].\n","type":"integer"},"execution_expires_after":{"description":"Task timeout threshold in seconds. Default 172800 (48h). Range: [3600, 259200].\n","maximum":259200,"minimum":3600,"type":"integer"},"generate_audio":{"default":true,"description":"Supported by Seedance 2.5, 2.0, 2.0 fast, 2.0 mini, and 1.5 pro. Whether the generated video includes audio synchronized with the visuals.\ntrue: The model outputs a video with synchronized audio.\nfalse: The model outputs a silent video.\nOn models that support it, the generated audio track is content-checked by the provider separately from the video; a refusal there is reported as `content_policy_violation` and, because the audio differs each run, can be intermittent for an input the provider otherwise accepts. Send `false` for a silent video if that happens; this helps only when the audio was the refused modality, which a synchronous Router response names as `refusal_subject: output_audio` (a queued request does not identify it yet). A Seedance 2.5 final render inherits `generate_audio` from its Draft task, so recreate the Draft with `false` instead.\n","type":"boolean"},"model":{"description":"The ID of the model to call. Supported models: seedance-1-5-pro-251215, seedance-1-0-pro-250528, seedance-1-0-pro-fast-251015, dreamina-seedance-2-0-260128, dreamina-seedance-2-0-fast-260128, dreamina-seedance-2-0-mini and dreamina-seedance-2-5-260628. A direct v1 call to POST /proxy/byteplus/api/v3/contents/generations/tasks MUST supply it — the proxy refuses any other value, and an omitted one, with a 400. It is NOT in this schema's `required` list because Comfy Router fills it from the `{model}` path segment of /v2/models/byteplus/{model}, so a Router caller omits it.","nullable":true,"type":"string"},"output_format":{"default":"mp4","description":"Seedance 2.5 only. Container format of the output video.\nmp4: General-purpose container (H.264/AAC, yuv420p) with broad compatibility and smaller file size.\nmov: Professional container (H.264 High 4:4:4 Predictive/PCM, yuv444p) with high color precision, suited for post-production; larger file size.\n","enum":["mp4","mov"],"type":"string"},"ratio":{"description":"Aspect ratio of the generated video. Seedance 2.0, 2.0 fast \u0026 2.0 mini, 1.5 pro default: adaptive.\nSeedance 2.5 first-frame / first-last-frame generation: the output follows the first frame's aspect ratio, so only `adaptive` (or omitting the field) is accepted; a concrete ratio is refused with a 400 before dispatch. Seedance 2.0 accepts a concrete ratio in those modes.\n","enum":["16:9","4:3","1:1","3:4","9:16","21:9","9:21","adaptive"],"type":"string"},"resolution":{"description":"Video resolution. Seedance 2.5, 2.0, 2.0 fast \u0026 2.0 mini, 1.5 pro, 1.0 lite default: 720p. Seedance 1.0 pro \u0026 pro-fast default: 1080p.\nNote: Seedance 2.0, 2.0 fast \u0026 2.0 mini do not support 1080p. Seedance 2.5 supports 480p, 720p, and 1080p.\n","enum":["480p","720p","1080p","4k"],"type":"string"},"return_last_frame":{"default":false,"description":"Whether to return the last frame image of the generated video.\ntrue: Returns the last frame image of the generated video. After setting this parameter to true, you can obtain the last frame image by calling the Querying the information about a video generation task. The last frame image is in PNG format, with its pixel width and height consistent with those of the generated video, and it contains no watermarks. Using this parameter allows the generation of multiple consecutive videos: the last frame of the previously generated video is used as the first frame of the next video task, enabling quick generation of multiple consecutive videos.\nfalse: Does not return the last frame image of the generated video.\n","type":"boolean"},"seed":{"description":"Seed integer for controlling randomness. Range: [-1, 2^32-1]. -1 uses a random seed.\n","maximum":4294967295,"minimum":-1,"type":"integer"},"service_tier":{"description":"Service tier for processing. Seedance 2.5, 2.0 \u0026 2.0 fast do not support flex (offline inference).\n","enum":["default","flex"],"type":"string"},"watermark":{"default":false,"description":"Whether the generated video includes a watermark.","type":"boolean"}},"required":["content"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/BytePlusVideoGenerationQueryResponse"}],"description":"Comfy Router output schema for the BytePlus Seedance video models: the terminal task-query document, forwarded unchanged EXCEPT for the two re-hosted `content` URLs described below - every other field is BytePlus's own. The operation is submit-and-poll and Router polls on the caller's behalf, so the body a caller receives is the finished task (`status: succeeded`) rather than the task handle the underlying submit returns.\nThe generated video's download URL is at `content.video_url`, present once the task succeeds. A failed task carries `error.code` and `error.message` instead; `status` is BytePlus's own vocabulary (queued, running, cancelled, succeeded, failed, expired), forwarded unchanged.\nThat URL is RE-HOSTED: Router copies the finished video onto Comfy storage and answers a Comfy-signed URL valid for up to 24 hours in place of BytePlus's own, which BytePlus clears 24 hours after the task and caps at 100 downloads on some models. Exactly two leaves are rewritten this way - `content.video_url` and `content.last_frame_url`, both modelled here. Any OTHER asset URL a task carries keeps BytePlus's own link and is NOT flagged as non-durable, so a stored or replayed document can still hand back a link that BytePlus has since cleared.\nDurability is per leaf, not per response: a leaf whose re-host could not be performed keeps BytePlus's own URL rather than a Comfy one, and the other is unaffected - so a caller that stores or replays this document should not assume every URL in it outlives the call. 24 hours is the CEILING, not a guarantee: the Comfy link is signed for 24 hours from the moment it is minted, and Router memoises it for 23 hours, so a later poll or an `Idempotency-Key` replay can hand back a link with as little as an hour left. Either way the link expires, so download the asset rather than storing it.","example":{"content":{"last_frame_url":"https://example.invalid/byteplus/seedance-1-0-pro-250528/last-frame","video_url":"https://example.invalid/byteplus/seedance-1-0-pro-250528/generated.mp4"},"created_at":1767225600,"duration":5,"error":null,"id":"3f7a1b28-5c0d-4e91-8a6f-1b2c3d4e5f60","model":"seedance-1-0-pro-250528","output_format":"mp4","resolution":"1080p","seed":1234567890123,"status":"succeeded","updated_at":1767225730}}}}}}}}},"components":{"schemas":{"BytePlusErrorCode":{"description":"Upstream ModelArk error code. SensitiveContentDetected, InputTextSensitiveContentDetected, InputImageSensitiveContentDetected, InputVideoSensitiveContentDetected, InputAudioSensitiveContentDetected, OutputTextSensitiveContentDetected, OutputImageSensitiveContentDetected, OutputVideoSensitiveContentDetected and OutputAudioSensitiveContentDetected identify content-policy refusals. A family may have a dot-separated reason, such as InputImageSensitiveContentDetected.PrivacyInformation, OutputVideoSensitiveContentDetected.PolicyViolation or OutputImageSensitiveContentDetected.DeepFake. This is an open string, not an enum: other codes describe validation and provider failures. Router recognizes policy families in HTTP 400 error envelopes and HTTP 200 failed-task responses without overriding transport failures.","externalDocs":{"url":"https://docs.byteplus.com/en/docs/ModelArk/1299023"},"type":"string"},"BytePlusVideoGenerationContent":{"oneOf":[{"properties":{"type":{"enum":["text"]}},"required":["text"]},{"properties":{"type":{"enum":["image_url"]}},"required":["image_url"]},{"properties":{"type":{"enum":["video_url"]}},"required":["video_url"]},{"properties":{"type":{"enum":["audio_url"]}},"required":["audio_url"]},{"properties":{"type":{"enum":["draft_task"]}},"required":["draft_task"]}],"properties":{"audio_url":{"description":"Input audio object. Only Seedance 2.5, 2.0, 2.0 fast \u0026 2.0 mini support audio input. Seedance 2.0, 2.0 fast \u0026 2.0 mini cannot use audio alone - they must include at least 1 image or video; Seedance 2.5 supports audio-only input.","properties":{"url":{"description":"Audio URL, Base64 encoding, or Asset ID.\nAudio URL: Public URL of the audio (wav, mp3).\nBase64: Format data:audio/\u003cformat\u003e;base64,\u003ccontent\u003e\nAsset ID: Format asset://\u003cASSET_ID\u003e\n","type":"string"}},"required":["url"],"type":"object"},"draft_task":{"description":"Seedance 2.5 only. The Draft task to render as the final video. It must be the only content item.\nThe final video reuses the Draft task's prompt, input assets, duration, ratio, seed, generate_audio and omni_reference_task_type; do not send them again. resolution defaults to and only supports 1080p.\n","properties":{"id":{"description":"The task ID returned when the Draft video was created with `draft` set to true.","type":"string"}},"required":["id"],"type":"object"},"image_url":{"properties":{"url":{"description":"Image content for image-to-video generation (when type is \"image_url\")\nImage URL: Make sure that the image URL is accessible.\nBase64-encoded content: Format must be data:image/\u003cformat\u003e;base64,\u003ccontent\u003e\nAsset ID: Format asset://\u003cASSET_ID\u003e\n","type":"string"}},"required":["url"],"type":"object"},"role":{"description":"The role/position of the content item.\nFor images: first_frame, last_frame, or reference_image.\nFor videos: reference_video (Seedance 2.5, 2.0, 2.0 fast \u0026 2.0 mini only).\nFor audio: reference_audio (Seedance 2.5, 2.0, 2.0 fast \u0026 2.0 mini only).\n","enum":["first_frame","last_frame","reference_image","reference_video","reference_audio"],"type":"string"},"text":{"description":"The input text information for the model. Includes text prompt and optional parameters.\n\nText prompt (required): Description of the video to be generated using Chinese and English characters.\n\nParameters (optional): Add --[parameters] after the text prompt to control video specifications:\n- --resolution (--rs): 480p, 720p, 1080p (default: 720p)\n- --ratio (--rt): 21:9, 16:9, 4:3, 1:1, 3:4, 9:16, 9:21, adaptive (default: 16:9 or adaptive)\n- --duration (--dur): 3-12 seconds (default: 5)\n- --framepersecond (--fps): 24 (default: 24)\n- --watermark (--wm): true/false (default: false)\n- --seed (--seed): -1 to 2^32-1 (default: -1)\n- --camerafixed (--cf): true/false (default: false)\n\nExample: \"A beautiful landscape --ratio 16:9 --resolution 720p --duration 5\"\n\nComfy-side guardrail, not a BytePlus constraint. BytePlus publishes no\ntext-length limit and accepted 40,000 characters under test (2026-09-17).\nIt counts characters, not bytes, so a multi-byte prompt may be several\ntimes this size on the wire. It bounds this one field, not the request:\n`content` is an array, and the per-request body limit is what bounds the\nwhole document. Comfy Router (/v2/models/byteplus/{model}) is the surface\nthat enforces it; on a direct v1 /proxy call BytePlus's own validator\nanswers instead. Set far above real traffic so it never adjudicates a\ngenuine prompt — raise it if a caller needs more.\n","maxLength":100000,"type":"string"},"type":{"description":"The type of the input content","enum":["text","image_url","video_url","audio_url","draft_task"],"type":"string"},"video_url":{"description":"Input video object. Only Seedance 2.5, 2.0, 2.0 fast \u0026 2.0 mini support video input.","properties":{"url":{"description":"Video URL or Asset ID.\nVideo URL: Public URL of the video (mp4, mov).\nAsset ID: Format asset://\u003cASSET_ID\u003e\n","type":"string"}},"required":["url"],"type":"object"}},"required":["type"],"type":"object"},"BytePlusVideoGenerationQueryResponse":{"properties":{"content":{"description":"The output after the video generation task is completed, which contains the download URL of the output video and, when BytePlus returns one, the download URL of its last frame. Both `video_url` and `last_frame_url` are RE-HOSTED onto Comfy storage; every other field here is BytePlus's own. Nullable - BytePlus clears the URLs 24 hours after the task, and a succeeded document polled after that can carry `content` absent or null.","nullable":true,"properties":{"last_frame_url":{"description":"Download URL for the last frame of the generated video, returned when the request set `return_last_frame`. Do not infer the image format from this URL: BytePlus documents the last frame as PNG on the request side, Router re-hosts whatever bytes it is served and types them from the upstream Content-Type or a content sniff, and `image/jpeg` is only the last-resort fallback when both fail. Router re-hosts the last frame onto Comfy storage and rewrites this field, so it is normally a Comfy-signed URL valid for up to 24 hours - signed for 24 hours when minted and replayed from a 23-hour memo, so a later poll can hand back one with as little as an hour left. When the re-host could not be performed the field keeps BytePlus's own URL instead, which BytePlus clears 24 hours after the task. Either way the link expires, so download the frame rather than storing the URL.","type":"string"},"output_format":{"description":"Container format of the generated video (mp4 or mov), when BytePlus nests it inside `content`. Seedance models more commonly return it as a TOP-LEVEL sibling of `content` - see the top-level `output_format` field - and Router reads whichever of the two is present.","type":"string"},"video_url":{"description":"Download URL for the output video. Router re-hosts the video onto Comfy storage and rewrites this field, so it is normally a Comfy-signed URL valid for up to 24 hours - signed for 24 hours when minted and replayed from a 23-hour memo, so a later poll can hand back one with as little as an hour left. When the re-host could not be performed the field keeps BytePlus's own URL instead, which BytePlus clears 24 hours after the task and caps at 100 downloads on some models. Either way the link expires, so download the video rather than storing the URL.","type":"string"}},"type":"object"},"created_at":{"description":"The time when the task was created. The value is a UNIX timestamp in seconds.","type":"integer"},"duration":{"description":"The duration of the generated video in seconds. Declared as a number rather than an integer because BytePlus is not consistent about it - video tasks have been observed returning whole seconds and sibling BytePlus surfaces report fractional durations - so a client must not assume an integral value. BytePlus's own field, returned on succeeded video tasks and forwarded unchanged.","type":"number"},"error":{"description":"The error information. If the task succeeds, null is returned. If the task fails, the error information is returned.","nullable":true,"properties":{"code":{"$ref":"#/components/schemas/BytePlusErrorCode"},"message":{"description":"The error message","type":"string"}},"type":"object"},"id":{"description":"The ID of the video generation task","type":"string"},"model":{"description":"The name and version of the model used by the task","type":"string"},"output_format":{"description":"Container format of the generated video (mp4 or mov), returned at the TOP LEVEL as a sibling of `content` - this is where the Seedance video task query returns it. BytePlus's own field, forwarded unchanged.","type":"string"},"resolution":{"description":"The resolution of the generated video, for example `1080p`. BytePlus's own field, returned on succeeded video tasks and forwarded unchanged.","type":"string"},"seed":{"description":"The generation seed actually used for the task. BytePlus's own field, returned on succeeded video tasks and forwarded unchanged.","format":"int64","type":"integer"},"status":{"description":"The state of the task","enum":["queued","running","cancelled","succeeded","failed","expired"],"type":"string"},"updated_at":{"description":"The time when the task was last updated. The value is a UNIX timestamp in seconds.","type":"integer"},"usage":{"description":"The token usage for the request","properties":{"completion_tokens":{"description":"The number of tokens generated by the model","type":"integer"},"total_tokens":{"description":"For the video generation model, the number of input tokens is not calculated and defaults to 0. Therefore, total_tokens = completion_tokens.","type":"integer"}},"type":"object"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"byteplus/dreamina-seedance-2-0-260128","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image","video","audio"],"x-comfy-router-output-modalities":["video","audio"],"x-comfy-router-alt-providers":[{"provider":"fal","model_id":"fal/fal-seedance-2.0"},{"provider":"higgsfield","model_id":"higgsfield/higgsfield-seedance-2.0"},{"provider":"runware","model_id":"runware/runware-seedance-2.0"},{"provider":"wavespeed","model_id":"wavespeed/wavespeed-seedance-2.0"}]}
+{"openapi":"3.0.2","info":{"title":"byteplus/dreamina-seedance-2-0-260128","description":"The request body Comfy Router accepts for the model \"byteplus/dreamina-seedance-2-0-260128\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"ea6dda283563"},"paths":{"/v2/models/byteplus/dreamina-seedance-2-0-260128":{"post":{"operationId":"runRouterModel","summary":"Run byteplus/dreamina-seedance-2-0-260128 synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"description":"Request body for a BytePlus Seedance / Dreamina video generation task. The `example` below is the COMFY ROUTER form and deliberately omits `model`, which Router fills from the `{model}` path segment; a direct v1 call to POST /proxy/byteplus/api/v3/contents/generations/tasks must add `model` to it. This text-to-video example omits the `content` item with `type: image_url` and an `image_url.url` that an image-to-video call supplies; text-only models must omit that image item.\n`content` is the whole `required` floor, and it is the floor the v1 surface already published and videoGenerationProxy's Rewrite already refuses an empty value for. Everything else BytePlus accepts varies by model — the per-model resolutions, durations, ratios and input kinds are documented on the individual fields — so the enums and bounds below are the union BytePlus admits across the family and BytePlus stays the authority on which member accepts which value.","example":{"content":[{"text":"A red fox trotting through a snowy pine forest","type":"text"}],"duration":5,"ratio":"16:9","resolution":"720p"},"properties":{"callback_url":{"description":"Callback notification address for the result of this generation task","format":"uri","type":"string"},"content":{"description":"The input content for the model to generate a video","items":{"$ref":"#/components/schemas/BytePlusVideoGenerationContent"},"minItems":1,"type":"array"},"draft":{"default":false,"description":"Seedance 2.5 only. Generates a 480p Draft video; resolution must be 480p.\nPass the returned task ID in a `draft_task` content item to render the final 1080p video. The Draft task ID stays valid for 7 days.\n","type":"boolean"},"duration":{"anyOf":[{"enum":[-1]},{"maximum":30,"minimum":2}],"description":"Video duration in seconds. Seedance 2.5: [4,30] or -1 (auto; video editing tasks support only -1). Seedance 2.0, 2.0 fast \u0026 2.0 mini: [4,15] or -1 (auto). Seedance 1.5 pro: [4,12] or -1. Seedance 1.0: [2,12].\n","type":"integer"},"execution_expires_after":{"description":"Task timeout threshold in seconds. Default 172800 (48h). Range: [3600, 259200].\n","maximum":259200,"minimum":3600,"type":"integer"},"generate_audio":{"default":true,"description":"Supported by Seedance 2.5, 2.0, 2.0 fast, 2.0 mini, and 1.5 pro. Whether the generated video includes audio synchronized with the visuals.\ntrue: The model outputs a video with synchronized audio.\nfalse: The model outputs a silent video.\nOn models that support it, the generated audio track is content-checked by the provider separately from the video; a refusal there is reported as `content_policy_violation` and, because the audio differs each run, can be intermittent for an input the provider otherwise accepts. Send `false` for a silent video if that happens; this helps only when the audio was the refused modality, which a synchronous Router response names as `refusal_subject: output_audio` (a queued request does not identify it yet). A Seedance 2.5 final render inherits `generate_audio` from its Draft task, so recreate the Draft with `false` instead.\n","type":"boolean"},"model":{"description":"The ID of the model to call. Supported models: seedance-1-5-pro-251215, seedance-1-0-pro-250528, seedance-1-0-pro-fast-251015, dreamina-seedance-2-0-260128, dreamina-seedance-2-0-fast-260128, dreamina-seedance-2-0-mini and dreamina-seedance-2-5-260628. A direct v1 call to POST /proxy/byteplus/api/v3/contents/generations/tasks MUST supply it — the proxy refuses any other value, and an omitted one, with a 400. It is NOT in this schema's `required` list because Comfy Router fills it from the `{model}` path segment of /v2/models/byteplus/{model}, so a Router caller omits it.","nullable":true,"type":"string"},"output_format":{"default":"mp4","description":"Seedance 2.5 only. Container format of the output video.\nmp4: General-purpose container (H.264/AAC, yuv420p) with broad compatibility and smaller file size.\nmov: Professional container (H.264 High 4:4:4 Predictive/PCM, yuv444p) with high color precision, suited for post-production; larger file size.\n","enum":["mp4","mov"],"type":"string"},"ratio":{"description":"Aspect ratio of the generated video. Seedance 2.0, 2.0 fast \u0026 2.0 mini, 1.5 pro default: adaptive.\nSeedance 2.5 first-frame / first-last-frame generation: the output follows the first frame's aspect ratio, so only `adaptive` (or omitting the field) is accepted; a concrete ratio is refused with a 400 before dispatch. Seedance 2.0 accepts a concrete ratio in those modes.\n","enum":["16:9","4:3","1:1","3:4","9:16","21:9","9:21","adaptive"],"type":"string"},"resolution":{"description":"Video resolution. Seedance 2.5, 2.0, 2.0 fast \u0026 2.0 mini, 1.5 pro, 1.0 lite default: 720p. Seedance 1.0 pro \u0026 pro-fast default: 1080p.\nNote: Seedance 2.0, 2.0 fast \u0026 2.0 mini do not support 1080p. Seedance 2.5 supports 480p, 720p, and 1080p.\n","enum":["480p","720p","1080p","4k"],"type":"string"},"return_last_frame":{"default":false,"description":"Whether to return the last frame image of the generated video.\ntrue: Returns the last frame image of the generated video. After setting this parameter to true, you can obtain the last frame image by calling the Querying the information about a video generation task. The last frame image is in PNG format, with its pixel width and height consistent with those of the generated video, and it contains no watermarks. Using this parameter allows the generation of multiple consecutive videos: the last frame of the previously generated video is used as the first frame of the next video task, enabling quick generation of multiple consecutive videos.\nfalse: Does not return the last frame image of the generated video.\n","type":"boolean"},"seed":{"description":"Seed integer for controlling randomness. Range: [-1, 2^32-1]. -1 uses a random seed.\n","maximum":4294967295,"minimum":-1,"type":"integer"},"service_tier":{"description":"Service tier for processing. Seedance 2.5, 2.0 \u0026 2.0 fast do not support flex (offline inference).\n","enum":["default","flex"],"type":"string"},"watermark":{"default":false,"description":"Whether the generated video includes a watermark.","type":"boolean"}},"required":["content"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/BytePlusVideoGenerationQueryResponse"}],"description":"Comfy Router output schema for the BytePlus Seedance video models: the terminal task-query document, forwarded unchanged EXCEPT for the two re-hosted `content` URLs described below - every other field is BytePlus's own. The operation is submit-and-poll and Router polls on the caller's behalf, so the body a caller receives is the finished task (`status: succeeded`) rather than the task handle the underlying submit returns.\nThe generated video's download URL is at `content.video_url`, present once the task succeeds. A failed task carries `error.code` and `error.message` instead; `status` is BytePlus's own vocabulary (queued, running, cancelled, succeeded, failed, expired), forwarded unchanged.\nThat URL is RE-HOSTED: Router copies the finished video onto Comfy storage and answers a Comfy-signed URL valid for up to 24 hours in place of BytePlus's own, which BytePlus clears 24 hours after the task and caps at 100 downloads on some models. Exactly two leaves are rewritten this way - `content.video_url` and `content.last_frame_url`, both modelled here. Any OTHER asset URL a task carries keeps BytePlus's own link and is NOT flagged as non-durable, so a stored or replayed document can still hand back a link that BytePlus has since cleared.\nDurability is per leaf, not per response: a leaf whose re-host could not be performed keeps BytePlus's own URL rather than a Comfy one, and the other is unaffected - so a caller that stores or replays this document should not assume every URL in it outlives the call. 24 hours is the CEILING, not a guarantee: the Comfy link is signed for 24 hours from the moment it is minted, and Router memoises it for 23 hours, so a later poll or an `Idempotency-Key` replay can hand back a link with as little as an hour left. Either way the link expires, so download the asset rather than storing it.","example":{"content":{"last_frame_url":"https://example.invalid/byteplus/seedance-1-0-pro-250528/last-frame","video_url":"https://example.invalid/byteplus/seedance-1-0-pro-250528/generated.mp4"},"created_at":1767225600,"duration":5,"error":null,"id":"3f7a1b28-5c0d-4e91-8a6f-1b2c3d4e5f60","model":"seedance-1-0-pro-250528","output_format":"mp4","resolution":"1080p","seed":1234567890123,"status":"succeeded","updated_at":1767225730}}}}}}}}},"components":{"schemas":{"BytePlusErrorCode":{"description":"Upstream ModelArk error code. SensitiveContentDetected, InputTextSensitiveContentDetected, InputImageSensitiveContentDetected, InputVideoSensitiveContentDetected, InputAudioSensitiveContentDetected, OutputTextSensitiveContentDetected, OutputImageSensitiveContentDetected, OutputVideoSensitiveContentDetected and OutputAudioSensitiveContentDetected identify content-policy refusals. A family may have a dot-separated reason, such as InputImageSensitiveContentDetected.PrivacyInformation, OutputVideoSensitiveContentDetected.PolicyViolation or OutputImageSensitiveContentDetected.DeepFake. This is an open string, not an enum: other codes describe validation and provider failures. Router recognizes policy families in HTTP 400 error envelopes and HTTP 200 failed-task responses without overriding transport failures.","externalDocs":{"url":"https://docs.byteplus.com/en/docs/ModelArk/1299023"},"type":"string"},"BytePlusVideoGenerationContent":{"oneOf":[{"properties":{"type":{"enum":["text"]}},"required":["text"]},{"properties":{"type":{"enum":["image_url"]}},"required":["image_url"]},{"properties":{"type":{"enum":["video_url"]}},"required":["video_url"]},{"properties":{"type":{"enum":["audio_url"]}},"required":["audio_url"]},{"properties":{"type":{"enum":["draft_task"]}},"required":["draft_task"]}],"properties":{"audio_url":{"description":"Input audio object. Only Seedance 2.5, 2.0, 2.0 fast \u0026 2.0 mini support audio input. Seedance 2.0, 2.0 fast \u0026 2.0 mini cannot use audio alone - they must include at least 1 image or video; Seedance 2.5 supports audio-only input.","properties":{"url":{"description":"Audio URL, Base64 encoding, or Asset ID.\nAudio URL: Public URL of the audio (wav, mp3).\nBase64: Format data:audio/\u003cformat\u003e;base64,\u003ccontent\u003e\nAsset ID: Format asset://\u003cASSET_ID\u003e\n","type":"string"}},"required":["url"],"type":"object"},"draft_task":{"description":"Seedance 2.5 only. The Draft task to render as the final video. It must be the only content item.\nThe final video reuses the Draft task's prompt, input assets, duration, ratio, seed, generate_audio and omni_reference_task_type; do not send them again. resolution defaults to and only supports 1080p.\n","properties":{"id":{"description":"The task ID returned when the Draft video was created with `draft` set to true.","type":"string"}},"required":["id"],"type":"object"},"image_url":{"properties":{"url":{"description":"Image content for image-to-video generation (when type is \"image_url\")\nImage URL: Make sure that the image URL is accessible.\nBase64-encoded content: Format must be data:image/\u003cformat\u003e;base64,\u003ccontent\u003e\nAsset ID: Format asset://\u003cASSET_ID\u003e\n","type":"string"}},"required":["url"],"type":"object"},"role":{"description":"The role/position of the content item.\nFor images: first_frame, last_frame, or reference_image.\nFor videos: reference_video (Seedance 2.5, 2.0, 2.0 fast \u0026 2.0 mini only).\nFor audio: reference_audio (Seedance 2.5, 2.0, 2.0 fast \u0026 2.0 mini only).\n","enum":["first_frame","last_frame","reference_image","reference_video","reference_audio"],"type":"string"},"text":{"description":"The input text information for the model. Includes text prompt and optional parameters.\n\nText prompt (required): Description of the video to be generated using Chinese and English characters.\n\nParameters (optional): Add --[parameters] after the text prompt to control video specifications:\n- --resolution (--rs): 480p, 720p, 1080p (default: 720p)\n- --ratio (--rt): 21:9, 16:9, 4:3, 1:1, 3:4, 9:16, 9:21, adaptive (default: 16:9 or adaptive)\n- --duration (--dur): 3-12 seconds (default: 5)\n- --framepersecond (--fps): 24 (default: 24)\n- --watermark (--wm): true/false (default: false)\n- --seed (--seed): -1 to 2^32-1 (default: -1)\n- --camerafixed (--cf): true/false (default: false)\n\nExample: \"A beautiful landscape --ratio 16:9 --resolution 720p --duration 5\"\n\nComfy-side guardrail, not a BytePlus constraint. BytePlus publishes no\ntext-length limit and accepted 40,000 characters under test (2026-09-17).\nIt counts characters, not bytes, so a multi-byte prompt may be several\ntimes this size on the wire. It bounds this one field, not the request:\n`content` is an array, and the per-request body limit is what bounds the\nwhole document. Comfy Router (/v2/models/byteplus/{model}) is the surface\nthat enforces it; on a direct v1 /proxy call BytePlus's own validator\nanswers instead. Set far above real traffic so it never adjudicates a\ngenuine prompt — raise it if a caller needs more.\n","maxLength":100000,"type":"string"},"type":{"description":"The type of the input content","enum":["text","image_url","video_url","audio_url","draft_task"],"type":"string"},"video_url":{"description":"Input video object. Only Seedance 2.5, 2.0, 2.0 fast \u0026 2.0 mini support video input.","properties":{"url":{"description":"Video URL or Asset ID.\nVideo URL: Public URL of the video (mp4, mov).\nAsset ID: Format asset://\u003cASSET_ID\u003e\n","type":"string"}},"required":["url"],"type":"object"}},"required":["type"],"type":"object"},"BytePlusVideoGenerationQueryResponse":{"properties":{"content":{"description":"The output after the video generation task is completed, which contains the download URL of the output video and, when BytePlus returns one, the download URL of its last frame. Both `video_url` and `last_frame_url` are RE-HOSTED onto Comfy storage; every other field here is BytePlus's own. Nullable - BytePlus clears the URLs 24 hours after the task, and a succeeded document polled after that can carry `content` absent or null.","nullable":true,"properties":{"last_frame_url":{"description":"Download URL for the last frame of the generated video, returned when the request set `return_last_frame`. Do not infer the image format from this URL: BytePlus documents the last frame as PNG on the request side, Router re-hosts whatever bytes it is served and types them from the upstream Content-Type or a content sniff, and `image/jpeg` is only the last-resort fallback when both fail. Router re-hosts the last frame onto Comfy storage and rewrites this field, so it is normally a Comfy-signed URL valid for up to 24 hours - signed for 24 hours when minted and replayed from a 23-hour memo, so a later poll can hand back one with as little as an hour left. When the re-host could not be performed the field keeps BytePlus's own URL instead, which BytePlus clears 24 hours after the task. Either way the link expires, so download the frame rather than storing the URL.","type":"string"},"output_format":{"description":"Container format of the generated video (mp4 or mov), when BytePlus nests it inside `content`. Seedance models more commonly return it as a TOP-LEVEL sibling of `content` - see the top-level `output_format` field - and Router reads whichever of the two is present.","type":"string"},"video_url":{"description":"Download URL for the output video. Router re-hosts the video onto Comfy storage and rewrites this field, so it is normally a Comfy-signed URL valid for up to 24 hours - signed for 24 hours when minted and replayed from a 23-hour memo, so a later poll can hand back one with as little as an hour left. When the re-host could not be performed the field keeps BytePlus's own URL instead, which BytePlus clears 24 hours after the task and caps at 100 downloads on some models. Either way the link expires, so download the video rather than storing the URL.","type":"string"}},"type":"object"},"created_at":{"description":"The time when the task was created. The value is a UNIX timestamp in seconds.","type":"integer"},"duration":{"description":"The duration of the generated video in seconds. Declared as a number rather than an integer because BytePlus is not consistent about it - video tasks have been observed returning whole seconds and sibling BytePlus surfaces report fractional durations - so a client must not assume an integral value. BytePlus's own field, returned on succeeded video tasks and forwarded unchanged.","type":"number"},"error":{"description":"The error information. If the task succeeds, null is returned. If the task fails, the error information is returned.","nullable":true,"properties":{"code":{"$ref":"#/components/schemas/BytePlusErrorCode"},"message":{"description":"The error message","type":"string"}},"type":"object"},"id":{"description":"The ID of the video generation task","type":"string"},"model":{"description":"The name and version of the model used by the task","type":"string"},"output_format":{"description":"Container format of the generated video (mp4 or mov), returned at the TOP LEVEL as a sibling of `content` - this is where the Seedance video task query returns it. BytePlus's own field, forwarded unchanged.","type":"string"},"resolution":{"description":"The resolution of the generated video, for example `1080p`. BytePlus's own field, returned on succeeded video tasks and forwarded unchanged.","type":"string"},"seed":{"description":"The generation seed actually used for the task. BytePlus's own field, returned on succeeded video tasks and forwarded unchanged.","format":"int64","type":"integer"},"status":{"description":"The state of the task","enum":["queued","running","cancelled","succeeded","failed","expired"],"type":"string"},"updated_at":{"description":"The time when the task was last updated. The value is a UNIX timestamp in seconds.","type":"integer"},"usage":{"description":"The token usage for the request","properties":{"completion_tokens":{"description":"The number of tokens generated by the model","type":"integer"},"total_tokens":{"description":"For the video generation model, the number of input tokens is not calculated and defaults to 0. Therefore, total_tokens = completion_tokens.","type":"integer"}},"type":"object"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"byteplus/dreamina-seedance-2-0-260128","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image","video","audio"],"x-comfy-router-output-modalities":["video","audio"],"x-comfy-router-alt-providers":[{"provider":"fal","model_id":"fal/fal-seedance-2.0"},{"provider":"higgsfield","model_id":"higgsfield/higgsfield-seedance-2.0"},{"provider":"pika","model_id":"pika/pika-seedance-2.0"},{"provider":"runware","model_id":"runware/runware-seedance-2.0"},{"provider":"wavespeed","model_id":"wavespeed/wavespeed-seedance-2.0"}]}
diff --git a/router-schemas/meshy/meshy-5.json b/router-schemas/meshy/meshy-5.json
index 7155379d2f..89c2092227 100644
--- a/router-schemas/meshy/meshy-5.json
+++ b/router-schemas/meshy/meshy-5.json
@@ -1 +1 @@
-{"openapi":"3.0.2","info":{"title":"meshy/meshy-5","description":"The request body Comfy Router accepts for the model \"meshy/meshy-5\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"7e376ab5d6cc"},"paths":{"/v2/models/meshy/meshy-5":{"post":{"operationId":"runRouterModel","summary":"Run meshy/meshy-5 synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/MeshyTextTo3DInputs"},{"properties":{"geometry_resolution":{"default":"standard","description":"Geometry resolution of the generated mesh. meshy-5 supports `standard` only; `2k` requires ai_model meshy-7, meshy-7.1 or latest, and `4k` requires meshy-7.1 or latest.","enum":["standard"],"type":"string"},"texture_resolution":{"default":"2k","description":"Texture resolution of the generated textures. meshy-5 supports `2k` only; `4k` and `8k` require ai_model meshy-6, meshy-7 or meshy-7.1.","enum":["2k"],"type":"string"},"ultra_mode":{"default":false,"description":"Not supported on meshy-5, which accepts only `false`. Ultra generation requires ai_model meshy-7, meshy-7.1 or latest (and is a preview-mode option); address meshy/meshy-7 or meshy/meshy-7.1 to use it.","enum":[false],"type":"boolean"}},"type":"object"}],"description":"The Comfy Router input schema for `meshy/meshy-5` text-to-3d.\nmeshy-5 supports NEITHER Ultra generation NOR 4k/8k textures, so this component narrows `ultra_mode` to `false`, `geometry_resolution` to `standard`, and `texture_resolution` to `2k`. Everything else is the shared shape in MeshyTextTo3DInputs.","example":{"art_style":"realistic","mode":"preview","prompt":"a red cube"}}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/MeshyTextTo3DTask"},{"properties":{"status":{"enum":["SUCCEEDED"],"type":"string"}},"required":["status"],"type":"object"}],"description":"Comfy Router output schema for the Meshy text-to-3D models: the terminal `GET /proxy/meshy/openapi/v2/text-to-3d/{task_id}` task document, forwarded unchanged. The operation is SUBMIT-AND-POLL — `POST /proxy/meshy/openapi/v2/text-to-3d` answers with a `MeshyTextTo3DCreateResponse` handle whose `result` is the task id, and Router polls on the caller's behalf — so the body a caller receives is the finished task document rather than that handle. The family's three other operations (image-to-3d, multi-image-to-3d and retexture) stay reachable on their own `/proxy/meshy/...` routes and answer closely related task documents; a `{provider}/{model}` call settles onto the text-to-3d generation, which is the one this schema describes.\nThe generated mesh is in `model_urls`, at whichever of `glb`, `fbx`, `usdz` and `obj` the request produced — which of them are populated depends on the request, so read the one that is present rather than keying completion off `glb` alone. `glb` is the rendition a `{\"mode\": \"preview\"}` task with no format selector answers with, but it is not the only shape a success can take. Two neighbouring fields are NOT interchangeable with a mesh and a caller must not key completion off either: `mtl` is a material sidecar for `obj` rather than a mesh in its own right, and `thumbnail_url` is a preview image rather than the asset the call generated. Comfy Router draws exactly that line — a `SUCCEEDED` task carrying none of those four mesh URLs is answered as a Comfy Router error rather than with this document — so through Router a `200` here always carries a mesh, but not always the same one.\n`status` is Meshy's own UPPERCASE vocabulary — `PENDING`, `IN_PROGRESS`, `SUCCEEDED`, `FAILED`, `CANCELED` — forwarded unchanged and not case-folded. `SUCCEEDED` is the one terminal success; `FAILED` and `CANCELED` are terminal failures and reach a Router caller as a Comfy Router error rather than as this document, with the provider's own reason in `task_error.message`. `progress` runs 0..100 and is 100 on a succeeded task.\n`type` says which of the operation's two stages produced the document. `text-to-3d-preview` is the geometry stage, requested with `{\"mode\": \"preview\"}` and a prompt. `text-to-3d-refine` is the separate, dearer texturing task, requested with `{\"mode\": \"refine\"}` against a finished preview's `id` as `preview_task_id`; it is what populates `texture_urls`.\n`negative_prompt` and `texture_richness` are fields Meshy keeps for backward compatibility and does not populate meaningfully; `video_url` is likewise deprecated.","example":{"art_style":"realistic","created_at":1767225600000,"finished_at":1767225719000,"id":"018f2c7a-4b1e-7c3d-9a05-6e2f8b41d0c9","model_urls":{"fbx":"https://example.invalid/meshy/text-to-3d/model.fbx","glb":"https://example.invalid/meshy/text-to-3d/model.glb","mtl":"https://example.invalid/meshy/text-to-3d/model.mtl","obj":"https://example.invalid/meshy/text-to-3d/model.obj","usdz":"https://example.invalid/meshy/text-to-3d/model.usdz"},"progress":100,"prompt":"a red cube","started_at":1767225601000,"status":"SUCCEEDED","thumbnail_url":"https://example.invalid/meshy/text-to-3d/thumbnail.png","type":"text-to-3d-preview"}}}}}}}}},"components":{"schemas":{"MeshyArtStyle":{"default":"realistic","description":"Describe your desired art style of the object.","enum":["realistic","sculpture"],"type":"string"},"MeshyModelUrls":{"description":"Downloadable URLs to the textured 3D model files generated by Meshy.","properties":{"fbx":{"description":"Downloadable URL to the FBX file.","type":"string"},"glb":{"description":"Downloadable URL to the GLB file.","type":"string"},"mtl":{"description":"Downloadable URL to the MTL file.","type":"string"},"obj":{"description":"Downloadable URL to the OBJ file.","type":"string"},"usdz":{"description":"Downloadable URL to the USDZ file.","type":"string"}},"type":"object"},"MeshyPoseMode":{"description":"Specify the pose mode for the generated model.","enum":["a-pose","t-pose",""],"type":"string"},"MeshySymmetryMode":{"default":"auto","description":"Controls symmetry behavior during model generation.","enum":["off","auto","on"],"type":"string"},"MeshyTaskError":{"description":"Error object that contains the error message if the task failed.","properties":{"message":{"description":"Detailed error message.","type":"string"}},"type":"object"},"MeshyTaskStatus":{"description":"Status of the task.","enum":["PENDING","IN_PROGRESS","SUCCEEDED","FAILED","CANCELED"],"type":"string"},"MeshyTextTo3DInputs":{"description":"The model-agnostic Comfy Router input shape for the `meshy/*` text-to-3d models: either a PREVIEW body (`mode: preview`, which generates the untextured mesh) or a REFINE body (`mode: refine`, which textures a preview task's output).\nIt composes the same two branches the v1 `POST /proxy/meshy/openapi/v2/text-to-3d` body does, differing only in `ai_model`: on this route the model comes from the path (`/v2/models/meshy/{model}`), so `ai_model` may be omitted or sent as null and Comfy Router writes the path's model into it before the call is forwarded.","discriminator":{"mapping":{"preview":"#/components/schemas/MeshyTextTo3DPreviewInputs","refine":"#/components/schemas/MeshyTextTo3DRefineInputs"},"propertyName":"mode"},"oneOf":[{"$ref":"#/components/schemas/MeshyTextTo3DPreviewInputs"},{"$ref":"#/components/schemas/MeshyTextTo3DRefineInputs"}]},"MeshyTextTo3DPreviewInputs":{"description":"The meshy text-to-3d PREVIEW fields, shared by the `preview` branch of the v1 `POST /proxy/meshy/openapi/v2/text-to-3d` body and the `preview` branch of the Comfy Router input schema for the `meshy/*` models.\nThe two surfaces share every property and both `required` entries. They differ only in `ai_model`: the v1 body restates it as a fixed enum, because that route carries no path segment to supply the model from, while on the Comfy Router route the path supplies it.","properties":{"ai_model":{"description":"The Meshy model to run. On the Comfy Router route this is supplied by the path (`/v2/models/meshy/{model}`) and may be omitted or sent as null; Router writes the path's model here after validation. On the v1 /proxy route see MeshyTextTo3DPreviewRequest, which constrains it to MeshyAiModel.","nullable":true,"type":"string"},"art_style":{"$ref":"#/components/schemas/MeshyArtStyle"},"geometry_resolution":{"default":"standard","description":"Geometry resolution of the generated mesh. 2k and 4k run the Ultra pass for finer surface detail. 2k requires ai_model meshy-7, meshy-7.1 or latest; 4k requires meshy-7.1 or latest.","enum":["standard","2k","4k"],"type":"string"},"is_a_t_pose":{"default":false,"description":"Deprecated. Use pose_mode instead. Whether to generate the model in an A/T pose.","type":"boolean"},"mode":{"description":"This field should be set to \"preview\" when creating a preview task.","enum":["preview"],"type":"string"},"moderation":{"default":false,"description":"When true, input content will be screened for potentially harmful content.","type":"boolean"},"pose_mode":{"$ref":"#/components/schemas/MeshyPoseMode"},"prompt":{"description":"Describe what kind of object the 3D model is. Maximum 800 characters.","maxLength":800,"type":"string"},"should_remesh":{"default":false,"description":"Controls whether to enable the remesh phase. When false, returns highest-precision triangular mesh. Defaults to true for Meshy 5 and false for Meshy 6 and 7.","type":"boolean"},"symmetry_mode":{"$ref":"#/components/schemas/MeshySymmetryMode"},"target_polycount":{"default":30000,"description":"Specify the target number of polygons in the generated model. Valid range is 100 to 300,000.","maximum":300000,"minimum":100,"type":"integer"},"topology":{"$ref":"#/components/schemas/MeshyTopology"},"ultra_mode":{"default":false,"deprecated":true,"description":"Deprecated. Use geometry_resolution instead; true is equivalent to geometry_resolution 2k. Only supported when ai_model is meshy-7, meshy-7.1 or latest.","type":"boolean"}},"required":["mode","prompt"],"type":"object"},"MeshyTextTo3DRefineInputs":{"description":"The meshy text-to-3d REFINE fields, shared by the `refine` branch of the v1 `POST /proxy/meshy/openapi/v2/text-to-3d` body and the `refine` branch of the Comfy Router input schema for the `meshy/*` models.\nThe two surfaces share every property and both `required` entries. They differ only in `ai_model`: the v1 body restates it as a fixed enum, because that route carries no path segment to supply the model from, while on the Comfy Router route the path supplies it.","properties":{"ai_model":{"description":"The Meshy model to run. On the Comfy Router route this is supplied by the path (`/v2/models/meshy/{model}`) and may be omitted or sent as null; Router writes the path's model here after validation. On the v1 /proxy route see MeshyTextTo3DRefineRequest, which constrains it to MeshyAiModel.","nullable":true,"type":"string"},"enable_pbr":{"default":false,"description":"Generate PBR Maps (metallic, roughness, normal) in addition to the base color. Note that enable_pbr should be set to false when using Sculpture style.","type":"boolean"},"mode":{"description":"This field should be set to \"refine\" when creating a refine task.","enum":["refine"],"type":"string"},"moderation":{"default":false,"description":"When true, input content will be screened for potentially harmful content.","type":"boolean"},"preview_task_id":{"description":"The corresponding preview task id. The status of the given preview task must be SUCCEEDED.","type":"string"},"texture_image_url":{"description":"Provide a 2d image to guide the texturing process. Supports .jpg, .jpeg, .png formats or base64-encoded data URI.","type":"string"},"texture_prompt":{"description":"Provide an additional text prompt to guide the texturing process. Maximum 800 characters.","maxLength":800,"type":"string"},"texture_resolution":{"default":"2k","description":"Texture resolution of the generated textures. One of 2k, 4k or 8k. 4k and 8k require ai_model meshy-6, meshy-7, meshy-7.1 or latest.","enum":["2k","4k","8k"],"type":"string"}},"required":["mode","preview_task_id"],"type":"object"},"MeshyTextTo3DTask":{"properties":{"art_style":{"description":"The unmodified art_style that was used to create the preview task.","type":"string"},"created_at":{"description":"Timestamp of when the task was created, in milliseconds.","format":"int64","type":"integer"},"finished_at":{"description":"Timestamp of when the task was finished, in milliseconds. 0 if not finished.","format":"int64","type":"integer"},"id":{"description":"Unique identifier for the task.","type":"string"},"model_urls":{"$ref":"#/components/schemas/MeshyModelUrls"},"negative_prompt":{"description":"Deprecated field maintained for backward compatibility.","type":"string"},"preceding_tasks":{"description":"The count of preceding tasks. Only meaningful when status is PENDING.","type":"integer"},"progress":{"description":"Progress of the task. 0 if not started, 100 when succeeded.","maximum":100,"minimum":0,"type":"integer"},"prompt":{"description":"The unmodified prompt that was used to create the task.","type":"string"},"started_at":{"description":"Timestamp of when the task was started, in milliseconds. 0 if not started.","format":"int64","type":"integer"},"status":{"$ref":"#/components/schemas/MeshyTaskStatus"},"task_error":{"allOf":[{"$ref":"#/components/schemas/MeshyTaskError"}],"nullable":true},"texture_image_url":{"description":"Downloadable URL to the texture image that was used to guide the texturing process.","type":"string"},"texture_prompt":{"description":"Additional text prompt provided to guide the texturing process during the refine stage.","type":"string"},"texture_richness":{"description":"Deprecated field maintained for backward compatibility.","type":"string"},"texture_urls":{"description":"An array of texture URL objects that are generated from the task.","items":{"$ref":"#/components/schemas/MeshyTextureUrls"},"type":"array"},"thumbnail_url":{"description":"Downloadable URL to the thumbnail image of the model file.","type":"string"},"type":{"description":"Type of the Text to 3D task.","enum":["text-to-3d-preview","text-to-3d-refine"],"type":"string"},"video_url":{"description":"Deprecated field returning the downloadable URL to the preview video.","type":"string"}},"required":["id","status"],"type":"object"},"MeshyTextureUrls":{"description":"Texture URL object containing PBR maps.","properties":{"base_color":{"description":"Downloadable URL to the base color map image.","type":"string"},"metallic":{"description":"Downloadable URL to the metallic map image.","type":"string"},"normal":{"description":"Downloadable URL to the normal map image.","type":"string"},"roughness":{"description":"Downloadable URL to the roughness map image.","type":"string"}},"type":"object"},"MeshyTopology":{"default":"triangle","description":"Specify the topology of the generated model.","enum":["quad","triangle"],"type":"string"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"meshy/meshy-5","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text"],"x-comfy-router-output-modalities":["3d"]}
+{"openapi":"3.0.2","info":{"title":"meshy/meshy-5","description":"The request body Comfy Router accepts for the model \"meshy/meshy-5\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"28c123d64672"},"paths":{"/v2/models/meshy/meshy-5":{"post":{"operationId":"runRouterModel","summary":"Run meshy/meshy-5 synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/MeshyTextTo3DInputs"},{"properties":{"geometry_resolution":{"default":"standard","description":"Geometry resolution of the generated mesh. meshy-5 supports `standard` only; `2k` and `4k` require ai_model meshy-7.1 or latest.","enum":["standard"],"type":"string"},"texture_resolution":{"default":"2k","description":"Texture resolution of the generated textures. meshy-5 supports `2k` only; `4k` and `8k` require ai_model meshy-6, meshy-7 or meshy-7.1.","enum":["2k"],"type":"string"},"ultra_mode":{"default":false,"description":"Not supported on meshy-5, which accepts only `false`. Ultra generation requires ai_model meshy-7, meshy-7.1 or latest (and is a preview-mode option); address meshy/meshy-7 or meshy/meshy-7.1 to use it.","enum":[false],"type":"boolean"}},"type":"object"}],"description":"The Comfy Router input schema for `meshy/meshy-5` text-to-3d.\nmeshy-5 supports NEITHER Ultra generation NOR 4k/8k textures, so this component narrows `ultra_mode` to `false`, `geometry_resolution` to `standard`, and `texture_resolution` to `2k`. Everything else is the shared shape in MeshyTextTo3DInputs.","example":{"art_style":"realistic","mode":"preview","prompt":"a red cube"}}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/MeshyTextTo3DTask"},{"properties":{"status":{"enum":["SUCCEEDED"],"type":"string"}},"required":["status"],"type":"object"}],"description":"Comfy Router output schema for the Meshy text-to-3D models: the terminal `GET /proxy/meshy/openapi/v2/text-to-3d/{task_id}` task document, forwarded unchanged. The operation is SUBMIT-AND-POLL — `POST /proxy/meshy/openapi/v2/text-to-3d` answers with a `MeshyTextTo3DCreateResponse` handle whose `result` is the task id, and Router polls on the caller's behalf — so the body a caller receives is the finished task document rather than that handle. The family's three other operations (image-to-3d, multi-image-to-3d and retexture) stay reachable on their own `/proxy/meshy/...` routes and answer closely related task documents; a `{provider}/{model}` call settles onto the text-to-3d generation, which is the one this schema describes.\nThe generated mesh is in `model_urls`, at whichever of `glb`, `fbx`, `usdz` and `obj` the request produced — which of them are populated depends on the request, so read the one that is present rather than keying completion off `glb` alone. `glb` is the rendition a `{\"mode\": \"preview\"}` task with no format selector answers with, but it is not the only shape a success can take. Two neighbouring fields are NOT interchangeable with a mesh and a caller must not key completion off either: `mtl` is a material sidecar for `obj` rather than a mesh in its own right, and `thumbnail_url` is a preview image rather than the asset the call generated. Comfy Router draws exactly that line — a `SUCCEEDED` task carrying none of those four mesh URLs is answered as a Comfy Router error rather than with this document — so through Router a `200` here always carries a mesh, but not always the same one.\n`status` is Meshy's own UPPERCASE vocabulary — `PENDING`, `IN_PROGRESS`, `SUCCEEDED`, `FAILED`, `CANCELED` — forwarded unchanged and not case-folded. `SUCCEEDED` is the one terminal success; `FAILED` and `CANCELED` are terminal failures and reach a Router caller as a Comfy Router error rather than as this document, with the provider's own reason in `task_error.message`. `progress` runs 0..100 and is 100 on a succeeded task.\n`type` says which of the operation's two stages produced the document. `text-to-3d-preview` is the geometry stage, requested with `{\"mode\": \"preview\"}` and a prompt. `text-to-3d-refine` is the separate, dearer texturing task, requested with `{\"mode\": \"refine\"}` against a finished preview's `id` as `preview_task_id`; it is what populates `texture_urls`.\n`negative_prompt` and `texture_richness` are fields Meshy keeps for backward compatibility and does not populate meaningfully; `video_url` is likewise deprecated.","example":{"art_style":"realistic","created_at":1767225600000,"finished_at":1767225719000,"id":"018f2c7a-4b1e-7c3d-9a05-6e2f8b41d0c9","model_urls":{"fbx":"https://example.invalid/meshy/text-to-3d/model.fbx","glb":"https://example.invalid/meshy/text-to-3d/model.glb","mtl":"https://example.invalid/meshy/text-to-3d/model.mtl","obj":"https://example.invalid/meshy/text-to-3d/model.obj","usdz":"https://example.invalid/meshy/text-to-3d/model.usdz"},"progress":100,"prompt":"a red cube","started_at":1767225601000,"status":"SUCCEEDED","thumbnail_url":"https://example.invalid/meshy/text-to-3d/thumbnail.png","type":"text-to-3d-preview"}}}}}}}}},"components":{"schemas":{"MeshyArtStyle":{"default":"realistic","description":"Describe your desired art style of the object.","enum":["realistic","sculpture"],"type":"string"},"MeshyModelUrls":{"description":"Downloadable URLs to the textured 3D model files generated by Meshy.","properties":{"fbx":{"description":"Downloadable URL to the FBX file.","type":"string"},"glb":{"description":"Downloadable URL to the GLB file.","type":"string"},"mtl":{"description":"Downloadable URL to the MTL file.","type":"string"},"obj":{"description":"Downloadable URL to the OBJ file.","type":"string"},"usdz":{"description":"Downloadable URL to the USDZ file.","type":"string"}},"type":"object"},"MeshyPoseMode":{"description":"Specify the pose mode for the generated model.","enum":["a-pose","t-pose",""],"type":"string"},"MeshySymmetryMode":{"default":"auto","description":"Controls symmetry behavior during model generation.","enum":["off","auto","on"],"type":"string"},"MeshyTaskError":{"description":"Error object that contains the error message if the task failed.","properties":{"message":{"description":"Detailed error message.","type":"string"}},"type":"object"},"MeshyTaskStatus":{"description":"Status of the task.","enum":["PENDING","IN_PROGRESS","SUCCEEDED","FAILED","CANCELED"],"type":"string"},"MeshyTextTo3DInputs":{"description":"The model-agnostic Comfy Router input shape for the `meshy/*` text-to-3d models: either a PREVIEW body (`mode: preview`, which generates the untextured mesh) or a REFINE body (`mode: refine`, which textures a preview task's output).\nIt composes the same two branches the v1 `POST /proxy/meshy/openapi/v2/text-to-3d` body does, differing only in `ai_model`: on this route the model comes from the path (`/v2/models/meshy/{model}`), so `ai_model` may be omitted or sent as null and Comfy Router writes the path's model into it before the call is forwarded.","discriminator":{"mapping":{"preview":"#/components/schemas/MeshyTextTo3DPreviewInputs","refine":"#/components/schemas/MeshyTextTo3DRefineInputs"},"propertyName":"mode"},"oneOf":[{"$ref":"#/components/schemas/MeshyTextTo3DPreviewInputs"},{"$ref":"#/components/schemas/MeshyTextTo3DRefineInputs"}]},"MeshyTextTo3DPreviewInputs":{"description":"The meshy text-to-3d PREVIEW fields, shared by the `preview` branch of the v1 `POST /proxy/meshy/openapi/v2/text-to-3d` body and the `preview` branch of the Comfy Router input schema for the `meshy/*` models.\nThe two surfaces share every property and both `required` entries. They differ only in `ai_model`: the v1 body restates it as a fixed enum, because that route carries no path segment to supply the model from, while on the Comfy Router route the path supplies it.","properties":{"ai_model":{"description":"The Meshy model to run. On the Comfy Router route this is supplied by the path (`/v2/models/meshy/{model}`) and may be omitted or sent as null; Router writes the path's model here after validation. On the v1 /proxy route see MeshyTextTo3DPreviewRequest, which constrains it to MeshyAiModel.","nullable":true,"type":"string"},"art_style":{"$ref":"#/components/schemas/MeshyArtStyle"},"geometry_resolution":{"default":"standard","description":"Geometry resolution of the generated mesh. 2k and 4k run the Ultra pass for finer surface detail. 2k and 4k require ai_model meshy-7.1 or latest.","enum":["standard","2k","4k"],"type":"string"},"is_a_t_pose":{"default":false,"description":"Deprecated. Use pose_mode instead. Whether to generate the model in an A/T pose.","type":"boolean"},"mode":{"description":"This field should be set to \"preview\" when creating a preview task.","enum":["preview"],"type":"string"},"moderation":{"default":false,"description":"When true, input content will be screened for potentially harmful content.","type":"boolean"},"pose_mode":{"$ref":"#/components/schemas/MeshyPoseMode"},"prompt":{"description":"Describe what kind of object the 3D model is. Maximum 800 characters.","maxLength":800,"type":"string"},"should_remesh":{"default":false,"description":"Controls whether to enable the remesh phase. When false, returns highest-precision triangular mesh. Defaults to true for Meshy 5 and false for Meshy 6 and 7.","type":"boolean"},"symmetry_mode":{"$ref":"#/components/schemas/MeshySymmetryMode"},"target_polycount":{"default":30000,"description":"Specify the target number of polygons in the generated model. Valid range is 100 to 300,000.","maximum":300000,"minimum":100,"type":"integer"},"topology":{"$ref":"#/components/schemas/MeshyTopology"},"ultra_mode":{"default":false,"deprecated":true,"description":"Deprecated. Use geometry_resolution instead; true is equivalent to geometry_resolution 2k. Only supported when ai_model is meshy-7, meshy-7.1 or latest. The exception is meshy-7: geometry_resolution 2k requires meshy-7.1 or latest, so on meshy-7 ultra_mode true is the way to request the Ultra pass.","type":"boolean"}},"required":["mode","prompt"],"type":"object"},"MeshyTextTo3DRefineInputs":{"description":"The meshy text-to-3d REFINE fields, shared by the `refine` branch of the v1 `POST /proxy/meshy/openapi/v2/text-to-3d` body and the `refine` branch of the Comfy Router input schema for the `meshy/*` models.\nThe two surfaces share every property and both `required` entries. They differ only in `ai_model`: the v1 body restates it as a fixed enum, because that route carries no path segment to supply the model from, while on the Comfy Router route the path supplies it.","properties":{"ai_model":{"description":"The Meshy model to run. On the Comfy Router route this is supplied by the path (`/v2/models/meshy/{model}`) and may be omitted or sent as null; Router writes the path's model here after validation. On the v1 /proxy route see MeshyTextTo3DRefineRequest, which constrains it to MeshyAiModel.","nullable":true,"type":"string"},"enable_pbr":{"default":false,"description":"Generate PBR Maps (metallic, roughness, normal) in addition to the base color. Note that enable_pbr should be set to false when using Sculpture style.","type":"boolean"},"mode":{"description":"This field should be set to \"refine\" when creating a refine task.","enum":["refine"],"type":"string"},"moderation":{"default":false,"description":"When true, input content will be screened for potentially harmful content.","type":"boolean"},"preview_task_id":{"description":"The corresponding preview task id. The status of the given preview task must be SUCCEEDED.","type":"string"},"texture_image_url":{"description":"Provide a 2d image to guide the texturing process. Supports .jpg, .jpeg, .png formats or base64-encoded data URI.","type":"string"},"texture_prompt":{"description":"Provide an additional text prompt to guide the texturing process. Maximum 800 characters.","maxLength":800,"type":"string"},"texture_resolution":{"default":"2k","description":"Texture resolution of the generated textures. One of 2k, 4k or 8k. 4k and 8k require ai_model meshy-6, meshy-7, meshy-7.1 or latest.","enum":["2k","4k","8k"],"type":"string"}},"required":["mode","preview_task_id"],"type":"object"},"MeshyTextTo3DTask":{"properties":{"art_style":{"description":"The unmodified art_style that was used to create the preview task.","type":"string"},"created_at":{"description":"Timestamp of when the task was created, in milliseconds.","format":"int64","type":"integer"},"finished_at":{"description":"Timestamp of when the task was finished, in milliseconds. 0 if not finished.","format":"int64","type":"integer"},"id":{"description":"Unique identifier for the task.","type":"string"},"model_urls":{"$ref":"#/components/schemas/MeshyModelUrls"},"negative_prompt":{"description":"Deprecated field maintained for backward compatibility.","type":"string"},"preceding_tasks":{"description":"The count of preceding tasks. Only meaningful when status is PENDING.","type":"integer"},"progress":{"description":"Progress of the task. 0 if not started, 100 when succeeded.","maximum":100,"minimum":0,"type":"integer"},"prompt":{"description":"The unmodified prompt that was used to create the task.","type":"string"},"started_at":{"description":"Timestamp of when the task was started, in milliseconds. 0 if not started.","format":"int64","type":"integer"},"status":{"$ref":"#/components/schemas/MeshyTaskStatus"},"task_error":{"allOf":[{"$ref":"#/components/schemas/MeshyTaskError"}],"nullable":true},"texture_image_url":{"description":"Downloadable URL to the texture image that was used to guide the texturing process.","type":"string"},"texture_prompt":{"description":"Additional text prompt provided to guide the texturing process during the refine stage.","type":"string"},"texture_richness":{"description":"Deprecated field maintained for backward compatibility.","type":"string"},"texture_urls":{"description":"An array of texture URL objects that are generated from the task.","items":{"$ref":"#/components/schemas/MeshyTextureUrls"},"type":"array"},"thumbnail_url":{"description":"Downloadable URL to the thumbnail image of the model file.","type":"string"},"type":{"description":"Type of the Text to 3D task.","enum":["text-to-3d-preview","text-to-3d-refine"],"type":"string"},"video_url":{"description":"Deprecated field returning the downloadable URL to the preview video.","type":"string"}},"required":["id","status"],"type":"object"},"MeshyTextureUrls":{"description":"Texture URL object containing PBR maps.","properties":{"base_color":{"description":"Downloadable URL to the base color map image.","type":"string"},"metallic":{"description":"Downloadable URL to the metallic map image.","type":"string"},"normal":{"description":"Downloadable URL to the normal map image.","type":"string"},"roughness":{"description":"Downloadable URL to the roughness map image.","type":"string"}},"type":"object"},"MeshyTopology":{"default":"triangle","description":"Specify the topology of the generated model.","enum":["quad","triangle"],"type":"string"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"meshy/meshy-5","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text"],"x-comfy-router-output-modalities":["3d"]}
diff --git a/router-schemas/meshy/meshy-6.json b/router-schemas/meshy/meshy-6.json
index 1bf2bb53ce..1ce0cad503 100644
--- a/router-schemas/meshy/meshy-6.json
+++ b/router-schemas/meshy/meshy-6.json
@@ -1 +1 @@
-{"openapi":"3.0.2","info":{"title":"meshy/meshy-6","description":"The request body Comfy Router accepts for the model \"meshy/meshy-6\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"8310791e9401"},"paths":{"/v2/models/meshy/meshy-6":{"post":{"operationId":"runRouterModel","summary":"Run meshy/meshy-6 synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/MeshyTextTo3DInputs"},{"properties":{"geometry_resolution":{"default":"standard","description":"Geometry resolution of the generated mesh. meshy-6 supports `standard` only; `2k` requires ai_model meshy-7, meshy-7.1 or latest, and `4k` requires meshy-7.1 or latest.","enum":["standard"],"type":"string"},"ultra_mode":{"default":false,"description":"Not supported on meshy-6, which accepts only `false`. Ultra generation requires ai_model meshy-7, meshy-7.1 or latest (and is a preview-mode option); address meshy/meshy-7 or meshy/meshy-7.1 to use it.","enum":[false],"type":"boolean"}},"type":"object"}],"description":"The Comfy Router input schema for `meshy/meshy-6` text-to-3d.\nmeshy-6 supports the full `2k`/`4k`/`8k` texture range but NOT Ultra generation, so this component narrows `ultra_mode` to `false` and `geometry_resolution` to `standard`, and leaves `texture_resolution` at the shared enum. Everything else is the shared shape in MeshyTextTo3DInputs.","example":{"art_style":"realistic","mode":"preview","prompt":"a red cube"}}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/MeshyTextTo3DTask"},{"properties":{"status":{"enum":["SUCCEEDED"],"type":"string"}},"required":["status"],"type":"object"}],"description":"Comfy Router output schema for the Meshy text-to-3D models: the terminal `GET /proxy/meshy/openapi/v2/text-to-3d/{task_id}` task document, forwarded unchanged. The operation is SUBMIT-AND-POLL — `POST /proxy/meshy/openapi/v2/text-to-3d` answers with a `MeshyTextTo3DCreateResponse` handle whose `result` is the task id, and Router polls on the caller's behalf — so the body a caller receives is the finished task document rather than that handle. The family's three other operations (image-to-3d, multi-image-to-3d and retexture) stay reachable on their own `/proxy/meshy/...` routes and answer closely related task documents; a `{provider}/{model}` call settles onto the text-to-3d generation, which is the one this schema describes.\nThe generated mesh is in `model_urls`, at whichever of `glb`, `fbx`, `usdz` and `obj` the request produced — which of them are populated depends on the request, so read the one that is present rather than keying completion off `glb` alone. `glb` is the rendition a `{\"mode\": \"preview\"}` task with no format selector answers with, but it is not the only shape a success can take. Two neighbouring fields are NOT interchangeable with a mesh and a caller must not key completion off either: `mtl` is a material sidecar for `obj` rather than a mesh in its own right, and `thumbnail_url` is a preview image rather than the asset the call generated. Comfy Router draws exactly that line — a `SUCCEEDED` task carrying none of those four mesh URLs is answered as a Comfy Router error rather than with this document — so through Router a `200` here always carries a mesh, but not always the same one.\n`status` is Meshy's own UPPERCASE vocabulary — `PENDING`, `IN_PROGRESS`, `SUCCEEDED`, `FAILED`, `CANCELED` — forwarded unchanged and not case-folded. `SUCCEEDED` is the one terminal success; `FAILED` and `CANCELED` are terminal failures and reach a Router caller as a Comfy Router error rather than as this document, with the provider's own reason in `task_error.message`. `progress` runs 0..100 and is 100 on a succeeded task.\n`type` says which of the operation's two stages produced the document. `text-to-3d-preview` is the geometry stage, requested with `{\"mode\": \"preview\"}` and a prompt. `text-to-3d-refine` is the separate, dearer texturing task, requested with `{\"mode\": \"refine\"}` against a finished preview's `id` as `preview_task_id`; it is what populates `texture_urls`.\n`negative_prompt` and `texture_richness` are fields Meshy keeps for backward compatibility and does not populate meaningfully; `video_url` is likewise deprecated.","example":{"art_style":"realistic","created_at":1767225600000,"finished_at":1767225719000,"id":"018f2c7a-4b1e-7c3d-9a05-6e2f8b41d0c9","model_urls":{"fbx":"https://example.invalid/meshy/text-to-3d/model.fbx","glb":"https://example.invalid/meshy/text-to-3d/model.glb","mtl":"https://example.invalid/meshy/text-to-3d/model.mtl","obj":"https://example.invalid/meshy/text-to-3d/model.obj","usdz":"https://example.invalid/meshy/text-to-3d/model.usdz"},"progress":100,"prompt":"a red cube","started_at":1767225601000,"status":"SUCCEEDED","thumbnail_url":"https://example.invalid/meshy/text-to-3d/thumbnail.png","type":"text-to-3d-preview"}}}}}}}}},"components":{"schemas":{"MeshyArtStyle":{"default":"realistic","description":"Describe your desired art style of the object.","enum":["realistic","sculpture"],"type":"string"},"MeshyModelUrls":{"description":"Downloadable URLs to the textured 3D model files generated by Meshy.","properties":{"fbx":{"description":"Downloadable URL to the FBX file.","type":"string"},"glb":{"description":"Downloadable URL to the GLB file.","type":"string"},"mtl":{"description":"Downloadable URL to the MTL file.","type":"string"},"obj":{"description":"Downloadable URL to the OBJ file.","type":"string"},"usdz":{"description":"Downloadable URL to the USDZ file.","type":"string"}},"type":"object"},"MeshyPoseMode":{"description":"Specify the pose mode for the generated model.","enum":["a-pose","t-pose",""],"type":"string"},"MeshySymmetryMode":{"default":"auto","description":"Controls symmetry behavior during model generation.","enum":["off","auto","on"],"type":"string"},"MeshyTaskError":{"description":"Error object that contains the error message if the task failed.","properties":{"message":{"description":"Detailed error message.","type":"string"}},"type":"object"},"MeshyTaskStatus":{"description":"Status of the task.","enum":["PENDING","IN_PROGRESS","SUCCEEDED","FAILED","CANCELED"],"type":"string"},"MeshyTextTo3DInputs":{"description":"The model-agnostic Comfy Router input shape for the `meshy/*` text-to-3d models: either a PREVIEW body (`mode: preview`, which generates the untextured mesh) or a REFINE body (`mode: refine`, which textures a preview task's output).\nIt composes the same two branches the v1 `POST /proxy/meshy/openapi/v2/text-to-3d` body does, differing only in `ai_model`: on this route the model comes from the path (`/v2/models/meshy/{model}`), so `ai_model` may be omitted or sent as null and Comfy Router writes the path's model into it before the call is forwarded.","discriminator":{"mapping":{"preview":"#/components/schemas/MeshyTextTo3DPreviewInputs","refine":"#/components/schemas/MeshyTextTo3DRefineInputs"},"propertyName":"mode"},"oneOf":[{"$ref":"#/components/schemas/MeshyTextTo3DPreviewInputs"},{"$ref":"#/components/schemas/MeshyTextTo3DRefineInputs"}]},"MeshyTextTo3DPreviewInputs":{"description":"The meshy text-to-3d PREVIEW fields, shared by the `preview` branch of the v1 `POST /proxy/meshy/openapi/v2/text-to-3d` body and the `preview` branch of the Comfy Router input schema for the `meshy/*` models.\nThe two surfaces share every property and both `required` entries. They differ only in `ai_model`: the v1 body restates it as a fixed enum, because that route carries no path segment to supply the model from, while on the Comfy Router route the path supplies it.","properties":{"ai_model":{"description":"The Meshy model to run. On the Comfy Router route this is supplied by the path (`/v2/models/meshy/{model}`) and may be omitted or sent as null; Router writes the path's model here after validation. On the v1 /proxy route see MeshyTextTo3DPreviewRequest, which constrains it to MeshyAiModel.","nullable":true,"type":"string"},"art_style":{"$ref":"#/components/schemas/MeshyArtStyle"},"geometry_resolution":{"default":"standard","description":"Geometry resolution of the generated mesh. 2k and 4k run the Ultra pass for finer surface detail. 2k requires ai_model meshy-7, meshy-7.1 or latest; 4k requires meshy-7.1 or latest.","enum":["standard","2k","4k"],"type":"string"},"is_a_t_pose":{"default":false,"description":"Deprecated. Use pose_mode instead. Whether to generate the model in an A/T pose.","type":"boolean"},"mode":{"description":"This field should be set to \"preview\" when creating a preview task.","enum":["preview"],"type":"string"},"moderation":{"default":false,"description":"When true, input content will be screened for potentially harmful content.","type":"boolean"},"pose_mode":{"$ref":"#/components/schemas/MeshyPoseMode"},"prompt":{"description":"Describe what kind of object the 3D model is. Maximum 800 characters.","maxLength":800,"type":"string"},"should_remesh":{"default":false,"description":"Controls whether to enable the remesh phase. When false, returns highest-precision triangular mesh. Defaults to true for Meshy 5 and false for Meshy 6 and 7.","type":"boolean"},"symmetry_mode":{"$ref":"#/components/schemas/MeshySymmetryMode"},"target_polycount":{"default":30000,"description":"Specify the target number of polygons in the generated model. Valid range is 100 to 300,000.","maximum":300000,"minimum":100,"type":"integer"},"topology":{"$ref":"#/components/schemas/MeshyTopology"},"ultra_mode":{"default":false,"deprecated":true,"description":"Deprecated. Use geometry_resolution instead; true is equivalent to geometry_resolution 2k. Only supported when ai_model is meshy-7, meshy-7.1 or latest.","type":"boolean"}},"required":["mode","prompt"],"type":"object"},"MeshyTextTo3DRefineInputs":{"description":"The meshy text-to-3d REFINE fields, shared by the `refine` branch of the v1 `POST /proxy/meshy/openapi/v2/text-to-3d` body and the `refine` branch of the Comfy Router input schema for the `meshy/*` models.\nThe two surfaces share every property and both `required` entries. They differ only in `ai_model`: the v1 body restates it as a fixed enum, because that route carries no path segment to supply the model from, while on the Comfy Router route the path supplies it.","properties":{"ai_model":{"description":"The Meshy model to run. On the Comfy Router route this is supplied by the path (`/v2/models/meshy/{model}`) and may be omitted or sent as null; Router writes the path's model here after validation. On the v1 /proxy route see MeshyTextTo3DRefineRequest, which constrains it to MeshyAiModel.","nullable":true,"type":"string"},"enable_pbr":{"default":false,"description":"Generate PBR Maps (metallic, roughness, normal) in addition to the base color. Note that enable_pbr should be set to false when using Sculpture style.","type":"boolean"},"mode":{"description":"This field should be set to \"refine\" when creating a refine task.","enum":["refine"],"type":"string"},"moderation":{"default":false,"description":"When true, input content will be screened for potentially harmful content.","type":"boolean"},"preview_task_id":{"description":"The corresponding preview task id. The status of the given preview task must be SUCCEEDED.","type":"string"},"texture_image_url":{"description":"Provide a 2d image to guide the texturing process. Supports .jpg, .jpeg, .png formats or base64-encoded data URI.","type":"string"},"texture_prompt":{"description":"Provide an additional text prompt to guide the texturing process. Maximum 800 characters.","maxLength":800,"type":"string"},"texture_resolution":{"default":"2k","description":"Texture resolution of the generated textures. One of 2k, 4k or 8k. 4k and 8k require ai_model meshy-6, meshy-7, meshy-7.1 or latest.","enum":["2k","4k","8k"],"type":"string"}},"required":["mode","preview_task_id"],"type":"object"},"MeshyTextTo3DTask":{"properties":{"art_style":{"description":"The unmodified art_style that was used to create the preview task.","type":"string"},"created_at":{"description":"Timestamp of when the task was created, in milliseconds.","format":"int64","type":"integer"},"finished_at":{"description":"Timestamp of when the task was finished, in milliseconds. 0 if not finished.","format":"int64","type":"integer"},"id":{"description":"Unique identifier for the task.","type":"string"},"model_urls":{"$ref":"#/components/schemas/MeshyModelUrls"},"negative_prompt":{"description":"Deprecated field maintained for backward compatibility.","type":"string"},"preceding_tasks":{"description":"The count of preceding tasks. Only meaningful when status is PENDING.","type":"integer"},"progress":{"description":"Progress of the task. 0 if not started, 100 when succeeded.","maximum":100,"minimum":0,"type":"integer"},"prompt":{"description":"The unmodified prompt that was used to create the task.","type":"string"},"started_at":{"description":"Timestamp of when the task was started, in milliseconds. 0 if not started.","format":"int64","type":"integer"},"status":{"$ref":"#/components/schemas/MeshyTaskStatus"},"task_error":{"allOf":[{"$ref":"#/components/schemas/MeshyTaskError"}],"nullable":true},"texture_image_url":{"description":"Downloadable URL to the texture image that was used to guide the texturing process.","type":"string"},"texture_prompt":{"description":"Additional text prompt provided to guide the texturing process during the refine stage.","type":"string"},"texture_richness":{"description":"Deprecated field maintained for backward compatibility.","type":"string"},"texture_urls":{"description":"An array of texture URL objects that are generated from the task.","items":{"$ref":"#/components/schemas/MeshyTextureUrls"},"type":"array"},"thumbnail_url":{"description":"Downloadable URL to the thumbnail image of the model file.","type":"string"},"type":{"description":"Type of the Text to 3D task.","enum":["text-to-3d-preview","text-to-3d-refine"],"type":"string"},"video_url":{"description":"Deprecated field returning the downloadable URL to the preview video.","type":"string"}},"required":["id","status"],"type":"object"},"MeshyTextureUrls":{"description":"Texture URL object containing PBR maps.","properties":{"base_color":{"description":"Downloadable URL to the base color map image.","type":"string"},"metallic":{"description":"Downloadable URL to the metallic map image.","type":"string"},"normal":{"description":"Downloadable URL to the normal map image.","type":"string"},"roughness":{"description":"Downloadable URL to the roughness map image.","type":"string"}},"type":"object"},"MeshyTopology":{"default":"triangle","description":"Specify the topology of the generated model.","enum":["quad","triangle"],"type":"string"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"meshy/meshy-6","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text"],"x-comfy-router-output-modalities":["3d"]}
+{"openapi":"3.0.2","info":{"title":"meshy/meshy-6","description":"The request body Comfy Router accepts for the model \"meshy/meshy-6\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"40b613556857"},"paths":{"/v2/models/meshy/meshy-6":{"post":{"operationId":"runRouterModel","summary":"Run meshy/meshy-6 synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/MeshyTextTo3DInputs"},{"properties":{"geometry_resolution":{"default":"standard","description":"Geometry resolution of the generated mesh. meshy-6 supports `standard` only; `2k` and `4k` require ai_model meshy-7.1 or latest.","enum":["standard"],"type":"string"},"ultra_mode":{"default":false,"description":"Not supported on meshy-6, which accepts only `false`. Ultra generation requires ai_model meshy-7, meshy-7.1 or latest (and is a preview-mode option); address meshy/meshy-7 or meshy/meshy-7.1 to use it.","enum":[false],"type":"boolean"}},"type":"object"}],"description":"The Comfy Router input schema for `meshy/meshy-6` text-to-3d.\nmeshy-6 supports the full `2k`/`4k`/`8k` texture range but NOT Ultra generation, so this component narrows `ultra_mode` to `false` and `geometry_resolution` to `standard`, and leaves `texture_resolution` at the shared enum. Everything else is the shared shape in MeshyTextTo3DInputs.","example":{"art_style":"realistic","mode":"preview","prompt":"a red cube"}}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/MeshyTextTo3DTask"},{"properties":{"status":{"enum":["SUCCEEDED"],"type":"string"}},"required":["status"],"type":"object"}],"description":"Comfy Router output schema for the Meshy text-to-3D models: the terminal `GET /proxy/meshy/openapi/v2/text-to-3d/{task_id}` task document, forwarded unchanged. The operation is SUBMIT-AND-POLL — `POST /proxy/meshy/openapi/v2/text-to-3d` answers with a `MeshyTextTo3DCreateResponse` handle whose `result` is the task id, and Router polls on the caller's behalf — so the body a caller receives is the finished task document rather than that handle. The family's three other operations (image-to-3d, multi-image-to-3d and retexture) stay reachable on their own `/proxy/meshy/...` routes and answer closely related task documents; a `{provider}/{model}` call settles onto the text-to-3d generation, which is the one this schema describes.\nThe generated mesh is in `model_urls`, at whichever of `glb`, `fbx`, `usdz` and `obj` the request produced — which of them are populated depends on the request, so read the one that is present rather than keying completion off `glb` alone. `glb` is the rendition a `{\"mode\": \"preview\"}` task with no format selector answers with, but it is not the only shape a success can take. Two neighbouring fields are NOT interchangeable with a mesh and a caller must not key completion off either: `mtl` is a material sidecar for `obj` rather than a mesh in its own right, and `thumbnail_url` is a preview image rather than the asset the call generated. Comfy Router draws exactly that line — a `SUCCEEDED` task carrying none of those four mesh URLs is answered as a Comfy Router error rather than with this document — so through Router a `200` here always carries a mesh, but not always the same one.\n`status` is Meshy's own UPPERCASE vocabulary — `PENDING`, `IN_PROGRESS`, `SUCCEEDED`, `FAILED`, `CANCELED` — forwarded unchanged and not case-folded. `SUCCEEDED` is the one terminal success; `FAILED` and `CANCELED` are terminal failures and reach a Router caller as a Comfy Router error rather than as this document, with the provider's own reason in `task_error.message`. `progress` runs 0..100 and is 100 on a succeeded task.\n`type` says which of the operation's two stages produced the document. `text-to-3d-preview` is the geometry stage, requested with `{\"mode\": \"preview\"}` and a prompt. `text-to-3d-refine` is the separate, dearer texturing task, requested with `{\"mode\": \"refine\"}` against a finished preview's `id` as `preview_task_id`; it is what populates `texture_urls`.\n`negative_prompt` and `texture_richness` are fields Meshy keeps for backward compatibility and does not populate meaningfully; `video_url` is likewise deprecated.","example":{"art_style":"realistic","created_at":1767225600000,"finished_at":1767225719000,"id":"018f2c7a-4b1e-7c3d-9a05-6e2f8b41d0c9","model_urls":{"fbx":"https://example.invalid/meshy/text-to-3d/model.fbx","glb":"https://example.invalid/meshy/text-to-3d/model.glb","mtl":"https://example.invalid/meshy/text-to-3d/model.mtl","obj":"https://example.invalid/meshy/text-to-3d/model.obj","usdz":"https://example.invalid/meshy/text-to-3d/model.usdz"},"progress":100,"prompt":"a red cube","started_at":1767225601000,"status":"SUCCEEDED","thumbnail_url":"https://example.invalid/meshy/text-to-3d/thumbnail.png","type":"text-to-3d-preview"}}}}}}}}},"components":{"schemas":{"MeshyArtStyle":{"default":"realistic","description":"Describe your desired art style of the object.","enum":["realistic","sculpture"],"type":"string"},"MeshyModelUrls":{"description":"Downloadable URLs to the textured 3D model files generated by Meshy.","properties":{"fbx":{"description":"Downloadable URL to the FBX file.","type":"string"},"glb":{"description":"Downloadable URL to the GLB file.","type":"string"},"mtl":{"description":"Downloadable URL to the MTL file.","type":"string"},"obj":{"description":"Downloadable URL to the OBJ file.","type":"string"},"usdz":{"description":"Downloadable URL to the USDZ file.","type":"string"}},"type":"object"},"MeshyPoseMode":{"description":"Specify the pose mode for the generated model.","enum":["a-pose","t-pose",""],"type":"string"},"MeshySymmetryMode":{"default":"auto","description":"Controls symmetry behavior during model generation.","enum":["off","auto","on"],"type":"string"},"MeshyTaskError":{"description":"Error object that contains the error message if the task failed.","properties":{"message":{"description":"Detailed error message.","type":"string"}},"type":"object"},"MeshyTaskStatus":{"description":"Status of the task.","enum":["PENDING","IN_PROGRESS","SUCCEEDED","FAILED","CANCELED"],"type":"string"},"MeshyTextTo3DInputs":{"description":"The model-agnostic Comfy Router input shape for the `meshy/*` text-to-3d models: either a PREVIEW body (`mode: preview`, which generates the untextured mesh) or a REFINE body (`mode: refine`, which textures a preview task's output).\nIt composes the same two branches the v1 `POST /proxy/meshy/openapi/v2/text-to-3d` body does, differing only in `ai_model`: on this route the model comes from the path (`/v2/models/meshy/{model}`), so `ai_model` may be omitted or sent as null and Comfy Router writes the path's model into it before the call is forwarded.","discriminator":{"mapping":{"preview":"#/components/schemas/MeshyTextTo3DPreviewInputs","refine":"#/components/schemas/MeshyTextTo3DRefineInputs"},"propertyName":"mode"},"oneOf":[{"$ref":"#/components/schemas/MeshyTextTo3DPreviewInputs"},{"$ref":"#/components/schemas/MeshyTextTo3DRefineInputs"}]},"MeshyTextTo3DPreviewInputs":{"description":"The meshy text-to-3d PREVIEW fields, shared by the `preview` branch of the v1 `POST /proxy/meshy/openapi/v2/text-to-3d` body and the `preview` branch of the Comfy Router input schema for the `meshy/*` models.\nThe two surfaces share every property and both `required` entries. They differ only in `ai_model`: the v1 body restates it as a fixed enum, because that route carries no path segment to supply the model from, while on the Comfy Router route the path supplies it.","properties":{"ai_model":{"description":"The Meshy model to run. On the Comfy Router route this is supplied by the path (`/v2/models/meshy/{model}`) and may be omitted or sent as null; Router writes the path's model here after validation. On the v1 /proxy route see MeshyTextTo3DPreviewRequest, which constrains it to MeshyAiModel.","nullable":true,"type":"string"},"art_style":{"$ref":"#/components/schemas/MeshyArtStyle"},"geometry_resolution":{"default":"standard","description":"Geometry resolution of the generated mesh. 2k and 4k run the Ultra pass for finer surface detail. 2k and 4k require ai_model meshy-7.1 or latest.","enum":["standard","2k","4k"],"type":"string"},"is_a_t_pose":{"default":false,"description":"Deprecated. Use pose_mode instead. Whether to generate the model in an A/T pose.","type":"boolean"},"mode":{"description":"This field should be set to \"preview\" when creating a preview task.","enum":["preview"],"type":"string"},"moderation":{"default":false,"description":"When true, input content will be screened for potentially harmful content.","type":"boolean"},"pose_mode":{"$ref":"#/components/schemas/MeshyPoseMode"},"prompt":{"description":"Describe what kind of object the 3D model is. Maximum 800 characters.","maxLength":800,"type":"string"},"should_remesh":{"default":false,"description":"Controls whether to enable the remesh phase. When false, returns highest-precision triangular mesh. Defaults to true for Meshy 5 and false for Meshy 6 and 7.","type":"boolean"},"symmetry_mode":{"$ref":"#/components/schemas/MeshySymmetryMode"},"target_polycount":{"default":30000,"description":"Specify the target number of polygons in the generated model. Valid range is 100 to 300,000.","maximum":300000,"minimum":100,"type":"integer"},"topology":{"$ref":"#/components/schemas/MeshyTopology"},"ultra_mode":{"default":false,"deprecated":true,"description":"Deprecated. Use geometry_resolution instead; true is equivalent to geometry_resolution 2k. Only supported when ai_model is meshy-7, meshy-7.1 or latest. The exception is meshy-7: geometry_resolution 2k requires meshy-7.1 or latest, so on meshy-7 ultra_mode true is the way to request the Ultra pass.","type":"boolean"}},"required":["mode","prompt"],"type":"object"},"MeshyTextTo3DRefineInputs":{"description":"The meshy text-to-3d REFINE fields, shared by the `refine` branch of the v1 `POST /proxy/meshy/openapi/v2/text-to-3d` body and the `refine` branch of the Comfy Router input schema for the `meshy/*` models.\nThe two surfaces share every property and both `required` entries. They differ only in `ai_model`: the v1 body restates it as a fixed enum, because that route carries no path segment to supply the model from, while on the Comfy Router route the path supplies it.","properties":{"ai_model":{"description":"The Meshy model to run. On the Comfy Router route this is supplied by the path (`/v2/models/meshy/{model}`) and may be omitted or sent as null; Router writes the path's model here after validation. On the v1 /proxy route see MeshyTextTo3DRefineRequest, which constrains it to MeshyAiModel.","nullable":true,"type":"string"},"enable_pbr":{"default":false,"description":"Generate PBR Maps (metallic, roughness, normal) in addition to the base color. Note that enable_pbr should be set to false when using Sculpture style.","type":"boolean"},"mode":{"description":"This field should be set to \"refine\" when creating a refine task.","enum":["refine"],"type":"string"},"moderation":{"default":false,"description":"When true, input content will be screened for potentially harmful content.","type":"boolean"},"preview_task_id":{"description":"The corresponding preview task id. The status of the given preview task must be SUCCEEDED.","type":"string"},"texture_image_url":{"description":"Provide a 2d image to guide the texturing process. Supports .jpg, .jpeg, .png formats or base64-encoded data URI.","type":"string"},"texture_prompt":{"description":"Provide an additional text prompt to guide the texturing process. Maximum 800 characters.","maxLength":800,"type":"string"},"texture_resolution":{"default":"2k","description":"Texture resolution of the generated textures. One of 2k, 4k or 8k. 4k and 8k require ai_model meshy-6, meshy-7, meshy-7.1 or latest.","enum":["2k","4k","8k"],"type":"string"}},"required":["mode","preview_task_id"],"type":"object"},"MeshyTextTo3DTask":{"properties":{"art_style":{"description":"The unmodified art_style that was used to create the preview task.","type":"string"},"created_at":{"description":"Timestamp of when the task was created, in milliseconds.","format":"int64","type":"integer"},"finished_at":{"description":"Timestamp of when the task was finished, in milliseconds. 0 if not finished.","format":"int64","type":"integer"},"id":{"description":"Unique identifier for the task.","type":"string"},"model_urls":{"$ref":"#/components/schemas/MeshyModelUrls"},"negative_prompt":{"description":"Deprecated field maintained for backward compatibility.","type":"string"},"preceding_tasks":{"description":"The count of preceding tasks. Only meaningful when status is PENDING.","type":"integer"},"progress":{"description":"Progress of the task. 0 if not started, 100 when succeeded.","maximum":100,"minimum":0,"type":"integer"},"prompt":{"description":"The unmodified prompt that was used to create the task.","type":"string"},"started_at":{"description":"Timestamp of when the task was started, in milliseconds. 0 if not started.","format":"int64","type":"integer"},"status":{"$ref":"#/components/schemas/MeshyTaskStatus"},"task_error":{"allOf":[{"$ref":"#/components/schemas/MeshyTaskError"}],"nullable":true},"texture_image_url":{"description":"Downloadable URL to the texture image that was used to guide the texturing process.","type":"string"},"texture_prompt":{"description":"Additional text prompt provided to guide the texturing process during the refine stage.","type":"string"},"texture_richness":{"description":"Deprecated field maintained for backward compatibility.","type":"string"},"texture_urls":{"description":"An array of texture URL objects that are generated from the task.","items":{"$ref":"#/components/schemas/MeshyTextureUrls"},"type":"array"},"thumbnail_url":{"description":"Downloadable URL to the thumbnail image of the model file.","type":"string"},"type":{"description":"Type of the Text to 3D task.","enum":["text-to-3d-preview","text-to-3d-refine"],"type":"string"},"video_url":{"description":"Deprecated field returning the downloadable URL to the preview video.","type":"string"}},"required":["id","status"],"type":"object"},"MeshyTextureUrls":{"description":"Texture URL object containing PBR maps.","properties":{"base_color":{"description":"Downloadable URL to the base color map image.","type":"string"},"metallic":{"description":"Downloadable URL to the metallic map image.","type":"string"},"normal":{"description":"Downloadable URL to the normal map image.","type":"string"},"roughness":{"description":"Downloadable URL to the roughness map image.","type":"string"}},"type":"object"},"MeshyTopology":{"default":"triangle","description":"Specify the topology of the generated model.","enum":["quad","triangle"],"type":"string"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"meshy/meshy-6","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text"],"x-comfy-router-output-modalities":["3d"]}
diff --git a/router-schemas/meshy/meshy-7.1.json b/router-schemas/meshy/meshy-7.1.json
index 12e3dafa78..8d3183d177 100644
--- a/router-schemas/meshy/meshy-7.1.json
+++ b/router-schemas/meshy/meshy-7.1.json
@@ -1 +1 @@
-{"openapi":"3.0.2","info":{"title":"meshy/meshy-7.1","description":"The request body Comfy Router accepts for the model \"meshy/meshy-7.1\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"5498d81fdaad"},"paths":{"/v2/models/meshy/meshy-7.1":{"post":{"operationId":"runRouterModel","summary":"Run meshy/meshy-7.1 synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/MeshyTextTo3DInputs"}],"description":"The Comfy Router input schema for `meshy/meshy-7.1` text-to-3d.\nmeshy-7.1 (PN-1223) is the tier every conditional option is available on -- Ultra generation (`ultra_mode`, preview mode), the full `standard`/`2k`/`4k` geometry range, and the full `2k`/`4k`/`8k` texture range -- so it narrows nothing and is the shared shape in MeshyTextTo3DInputs under its own id.","example":{"art_style":"realistic","mode":"preview","prompt":"a red cube"}}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/MeshyTextTo3DTask"},{"properties":{"status":{"enum":["SUCCEEDED"],"type":"string"}},"required":["status"],"type":"object"}],"description":"Comfy Router output schema for the Meshy text-to-3D models: the terminal `GET /proxy/meshy/openapi/v2/text-to-3d/{task_id}` task document, forwarded unchanged. The operation is SUBMIT-AND-POLL — `POST /proxy/meshy/openapi/v2/text-to-3d` answers with a `MeshyTextTo3DCreateResponse` handle whose `result` is the task id, and Router polls on the caller's behalf — so the body a caller receives is the finished task document rather than that handle. The family's three other operations (image-to-3d, multi-image-to-3d and retexture) stay reachable on their own `/proxy/meshy/...` routes and answer closely related task documents; a `{provider}/{model}` call settles onto the text-to-3d generation, which is the one this schema describes.\nThe generated mesh is in `model_urls`, at whichever of `glb`, `fbx`, `usdz` and `obj` the request produced — which of them are populated depends on the request, so read the one that is present rather than keying completion off `glb` alone. `glb` is the rendition a `{\"mode\": \"preview\"}` task with no format selector answers with, but it is not the only shape a success can take. Two neighbouring fields are NOT interchangeable with a mesh and a caller must not key completion off either: `mtl` is a material sidecar for `obj` rather than a mesh in its own right, and `thumbnail_url` is a preview image rather than the asset the call generated. Comfy Router draws exactly that line — a `SUCCEEDED` task carrying none of those four mesh URLs is answered as a Comfy Router error rather than with this document — so through Router a `200` here always carries a mesh, but not always the same one.\n`status` is Meshy's own UPPERCASE vocabulary — `PENDING`, `IN_PROGRESS`, `SUCCEEDED`, `FAILED`, `CANCELED` — forwarded unchanged and not case-folded. `SUCCEEDED` is the one terminal success; `FAILED` and `CANCELED` are terminal failures and reach a Router caller as a Comfy Router error rather than as this document, with the provider's own reason in `task_error.message`. `progress` runs 0..100 and is 100 on a succeeded task.\n`type` says which of the operation's two stages produced the document. `text-to-3d-preview` is the geometry stage, requested with `{\"mode\": \"preview\"}` and a prompt. `text-to-3d-refine` is the separate, dearer texturing task, requested with `{\"mode\": \"refine\"}` against a finished preview's `id` as `preview_task_id`; it is what populates `texture_urls`.\n`negative_prompt` and `texture_richness` are fields Meshy keeps for backward compatibility and does not populate meaningfully; `video_url` is likewise deprecated.","example":{"art_style":"realistic","created_at":1767225600000,"finished_at":1767225719000,"id":"018f2c7a-4b1e-7c3d-9a05-6e2f8b41d0c9","model_urls":{"fbx":"https://example.invalid/meshy/text-to-3d/model.fbx","glb":"https://example.invalid/meshy/text-to-3d/model.glb","mtl":"https://example.invalid/meshy/text-to-3d/model.mtl","obj":"https://example.invalid/meshy/text-to-3d/model.obj","usdz":"https://example.invalid/meshy/text-to-3d/model.usdz"},"progress":100,"prompt":"a red cube","started_at":1767225601000,"status":"SUCCEEDED","thumbnail_url":"https://example.invalid/meshy/text-to-3d/thumbnail.png","type":"text-to-3d-preview"}}}}}}}}},"components":{"schemas":{"MeshyArtStyle":{"default":"realistic","description":"Describe your desired art style of the object.","enum":["realistic","sculpture"],"type":"string"},"MeshyModelUrls":{"description":"Downloadable URLs to the textured 3D model files generated by Meshy.","properties":{"fbx":{"description":"Downloadable URL to the FBX file.","type":"string"},"glb":{"description":"Downloadable URL to the GLB file.","type":"string"},"mtl":{"description":"Downloadable URL to the MTL file.","type":"string"},"obj":{"description":"Downloadable URL to the OBJ file.","type":"string"},"usdz":{"description":"Downloadable URL to the USDZ file.","type":"string"}},"type":"object"},"MeshyPoseMode":{"description":"Specify the pose mode for the generated model.","enum":["a-pose","t-pose",""],"type":"string"},"MeshySymmetryMode":{"default":"auto","description":"Controls symmetry behavior during model generation.","enum":["off","auto","on"],"type":"string"},"MeshyTaskError":{"description":"Error object that contains the error message if the task failed.","properties":{"message":{"description":"Detailed error message.","type":"string"}},"type":"object"},"MeshyTaskStatus":{"description":"Status of the task.","enum":["PENDING","IN_PROGRESS","SUCCEEDED","FAILED","CANCELED"],"type":"string"},"MeshyTextTo3DInputs":{"description":"The model-agnostic Comfy Router input shape for the `meshy/*` text-to-3d models: either a PREVIEW body (`mode: preview`, which generates the untextured mesh) or a REFINE body (`mode: refine`, which textures a preview task's output).\nIt composes the same two branches the v1 `POST /proxy/meshy/openapi/v2/text-to-3d` body does, differing only in `ai_model`: on this route the model comes from the path (`/v2/models/meshy/{model}`), so `ai_model` may be omitted or sent as null and Comfy Router writes the path's model into it before the call is forwarded.","discriminator":{"mapping":{"preview":"#/components/schemas/MeshyTextTo3DPreviewInputs","refine":"#/components/schemas/MeshyTextTo3DRefineInputs"},"propertyName":"mode"},"oneOf":[{"$ref":"#/components/schemas/MeshyTextTo3DPreviewInputs"},{"$ref":"#/components/schemas/MeshyTextTo3DRefineInputs"}]},"MeshyTextTo3DPreviewInputs":{"description":"The meshy text-to-3d PREVIEW fields, shared by the `preview` branch of the v1 `POST /proxy/meshy/openapi/v2/text-to-3d` body and the `preview` branch of the Comfy Router input schema for the `meshy/*` models.\nThe two surfaces share every property and both `required` entries. They differ only in `ai_model`: the v1 body restates it as a fixed enum, because that route carries no path segment to supply the model from, while on the Comfy Router route the path supplies it.","properties":{"ai_model":{"description":"The Meshy model to run. On the Comfy Router route this is supplied by the path (`/v2/models/meshy/{model}`) and may be omitted or sent as null; Router writes the path's model here after validation. On the v1 /proxy route see MeshyTextTo3DPreviewRequest, which constrains it to MeshyAiModel.","nullable":true,"type":"string"},"art_style":{"$ref":"#/components/schemas/MeshyArtStyle"},"geometry_resolution":{"default":"standard","description":"Geometry resolution of the generated mesh. 2k and 4k run the Ultra pass for finer surface detail. 2k requires ai_model meshy-7, meshy-7.1 or latest; 4k requires meshy-7.1 or latest.","enum":["standard","2k","4k"],"type":"string"},"is_a_t_pose":{"default":false,"description":"Deprecated. Use pose_mode instead. Whether to generate the model in an A/T pose.","type":"boolean"},"mode":{"description":"This field should be set to \"preview\" when creating a preview task.","enum":["preview"],"type":"string"},"moderation":{"default":false,"description":"When true, input content will be screened for potentially harmful content.","type":"boolean"},"pose_mode":{"$ref":"#/components/schemas/MeshyPoseMode"},"prompt":{"description":"Describe what kind of object the 3D model is. Maximum 800 characters.","maxLength":800,"type":"string"},"should_remesh":{"default":false,"description":"Controls whether to enable the remesh phase. When false, returns highest-precision triangular mesh. Defaults to true for Meshy 5 and false for Meshy 6 and 7.","type":"boolean"},"symmetry_mode":{"$ref":"#/components/schemas/MeshySymmetryMode"},"target_polycount":{"default":30000,"description":"Specify the target number of polygons in the generated model. Valid range is 100 to 300,000.","maximum":300000,"minimum":100,"type":"integer"},"topology":{"$ref":"#/components/schemas/MeshyTopology"},"ultra_mode":{"default":false,"deprecated":true,"description":"Deprecated. Use geometry_resolution instead; true is equivalent to geometry_resolution 2k. Only supported when ai_model is meshy-7, meshy-7.1 or latest.","type":"boolean"}},"required":["mode","prompt"],"type":"object"},"MeshyTextTo3DRefineInputs":{"description":"The meshy text-to-3d REFINE fields, shared by the `refine` branch of the v1 `POST /proxy/meshy/openapi/v2/text-to-3d` body and the `refine` branch of the Comfy Router input schema for the `meshy/*` models.\nThe two surfaces share every property and both `required` entries. They differ only in `ai_model`: the v1 body restates it as a fixed enum, because that route carries no path segment to supply the model from, while on the Comfy Router route the path supplies it.","properties":{"ai_model":{"description":"The Meshy model to run. On the Comfy Router route this is supplied by the path (`/v2/models/meshy/{model}`) and may be omitted or sent as null; Router writes the path's model here after validation. On the v1 /proxy route see MeshyTextTo3DRefineRequest, which constrains it to MeshyAiModel.","nullable":true,"type":"string"},"enable_pbr":{"default":false,"description":"Generate PBR Maps (metallic, roughness, normal) in addition to the base color. Note that enable_pbr should be set to false when using Sculpture style.","type":"boolean"},"mode":{"description":"This field should be set to \"refine\" when creating a refine task.","enum":["refine"],"type":"string"},"moderation":{"default":false,"description":"When true, input content will be screened for potentially harmful content.","type":"boolean"},"preview_task_id":{"description":"The corresponding preview task id. The status of the given preview task must be SUCCEEDED.","type":"string"},"texture_image_url":{"description":"Provide a 2d image to guide the texturing process. Supports .jpg, .jpeg, .png formats or base64-encoded data URI.","type":"string"},"texture_prompt":{"description":"Provide an additional text prompt to guide the texturing process. Maximum 800 characters.","maxLength":800,"type":"string"},"texture_resolution":{"default":"2k","description":"Texture resolution of the generated textures. One of 2k, 4k or 8k. 4k and 8k require ai_model meshy-6, meshy-7, meshy-7.1 or latest.","enum":["2k","4k","8k"],"type":"string"}},"required":["mode","preview_task_id"],"type":"object"},"MeshyTextTo3DTask":{"properties":{"art_style":{"description":"The unmodified art_style that was used to create the preview task.","type":"string"},"created_at":{"description":"Timestamp of when the task was created, in milliseconds.","format":"int64","type":"integer"},"finished_at":{"description":"Timestamp of when the task was finished, in milliseconds. 0 if not finished.","format":"int64","type":"integer"},"id":{"description":"Unique identifier for the task.","type":"string"},"model_urls":{"$ref":"#/components/schemas/MeshyModelUrls"},"negative_prompt":{"description":"Deprecated field maintained for backward compatibility.","type":"string"},"preceding_tasks":{"description":"The count of preceding tasks. Only meaningful when status is PENDING.","type":"integer"},"progress":{"description":"Progress of the task. 0 if not started, 100 when succeeded.","maximum":100,"minimum":0,"type":"integer"},"prompt":{"description":"The unmodified prompt that was used to create the task.","type":"string"},"started_at":{"description":"Timestamp of when the task was started, in milliseconds. 0 if not started.","format":"int64","type":"integer"},"status":{"$ref":"#/components/schemas/MeshyTaskStatus"},"task_error":{"allOf":[{"$ref":"#/components/schemas/MeshyTaskError"}],"nullable":true},"texture_image_url":{"description":"Downloadable URL to the texture image that was used to guide the texturing process.","type":"string"},"texture_prompt":{"description":"Additional text prompt provided to guide the texturing process during the refine stage.","type":"string"},"texture_richness":{"description":"Deprecated field maintained for backward compatibility.","type":"string"},"texture_urls":{"description":"An array of texture URL objects that are generated from the task.","items":{"$ref":"#/components/schemas/MeshyTextureUrls"},"type":"array"},"thumbnail_url":{"description":"Downloadable URL to the thumbnail image of the model file.","type":"string"},"type":{"description":"Type of the Text to 3D task.","enum":["text-to-3d-preview","text-to-3d-refine"],"type":"string"},"video_url":{"description":"Deprecated field returning the downloadable URL to the preview video.","type":"string"}},"required":["id","status"],"type":"object"},"MeshyTextureUrls":{"description":"Texture URL object containing PBR maps.","properties":{"base_color":{"description":"Downloadable URL to the base color map image.","type":"string"},"metallic":{"description":"Downloadable URL to the metallic map image.","type":"string"},"normal":{"description":"Downloadable URL to the normal map image.","type":"string"},"roughness":{"description":"Downloadable URL to the roughness map image.","type":"string"}},"type":"object"},"MeshyTopology":{"default":"triangle","description":"Specify the topology of the generated model.","enum":["quad","triangle"],"type":"string"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"meshy/meshy-7.1","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text"],"x-comfy-router-output-modalities":["3d"]}
+{"openapi":"3.0.2","info":{"title":"meshy/meshy-7.1","description":"The request body Comfy Router accepts for the model \"meshy/meshy-7.1\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"899cd9077376"},"paths":{"/v2/models/meshy/meshy-7.1":{"post":{"operationId":"runRouterModel","summary":"Run meshy/meshy-7.1 synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/MeshyTextTo3DInputs"}],"description":"The Comfy Router input schema for `meshy/meshy-7.1` text-to-3d.\nmeshy-7.1 (PN-1223) is the tier every conditional option is available on -- Ultra generation (`ultra_mode`, preview mode), the full `standard`/`2k`/`4k` geometry range, and the full `2k`/`4k`/`8k` texture range -- so it narrows nothing and is the shared shape in MeshyTextTo3DInputs under its own id.","example":{"art_style":"realistic","mode":"preview","prompt":"a red cube"}}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/MeshyTextTo3DTask"},{"properties":{"status":{"enum":["SUCCEEDED"],"type":"string"}},"required":["status"],"type":"object"}],"description":"Comfy Router output schema for the Meshy text-to-3D models: the terminal `GET /proxy/meshy/openapi/v2/text-to-3d/{task_id}` task document, forwarded unchanged. The operation is SUBMIT-AND-POLL — `POST /proxy/meshy/openapi/v2/text-to-3d` answers with a `MeshyTextTo3DCreateResponse` handle whose `result` is the task id, and Router polls on the caller's behalf — so the body a caller receives is the finished task document rather than that handle. The family's three other operations (image-to-3d, multi-image-to-3d and retexture) stay reachable on their own `/proxy/meshy/...` routes and answer closely related task documents; a `{provider}/{model}` call settles onto the text-to-3d generation, which is the one this schema describes.\nThe generated mesh is in `model_urls`, at whichever of `glb`, `fbx`, `usdz` and `obj` the request produced — which of them are populated depends on the request, so read the one that is present rather than keying completion off `glb` alone. `glb` is the rendition a `{\"mode\": \"preview\"}` task with no format selector answers with, but it is not the only shape a success can take. Two neighbouring fields are NOT interchangeable with a mesh and a caller must not key completion off either: `mtl` is a material sidecar for `obj` rather than a mesh in its own right, and `thumbnail_url` is a preview image rather than the asset the call generated. Comfy Router draws exactly that line — a `SUCCEEDED` task carrying none of those four mesh URLs is answered as a Comfy Router error rather than with this document — so through Router a `200` here always carries a mesh, but not always the same one.\n`status` is Meshy's own UPPERCASE vocabulary — `PENDING`, `IN_PROGRESS`, `SUCCEEDED`, `FAILED`, `CANCELED` — forwarded unchanged and not case-folded. `SUCCEEDED` is the one terminal success; `FAILED` and `CANCELED` are terminal failures and reach a Router caller as a Comfy Router error rather than as this document, with the provider's own reason in `task_error.message`. `progress` runs 0..100 and is 100 on a succeeded task.\n`type` says which of the operation's two stages produced the document. `text-to-3d-preview` is the geometry stage, requested with `{\"mode\": \"preview\"}` and a prompt. `text-to-3d-refine` is the separate, dearer texturing task, requested with `{\"mode\": \"refine\"}` against a finished preview's `id` as `preview_task_id`; it is what populates `texture_urls`.\n`negative_prompt` and `texture_richness` are fields Meshy keeps for backward compatibility and does not populate meaningfully; `video_url` is likewise deprecated.","example":{"art_style":"realistic","created_at":1767225600000,"finished_at":1767225719000,"id":"018f2c7a-4b1e-7c3d-9a05-6e2f8b41d0c9","model_urls":{"fbx":"https://example.invalid/meshy/text-to-3d/model.fbx","glb":"https://example.invalid/meshy/text-to-3d/model.glb","mtl":"https://example.invalid/meshy/text-to-3d/model.mtl","obj":"https://example.invalid/meshy/text-to-3d/model.obj","usdz":"https://example.invalid/meshy/text-to-3d/model.usdz"},"progress":100,"prompt":"a red cube","started_at":1767225601000,"status":"SUCCEEDED","thumbnail_url":"https://example.invalid/meshy/text-to-3d/thumbnail.png","type":"text-to-3d-preview"}}}}}}}}},"components":{"schemas":{"MeshyArtStyle":{"default":"realistic","description":"Describe your desired art style of the object.","enum":["realistic","sculpture"],"type":"string"},"MeshyModelUrls":{"description":"Downloadable URLs to the textured 3D model files generated by Meshy.","properties":{"fbx":{"description":"Downloadable URL to the FBX file.","type":"string"},"glb":{"description":"Downloadable URL to the GLB file.","type":"string"},"mtl":{"description":"Downloadable URL to the MTL file.","type":"string"},"obj":{"description":"Downloadable URL to the OBJ file.","type":"string"},"usdz":{"description":"Downloadable URL to the USDZ file.","type":"string"}},"type":"object"},"MeshyPoseMode":{"description":"Specify the pose mode for the generated model.","enum":["a-pose","t-pose",""],"type":"string"},"MeshySymmetryMode":{"default":"auto","description":"Controls symmetry behavior during model generation.","enum":["off","auto","on"],"type":"string"},"MeshyTaskError":{"description":"Error object that contains the error message if the task failed.","properties":{"message":{"description":"Detailed error message.","type":"string"}},"type":"object"},"MeshyTaskStatus":{"description":"Status of the task.","enum":["PENDING","IN_PROGRESS","SUCCEEDED","FAILED","CANCELED"],"type":"string"},"MeshyTextTo3DInputs":{"description":"The model-agnostic Comfy Router input shape for the `meshy/*` text-to-3d models: either a PREVIEW body (`mode: preview`, which generates the untextured mesh) or a REFINE body (`mode: refine`, which textures a preview task's output).\nIt composes the same two branches the v1 `POST /proxy/meshy/openapi/v2/text-to-3d` body does, differing only in `ai_model`: on this route the model comes from the path (`/v2/models/meshy/{model}`), so `ai_model` may be omitted or sent as null and Comfy Router writes the path's model into it before the call is forwarded.","discriminator":{"mapping":{"preview":"#/components/schemas/MeshyTextTo3DPreviewInputs","refine":"#/components/schemas/MeshyTextTo3DRefineInputs"},"propertyName":"mode"},"oneOf":[{"$ref":"#/components/schemas/MeshyTextTo3DPreviewInputs"},{"$ref":"#/components/schemas/MeshyTextTo3DRefineInputs"}]},"MeshyTextTo3DPreviewInputs":{"description":"The meshy text-to-3d PREVIEW fields, shared by the `preview` branch of the v1 `POST /proxy/meshy/openapi/v2/text-to-3d` body and the `preview` branch of the Comfy Router input schema for the `meshy/*` models.\nThe two surfaces share every property and both `required` entries. They differ only in `ai_model`: the v1 body restates it as a fixed enum, because that route carries no path segment to supply the model from, while on the Comfy Router route the path supplies it.","properties":{"ai_model":{"description":"The Meshy model to run. On the Comfy Router route this is supplied by the path (`/v2/models/meshy/{model}`) and may be omitted or sent as null; Router writes the path's model here after validation. On the v1 /proxy route see MeshyTextTo3DPreviewRequest, which constrains it to MeshyAiModel.","nullable":true,"type":"string"},"art_style":{"$ref":"#/components/schemas/MeshyArtStyle"},"geometry_resolution":{"default":"standard","description":"Geometry resolution of the generated mesh. 2k and 4k run the Ultra pass for finer surface detail. 2k and 4k require ai_model meshy-7.1 or latest.","enum":["standard","2k","4k"],"type":"string"},"is_a_t_pose":{"default":false,"description":"Deprecated. Use pose_mode instead. Whether to generate the model in an A/T pose.","type":"boolean"},"mode":{"description":"This field should be set to \"preview\" when creating a preview task.","enum":["preview"],"type":"string"},"moderation":{"default":false,"description":"When true, input content will be screened for potentially harmful content.","type":"boolean"},"pose_mode":{"$ref":"#/components/schemas/MeshyPoseMode"},"prompt":{"description":"Describe what kind of object the 3D model is. Maximum 800 characters.","maxLength":800,"type":"string"},"should_remesh":{"default":false,"description":"Controls whether to enable the remesh phase. When false, returns highest-precision triangular mesh. Defaults to true for Meshy 5 and false for Meshy 6 and 7.","type":"boolean"},"symmetry_mode":{"$ref":"#/components/schemas/MeshySymmetryMode"},"target_polycount":{"default":30000,"description":"Specify the target number of polygons in the generated model. Valid range is 100 to 300,000.","maximum":300000,"minimum":100,"type":"integer"},"topology":{"$ref":"#/components/schemas/MeshyTopology"},"ultra_mode":{"default":false,"deprecated":true,"description":"Deprecated. Use geometry_resolution instead; true is equivalent to geometry_resolution 2k. Only supported when ai_model is meshy-7, meshy-7.1 or latest. The exception is meshy-7: geometry_resolution 2k requires meshy-7.1 or latest, so on meshy-7 ultra_mode true is the way to request the Ultra pass.","type":"boolean"}},"required":["mode","prompt"],"type":"object"},"MeshyTextTo3DRefineInputs":{"description":"The meshy text-to-3d REFINE fields, shared by the `refine` branch of the v1 `POST /proxy/meshy/openapi/v2/text-to-3d` body and the `refine` branch of the Comfy Router input schema for the `meshy/*` models.\nThe two surfaces share every property and both `required` entries. They differ only in `ai_model`: the v1 body restates it as a fixed enum, because that route carries no path segment to supply the model from, while on the Comfy Router route the path supplies it.","properties":{"ai_model":{"description":"The Meshy model to run. On the Comfy Router route this is supplied by the path (`/v2/models/meshy/{model}`) and may be omitted or sent as null; Router writes the path's model here after validation. On the v1 /proxy route see MeshyTextTo3DRefineRequest, which constrains it to MeshyAiModel.","nullable":true,"type":"string"},"enable_pbr":{"default":false,"description":"Generate PBR Maps (metallic, roughness, normal) in addition to the base color. Note that enable_pbr should be set to false when using Sculpture style.","type":"boolean"},"mode":{"description":"This field should be set to \"refine\" when creating a refine task.","enum":["refine"],"type":"string"},"moderation":{"default":false,"description":"When true, input content will be screened for potentially harmful content.","type":"boolean"},"preview_task_id":{"description":"The corresponding preview task id. The status of the given preview task must be SUCCEEDED.","type":"string"},"texture_image_url":{"description":"Provide a 2d image to guide the texturing process. Supports .jpg, .jpeg, .png formats or base64-encoded data URI.","type":"string"},"texture_prompt":{"description":"Provide an additional text prompt to guide the texturing process. Maximum 800 characters.","maxLength":800,"type":"string"},"texture_resolution":{"default":"2k","description":"Texture resolution of the generated textures. One of 2k, 4k or 8k. 4k and 8k require ai_model meshy-6, meshy-7, meshy-7.1 or latest.","enum":["2k","4k","8k"],"type":"string"}},"required":["mode","preview_task_id"],"type":"object"},"MeshyTextTo3DTask":{"properties":{"art_style":{"description":"The unmodified art_style that was used to create the preview task.","type":"string"},"created_at":{"description":"Timestamp of when the task was created, in milliseconds.","format":"int64","type":"integer"},"finished_at":{"description":"Timestamp of when the task was finished, in milliseconds. 0 if not finished.","format":"int64","type":"integer"},"id":{"description":"Unique identifier for the task.","type":"string"},"model_urls":{"$ref":"#/components/schemas/MeshyModelUrls"},"negative_prompt":{"description":"Deprecated field maintained for backward compatibility.","type":"string"},"preceding_tasks":{"description":"The count of preceding tasks. Only meaningful when status is PENDING.","type":"integer"},"progress":{"description":"Progress of the task. 0 if not started, 100 when succeeded.","maximum":100,"minimum":0,"type":"integer"},"prompt":{"description":"The unmodified prompt that was used to create the task.","type":"string"},"started_at":{"description":"Timestamp of when the task was started, in milliseconds. 0 if not started.","format":"int64","type":"integer"},"status":{"$ref":"#/components/schemas/MeshyTaskStatus"},"task_error":{"allOf":[{"$ref":"#/components/schemas/MeshyTaskError"}],"nullable":true},"texture_image_url":{"description":"Downloadable URL to the texture image that was used to guide the texturing process.","type":"string"},"texture_prompt":{"description":"Additional text prompt provided to guide the texturing process during the refine stage.","type":"string"},"texture_richness":{"description":"Deprecated field maintained for backward compatibility.","type":"string"},"texture_urls":{"description":"An array of texture URL objects that are generated from the task.","items":{"$ref":"#/components/schemas/MeshyTextureUrls"},"type":"array"},"thumbnail_url":{"description":"Downloadable URL to the thumbnail image of the model file.","type":"string"},"type":{"description":"Type of the Text to 3D task.","enum":["text-to-3d-preview","text-to-3d-refine"],"type":"string"},"video_url":{"description":"Deprecated field returning the downloadable URL to the preview video.","type":"string"}},"required":["id","status"],"type":"object"},"MeshyTextureUrls":{"description":"Texture URL object containing PBR maps.","properties":{"base_color":{"description":"Downloadable URL to the base color map image.","type":"string"},"metallic":{"description":"Downloadable URL to the metallic map image.","type":"string"},"normal":{"description":"Downloadable URL to the normal map image.","type":"string"},"roughness":{"description":"Downloadable URL to the roughness map image.","type":"string"}},"type":"object"},"MeshyTopology":{"default":"triangle","description":"Specify the topology of the generated model.","enum":["quad","triangle"],"type":"string"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"meshy/meshy-7.1","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text"],"x-comfy-router-output-modalities":["3d"]}
diff --git a/router-schemas/meshy/meshy-7.json b/router-schemas/meshy/meshy-7.json
index 57fd5e5bf5..59d9f9aafe 100644
--- a/router-schemas/meshy/meshy-7.json
+++ b/router-schemas/meshy/meshy-7.json
@@ -1 +1 @@
-{"openapi":"3.0.2","info":{"title":"meshy/meshy-7","description":"The request body Comfy Router accepts for the model \"meshy/meshy-7\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"c6f277494199"},"paths":{"/v2/models/meshy/meshy-7":{"post":{"operationId":"runRouterModel","summary":"Run meshy/meshy-7 synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/MeshyTextTo3DInputs"},{"properties":{"geometry_resolution":{"default":"standard","description":"Geometry resolution of the generated mesh. meshy-7 supports `standard` and `2k`; `4k` requires ai_model meshy-7.1 or latest.","enum":["standard","2k"],"type":"string"}},"type":"object"}],"description":"The Comfy Router input schema for `meshy/meshy-7` text-to-3d.\nmeshy-7 is the tier Ultra generation (`ultra_mode`, preview mode) and the full `2k`/`4k`/`8k` texture range are both available on, so it narrows only `geometry_resolution`, to `standard`/`2k`: `4k` requires ai_model meshy-7.1 or latest. Everything else is the shared shape in MeshyTextTo3DInputs.","example":{"art_style":"realistic","mode":"preview","prompt":"a red cube"}}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/MeshyTextTo3DTask"},{"properties":{"status":{"enum":["SUCCEEDED"],"type":"string"}},"required":["status"],"type":"object"}],"description":"Comfy Router output schema for the Meshy text-to-3D models: the terminal `GET /proxy/meshy/openapi/v2/text-to-3d/{task_id}` task document, forwarded unchanged. The operation is SUBMIT-AND-POLL — `POST /proxy/meshy/openapi/v2/text-to-3d` answers with a `MeshyTextTo3DCreateResponse` handle whose `result` is the task id, and Router polls on the caller's behalf — so the body a caller receives is the finished task document rather than that handle. The family's three other operations (image-to-3d, multi-image-to-3d and retexture) stay reachable on their own `/proxy/meshy/...` routes and answer closely related task documents; a `{provider}/{model}` call settles onto the text-to-3d generation, which is the one this schema describes.\nThe generated mesh is in `model_urls`, at whichever of `glb`, `fbx`, `usdz` and `obj` the request produced — which of them are populated depends on the request, so read the one that is present rather than keying completion off `glb` alone. `glb` is the rendition a `{\"mode\": \"preview\"}` task with no format selector answers with, but it is not the only shape a success can take. Two neighbouring fields are NOT interchangeable with a mesh and a caller must not key completion off either: `mtl` is a material sidecar for `obj` rather than a mesh in its own right, and `thumbnail_url` is a preview image rather than the asset the call generated. Comfy Router draws exactly that line — a `SUCCEEDED` task carrying none of those four mesh URLs is answered as a Comfy Router error rather than with this document — so through Router a `200` here always carries a mesh, but not always the same one.\n`status` is Meshy's own UPPERCASE vocabulary — `PENDING`, `IN_PROGRESS`, `SUCCEEDED`, `FAILED`, `CANCELED` — forwarded unchanged and not case-folded. `SUCCEEDED` is the one terminal success; `FAILED` and `CANCELED` are terminal failures and reach a Router caller as a Comfy Router error rather than as this document, with the provider's own reason in `task_error.message`. `progress` runs 0..100 and is 100 on a succeeded task.\n`type` says which of the operation's two stages produced the document. `text-to-3d-preview` is the geometry stage, requested with `{\"mode\": \"preview\"}` and a prompt. `text-to-3d-refine` is the separate, dearer texturing task, requested with `{\"mode\": \"refine\"}` against a finished preview's `id` as `preview_task_id`; it is what populates `texture_urls`.\n`negative_prompt` and `texture_richness` are fields Meshy keeps for backward compatibility and does not populate meaningfully; `video_url` is likewise deprecated.","example":{"art_style":"realistic","created_at":1767225600000,"finished_at":1767225719000,"id":"018f2c7a-4b1e-7c3d-9a05-6e2f8b41d0c9","model_urls":{"fbx":"https://example.invalid/meshy/text-to-3d/model.fbx","glb":"https://example.invalid/meshy/text-to-3d/model.glb","mtl":"https://example.invalid/meshy/text-to-3d/model.mtl","obj":"https://example.invalid/meshy/text-to-3d/model.obj","usdz":"https://example.invalid/meshy/text-to-3d/model.usdz"},"progress":100,"prompt":"a red cube","started_at":1767225601000,"status":"SUCCEEDED","thumbnail_url":"https://example.invalid/meshy/text-to-3d/thumbnail.png","type":"text-to-3d-preview"}}}}}}}}},"components":{"schemas":{"MeshyArtStyle":{"default":"realistic","description":"Describe your desired art style of the object.","enum":["realistic","sculpture"],"type":"string"},"MeshyModelUrls":{"description":"Downloadable URLs to the textured 3D model files generated by Meshy.","properties":{"fbx":{"description":"Downloadable URL to the FBX file.","type":"string"},"glb":{"description":"Downloadable URL to the GLB file.","type":"string"},"mtl":{"description":"Downloadable URL to the MTL file.","type":"string"},"obj":{"description":"Downloadable URL to the OBJ file.","type":"string"},"usdz":{"description":"Downloadable URL to the USDZ file.","type":"string"}},"type":"object"},"MeshyPoseMode":{"description":"Specify the pose mode for the generated model.","enum":["a-pose","t-pose",""],"type":"string"},"MeshySymmetryMode":{"default":"auto","description":"Controls symmetry behavior during model generation.","enum":["off","auto","on"],"type":"string"},"MeshyTaskError":{"description":"Error object that contains the error message if the task failed.","properties":{"message":{"description":"Detailed error message.","type":"string"}},"type":"object"},"MeshyTaskStatus":{"description":"Status of the task.","enum":["PENDING","IN_PROGRESS","SUCCEEDED","FAILED","CANCELED"],"type":"string"},"MeshyTextTo3DInputs":{"description":"The model-agnostic Comfy Router input shape for the `meshy/*` text-to-3d models: either a PREVIEW body (`mode: preview`, which generates the untextured mesh) or a REFINE body (`mode: refine`, which textures a preview task's output).\nIt composes the same two branches the v1 `POST /proxy/meshy/openapi/v2/text-to-3d` body does, differing only in `ai_model`: on this route the model comes from the path (`/v2/models/meshy/{model}`), so `ai_model` may be omitted or sent as null and Comfy Router writes the path's model into it before the call is forwarded.","discriminator":{"mapping":{"preview":"#/components/schemas/MeshyTextTo3DPreviewInputs","refine":"#/components/schemas/MeshyTextTo3DRefineInputs"},"propertyName":"mode"},"oneOf":[{"$ref":"#/components/schemas/MeshyTextTo3DPreviewInputs"},{"$ref":"#/components/schemas/MeshyTextTo3DRefineInputs"}]},"MeshyTextTo3DPreviewInputs":{"description":"The meshy text-to-3d PREVIEW fields, shared by the `preview` branch of the v1 `POST /proxy/meshy/openapi/v2/text-to-3d` body and the `preview` branch of the Comfy Router input schema for the `meshy/*` models.\nThe two surfaces share every property and both `required` entries. They differ only in `ai_model`: the v1 body restates it as a fixed enum, because that route carries no path segment to supply the model from, while on the Comfy Router route the path supplies it.","properties":{"ai_model":{"description":"The Meshy model to run. On the Comfy Router route this is supplied by the path (`/v2/models/meshy/{model}`) and may be omitted or sent as null; Router writes the path's model here after validation. On the v1 /proxy route see MeshyTextTo3DPreviewRequest, which constrains it to MeshyAiModel.","nullable":true,"type":"string"},"art_style":{"$ref":"#/components/schemas/MeshyArtStyle"},"geometry_resolution":{"default":"standard","description":"Geometry resolution of the generated mesh. 2k and 4k run the Ultra pass for finer surface detail. 2k requires ai_model meshy-7, meshy-7.1 or latest; 4k requires meshy-7.1 or latest.","enum":["standard","2k","4k"],"type":"string"},"is_a_t_pose":{"default":false,"description":"Deprecated. Use pose_mode instead. Whether to generate the model in an A/T pose.","type":"boolean"},"mode":{"description":"This field should be set to \"preview\" when creating a preview task.","enum":["preview"],"type":"string"},"moderation":{"default":false,"description":"When true, input content will be screened for potentially harmful content.","type":"boolean"},"pose_mode":{"$ref":"#/components/schemas/MeshyPoseMode"},"prompt":{"description":"Describe what kind of object the 3D model is. Maximum 800 characters.","maxLength":800,"type":"string"},"should_remesh":{"default":false,"description":"Controls whether to enable the remesh phase. When false, returns highest-precision triangular mesh. Defaults to true for Meshy 5 and false for Meshy 6 and 7.","type":"boolean"},"symmetry_mode":{"$ref":"#/components/schemas/MeshySymmetryMode"},"target_polycount":{"default":30000,"description":"Specify the target number of polygons in the generated model. Valid range is 100 to 300,000.","maximum":300000,"minimum":100,"type":"integer"},"topology":{"$ref":"#/components/schemas/MeshyTopology"},"ultra_mode":{"default":false,"deprecated":true,"description":"Deprecated. Use geometry_resolution instead; true is equivalent to geometry_resolution 2k. Only supported when ai_model is meshy-7, meshy-7.1 or latest.","type":"boolean"}},"required":["mode","prompt"],"type":"object"},"MeshyTextTo3DRefineInputs":{"description":"The meshy text-to-3d REFINE fields, shared by the `refine` branch of the v1 `POST /proxy/meshy/openapi/v2/text-to-3d` body and the `refine` branch of the Comfy Router input schema for the `meshy/*` models.\nThe two surfaces share every property and both `required` entries. They differ only in `ai_model`: the v1 body restates it as a fixed enum, because that route carries no path segment to supply the model from, while on the Comfy Router route the path supplies it.","properties":{"ai_model":{"description":"The Meshy model to run. On the Comfy Router route this is supplied by the path (`/v2/models/meshy/{model}`) and may be omitted or sent as null; Router writes the path's model here after validation. On the v1 /proxy route see MeshyTextTo3DRefineRequest, which constrains it to MeshyAiModel.","nullable":true,"type":"string"},"enable_pbr":{"default":false,"description":"Generate PBR Maps (metallic, roughness, normal) in addition to the base color. Note that enable_pbr should be set to false when using Sculpture style.","type":"boolean"},"mode":{"description":"This field should be set to \"refine\" when creating a refine task.","enum":["refine"],"type":"string"},"moderation":{"default":false,"description":"When true, input content will be screened for potentially harmful content.","type":"boolean"},"preview_task_id":{"description":"The corresponding preview task id. The status of the given preview task must be SUCCEEDED.","type":"string"},"texture_image_url":{"description":"Provide a 2d image to guide the texturing process. Supports .jpg, .jpeg, .png formats or base64-encoded data URI.","type":"string"},"texture_prompt":{"description":"Provide an additional text prompt to guide the texturing process. Maximum 800 characters.","maxLength":800,"type":"string"},"texture_resolution":{"default":"2k","description":"Texture resolution of the generated textures. One of 2k, 4k or 8k. 4k and 8k require ai_model meshy-6, meshy-7, meshy-7.1 or latest.","enum":["2k","4k","8k"],"type":"string"}},"required":["mode","preview_task_id"],"type":"object"},"MeshyTextTo3DTask":{"properties":{"art_style":{"description":"The unmodified art_style that was used to create the preview task.","type":"string"},"created_at":{"description":"Timestamp of when the task was created, in milliseconds.","format":"int64","type":"integer"},"finished_at":{"description":"Timestamp of when the task was finished, in milliseconds. 0 if not finished.","format":"int64","type":"integer"},"id":{"description":"Unique identifier for the task.","type":"string"},"model_urls":{"$ref":"#/components/schemas/MeshyModelUrls"},"negative_prompt":{"description":"Deprecated field maintained for backward compatibility.","type":"string"},"preceding_tasks":{"description":"The count of preceding tasks. Only meaningful when status is PENDING.","type":"integer"},"progress":{"description":"Progress of the task. 0 if not started, 100 when succeeded.","maximum":100,"minimum":0,"type":"integer"},"prompt":{"description":"The unmodified prompt that was used to create the task.","type":"string"},"started_at":{"description":"Timestamp of when the task was started, in milliseconds. 0 if not started.","format":"int64","type":"integer"},"status":{"$ref":"#/components/schemas/MeshyTaskStatus"},"task_error":{"allOf":[{"$ref":"#/components/schemas/MeshyTaskError"}],"nullable":true},"texture_image_url":{"description":"Downloadable URL to the texture image that was used to guide the texturing process.","type":"string"},"texture_prompt":{"description":"Additional text prompt provided to guide the texturing process during the refine stage.","type":"string"},"texture_richness":{"description":"Deprecated field maintained for backward compatibility.","type":"string"},"texture_urls":{"description":"An array of texture URL objects that are generated from the task.","items":{"$ref":"#/components/schemas/MeshyTextureUrls"},"type":"array"},"thumbnail_url":{"description":"Downloadable URL to the thumbnail image of the model file.","type":"string"},"type":{"description":"Type of the Text to 3D task.","enum":["text-to-3d-preview","text-to-3d-refine"],"type":"string"},"video_url":{"description":"Deprecated field returning the downloadable URL to the preview video.","type":"string"}},"required":["id","status"],"type":"object"},"MeshyTextureUrls":{"description":"Texture URL object containing PBR maps.","properties":{"base_color":{"description":"Downloadable URL to the base color map image.","type":"string"},"metallic":{"description":"Downloadable URL to the metallic map image.","type":"string"},"normal":{"description":"Downloadable URL to the normal map image.","type":"string"},"roughness":{"description":"Downloadable URL to the roughness map image.","type":"string"}},"type":"object"},"MeshyTopology":{"default":"triangle","description":"Specify the topology of the generated model.","enum":["quad","triangle"],"type":"string"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"meshy/meshy-7","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text"],"x-comfy-router-output-modalities":["3d"]}
+{"openapi":"3.0.2","info":{"title":"meshy/meshy-7","description":"The request body Comfy Router accepts for the model \"meshy/meshy-7\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"8463adec09a2"},"paths":{"/v2/models/meshy/meshy-7":{"post":{"operationId":"runRouterModel","summary":"Run meshy/meshy-7 synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/MeshyTextTo3DInputs"},{"properties":{"geometry_resolution":{"default":"standard","description":"Geometry resolution of the generated mesh. meshy-7 supports `standard` only; `2k` and `4k` require ai_model meshy-7.1 or latest. For the Ultra pass on meshy-7 send `ultra_mode: true`, which is equivalent to `2k`.","enum":["standard"],"type":"string"}},"type":"object"}],"description":"The Comfy Router input schema for `meshy/meshy-7` text-to-3d.\nmeshy-7 is the tier Ultra generation (`ultra_mode`, preview mode) and the full `2k`/`4k`/`8k` texture range are both available on, so it narrows only `geometry_resolution`, to `standard`: `2k` and `4k` require ai_model meshy-7.1 or latest. The Ultra pass on meshy-7 is `ultra_mode: true`, which is equivalent to `2k`. Everything else is the shared shape in MeshyTextTo3DInputs.","example":{"art_style":"realistic","mode":"preview","prompt":"a red cube"}}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/MeshyTextTo3DTask"},{"properties":{"status":{"enum":["SUCCEEDED"],"type":"string"}},"required":["status"],"type":"object"}],"description":"Comfy Router output schema for the Meshy text-to-3D models: the terminal `GET /proxy/meshy/openapi/v2/text-to-3d/{task_id}` task document, forwarded unchanged. The operation is SUBMIT-AND-POLL — `POST /proxy/meshy/openapi/v2/text-to-3d` answers with a `MeshyTextTo3DCreateResponse` handle whose `result` is the task id, and Router polls on the caller's behalf — so the body a caller receives is the finished task document rather than that handle. The family's three other operations (image-to-3d, multi-image-to-3d and retexture) stay reachable on their own `/proxy/meshy/...` routes and answer closely related task documents; a `{provider}/{model}` call settles onto the text-to-3d generation, which is the one this schema describes.\nThe generated mesh is in `model_urls`, at whichever of `glb`, `fbx`, `usdz` and `obj` the request produced — which of them are populated depends on the request, so read the one that is present rather than keying completion off `glb` alone. `glb` is the rendition a `{\"mode\": \"preview\"}` task with no format selector answers with, but it is not the only shape a success can take. Two neighbouring fields are NOT interchangeable with a mesh and a caller must not key completion off either: `mtl` is a material sidecar for `obj` rather than a mesh in its own right, and `thumbnail_url` is a preview image rather than the asset the call generated. Comfy Router draws exactly that line — a `SUCCEEDED` task carrying none of those four mesh URLs is answered as a Comfy Router error rather than with this document — so through Router a `200` here always carries a mesh, but not always the same one.\n`status` is Meshy's own UPPERCASE vocabulary — `PENDING`, `IN_PROGRESS`, `SUCCEEDED`, `FAILED`, `CANCELED` — forwarded unchanged and not case-folded. `SUCCEEDED` is the one terminal success; `FAILED` and `CANCELED` are terminal failures and reach a Router caller as a Comfy Router error rather than as this document, with the provider's own reason in `task_error.message`. `progress` runs 0..100 and is 100 on a succeeded task.\n`type` says which of the operation's two stages produced the document. `text-to-3d-preview` is the geometry stage, requested with `{\"mode\": \"preview\"}` and a prompt. `text-to-3d-refine` is the separate, dearer texturing task, requested with `{\"mode\": \"refine\"}` against a finished preview's `id` as `preview_task_id`; it is what populates `texture_urls`.\n`negative_prompt` and `texture_richness` are fields Meshy keeps for backward compatibility and does not populate meaningfully; `video_url` is likewise deprecated.","example":{"art_style":"realistic","created_at":1767225600000,"finished_at":1767225719000,"id":"018f2c7a-4b1e-7c3d-9a05-6e2f8b41d0c9","model_urls":{"fbx":"https://example.invalid/meshy/text-to-3d/model.fbx","glb":"https://example.invalid/meshy/text-to-3d/model.glb","mtl":"https://example.invalid/meshy/text-to-3d/model.mtl","obj":"https://example.invalid/meshy/text-to-3d/model.obj","usdz":"https://example.invalid/meshy/text-to-3d/model.usdz"},"progress":100,"prompt":"a red cube","started_at":1767225601000,"status":"SUCCEEDED","thumbnail_url":"https://example.invalid/meshy/text-to-3d/thumbnail.png","type":"text-to-3d-preview"}}}}}}}}},"components":{"schemas":{"MeshyArtStyle":{"default":"realistic","description":"Describe your desired art style of the object.","enum":["realistic","sculpture"],"type":"string"},"MeshyModelUrls":{"description":"Downloadable URLs to the textured 3D model files generated by Meshy.","properties":{"fbx":{"description":"Downloadable URL to the FBX file.","type":"string"},"glb":{"description":"Downloadable URL to the GLB file.","type":"string"},"mtl":{"description":"Downloadable URL to the MTL file.","type":"string"},"obj":{"description":"Downloadable URL to the OBJ file.","type":"string"},"usdz":{"description":"Downloadable URL to the USDZ file.","type":"string"}},"type":"object"},"MeshyPoseMode":{"description":"Specify the pose mode for the generated model.","enum":["a-pose","t-pose",""],"type":"string"},"MeshySymmetryMode":{"default":"auto","description":"Controls symmetry behavior during model generation.","enum":["off","auto","on"],"type":"string"},"MeshyTaskError":{"description":"Error object that contains the error message if the task failed.","properties":{"message":{"description":"Detailed error message.","type":"string"}},"type":"object"},"MeshyTaskStatus":{"description":"Status of the task.","enum":["PENDING","IN_PROGRESS","SUCCEEDED","FAILED","CANCELED"],"type":"string"},"MeshyTextTo3DInputs":{"description":"The model-agnostic Comfy Router input shape for the `meshy/*` text-to-3d models: either a PREVIEW body (`mode: preview`, which generates the untextured mesh) or a REFINE body (`mode: refine`, which textures a preview task's output).\nIt composes the same two branches the v1 `POST /proxy/meshy/openapi/v2/text-to-3d` body does, differing only in `ai_model`: on this route the model comes from the path (`/v2/models/meshy/{model}`), so `ai_model` may be omitted or sent as null and Comfy Router writes the path's model into it before the call is forwarded.","discriminator":{"mapping":{"preview":"#/components/schemas/MeshyTextTo3DPreviewInputs","refine":"#/components/schemas/MeshyTextTo3DRefineInputs"},"propertyName":"mode"},"oneOf":[{"$ref":"#/components/schemas/MeshyTextTo3DPreviewInputs"},{"$ref":"#/components/schemas/MeshyTextTo3DRefineInputs"}]},"MeshyTextTo3DPreviewInputs":{"description":"The meshy text-to-3d PREVIEW fields, shared by the `preview` branch of the v1 `POST /proxy/meshy/openapi/v2/text-to-3d` body and the `preview` branch of the Comfy Router input schema for the `meshy/*` models.\nThe two surfaces share every property and both `required` entries. They differ only in `ai_model`: the v1 body restates it as a fixed enum, because that route carries no path segment to supply the model from, while on the Comfy Router route the path supplies it.","properties":{"ai_model":{"description":"The Meshy model to run. On the Comfy Router route this is supplied by the path (`/v2/models/meshy/{model}`) and may be omitted or sent as null; Router writes the path's model here after validation. On the v1 /proxy route see MeshyTextTo3DPreviewRequest, which constrains it to MeshyAiModel.","nullable":true,"type":"string"},"art_style":{"$ref":"#/components/schemas/MeshyArtStyle"},"geometry_resolution":{"default":"standard","description":"Geometry resolution of the generated mesh. 2k and 4k run the Ultra pass for finer surface detail. 2k and 4k require ai_model meshy-7.1 or latest.","enum":["standard","2k","4k"],"type":"string"},"is_a_t_pose":{"default":false,"description":"Deprecated. Use pose_mode instead. Whether to generate the model in an A/T pose.","type":"boolean"},"mode":{"description":"This field should be set to \"preview\" when creating a preview task.","enum":["preview"],"type":"string"},"moderation":{"default":false,"description":"When true, input content will be screened for potentially harmful content.","type":"boolean"},"pose_mode":{"$ref":"#/components/schemas/MeshyPoseMode"},"prompt":{"description":"Describe what kind of object the 3D model is. Maximum 800 characters.","maxLength":800,"type":"string"},"should_remesh":{"default":false,"description":"Controls whether to enable the remesh phase. When false, returns highest-precision triangular mesh. Defaults to true for Meshy 5 and false for Meshy 6 and 7.","type":"boolean"},"symmetry_mode":{"$ref":"#/components/schemas/MeshySymmetryMode"},"target_polycount":{"default":30000,"description":"Specify the target number of polygons in the generated model. Valid range is 100 to 300,000.","maximum":300000,"minimum":100,"type":"integer"},"topology":{"$ref":"#/components/schemas/MeshyTopology"},"ultra_mode":{"default":false,"deprecated":true,"description":"Deprecated. Use geometry_resolution instead; true is equivalent to geometry_resolution 2k. Only supported when ai_model is meshy-7, meshy-7.1 or latest. The exception is meshy-7: geometry_resolution 2k requires meshy-7.1 or latest, so on meshy-7 ultra_mode true is the way to request the Ultra pass.","type":"boolean"}},"required":["mode","prompt"],"type":"object"},"MeshyTextTo3DRefineInputs":{"description":"The meshy text-to-3d REFINE fields, shared by the `refine` branch of the v1 `POST /proxy/meshy/openapi/v2/text-to-3d` body and the `refine` branch of the Comfy Router input schema for the `meshy/*` models.\nThe two surfaces share every property and both `required` entries. They differ only in `ai_model`: the v1 body restates it as a fixed enum, because that route carries no path segment to supply the model from, while on the Comfy Router route the path supplies it.","properties":{"ai_model":{"description":"The Meshy model to run. On the Comfy Router route this is supplied by the path (`/v2/models/meshy/{model}`) and may be omitted or sent as null; Router writes the path's model here after validation. On the v1 /proxy route see MeshyTextTo3DRefineRequest, which constrains it to MeshyAiModel.","nullable":true,"type":"string"},"enable_pbr":{"default":false,"description":"Generate PBR Maps (metallic, roughness, normal) in addition to the base color. Note that enable_pbr should be set to false when using Sculpture style.","type":"boolean"},"mode":{"description":"This field should be set to \"refine\" when creating a refine task.","enum":["refine"],"type":"string"},"moderation":{"default":false,"description":"When true, input content will be screened for potentially harmful content.","type":"boolean"},"preview_task_id":{"description":"The corresponding preview task id. The status of the given preview task must be SUCCEEDED.","type":"string"},"texture_image_url":{"description":"Provide a 2d image to guide the texturing process. Supports .jpg, .jpeg, .png formats or base64-encoded data URI.","type":"string"},"texture_prompt":{"description":"Provide an additional text prompt to guide the texturing process. Maximum 800 characters.","maxLength":800,"type":"string"},"texture_resolution":{"default":"2k","description":"Texture resolution of the generated textures. One of 2k, 4k or 8k. 4k and 8k require ai_model meshy-6, meshy-7, meshy-7.1 or latest.","enum":["2k","4k","8k"],"type":"string"}},"required":["mode","preview_task_id"],"type":"object"},"MeshyTextTo3DTask":{"properties":{"art_style":{"description":"The unmodified art_style that was used to create the preview task.","type":"string"},"created_at":{"description":"Timestamp of when the task was created, in milliseconds.","format":"int64","type":"integer"},"finished_at":{"description":"Timestamp of when the task was finished, in milliseconds. 0 if not finished.","format":"int64","type":"integer"},"id":{"description":"Unique identifier for the task.","type":"string"},"model_urls":{"$ref":"#/components/schemas/MeshyModelUrls"},"negative_prompt":{"description":"Deprecated field maintained for backward compatibility.","type":"string"},"preceding_tasks":{"description":"The count of preceding tasks. Only meaningful when status is PENDING.","type":"integer"},"progress":{"description":"Progress of the task. 0 if not started, 100 when succeeded.","maximum":100,"minimum":0,"type":"integer"},"prompt":{"description":"The unmodified prompt that was used to create the task.","type":"string"},"started_at":{"description":"Timestamp of when the task was started, in milliseconds. 0 if not started.","format":"int64","type":"integer"},"status":{"$ref":"#/components/schemas/MeshyTaskStatus"},"task_error":{"allOf":[{"$ref":"#/components/schemas/MeshyTaskError"}],"nullable":true},"texture_image_url":{"description":"Downloadable URL to the texture image that was used to guide the texturing process.","type":"string"},"texture_prompt":{"description":"Additional text prompt provided to guide the texturing process during the refine stage.","type":"string"},"texture_richness":{"description":"Deprecated field maintained for backward compatibility.","type":"string"},"texture_urls":{"description":"An array of texture URL objects that are generated from the task.","items":{"$ref":"#/components/schemas/MeshyTextureUrls"},"type":"array"},"thumbnail_url":{"description":"Downloadable URL to the thumbnail image of the model file.","type":"string"},"type":{"description":"Type of the Text to 3D task.","enum":["text-to-3d-preview","text-to-3d-refine"],"type":"string"},"video_url":{"description":"Deprecated field returning the downloadable URL to the preview video.","type":"string"}},"required":["id","status"],"type":"object"},"MeshyTextureUrls":{"description":"Texture URL object containing PBR maps.","properties":{"base_color":{"description":"Downloadable URL to the base color map image.","type":"string"},"metallic":{"description":"Downloadable URL to the metallic map image.","type":"string"},"normal":{"description":"Downloadable URL to the normal map image.","type":"string"},"roughness":{"description":"Downloadable URL to the roughness map image.","type":"string"}},"type":"object"},"MeshyTopology":{"default":"triangle","description":"Specify the topology of the generated model.","enum":["quad","triangle"],"type":"string"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"meshy/meshy-7","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text"],"x-comfy-router-output-modalities":["3d"]}
diff --git a/router-schemas/openai/gpt-image-1.5.json b/router-schemas/openai/gpt-image-1.5.json
index 7971abe1b7..d93bb3c68e 100644
--- a/router-schemas/openai/gpt-image-1.5.json
+++ b/router-schemas/openai/gpt-image-1.5.json
@@ -1 +1 @@
-{"openapi":"3.0.2","info":{"title":"openai/gpt-image-1.5","description":"The request body Comfy Router accepts for the model \"openai/gpt-image-1.5\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"f44c74e2ce6b"},"paths":{"/v2/models/openai/gpt-image-1.5":{"post":{"operationId":"runRouterModel","summary":"Run openai/gpt-image-1.5 synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"description":"The request body for a gpt-image model. ONE model id reaches TWO upstream OpenAI operations and this body is what selects between them: OMIT `image` for text-to-image generation, INCLUDE it to edit the image you send. The example below is the generation call; adding `image` to it is the whole of the structural difference, and `image`'s own description carries a copyable edit body.","example":{"prompt":"a rocketship on a launchpad","quality":"low","size":"1024x1024"},"properties":{"background":{"description":"Background transparency. `auto` lets the model choose.","enum":["transparent","opaque","auto"],"example":"opaque","type":"string"},"image":{"$ref":"#/components/schemas/OpenAIImageRouterSource"},"mask":{"allOf":[{"$ref":"#/components/schemas/OpenAIImageRouterMedia"}],"description":"Optional. Marks the region to edit: its fully transparent areas are what the model replaces. Must be a PNG with an alpha channel, under 4 MiB -- the bound routerOpenAIMaskMaxBytes ENFORCES on the resolved bytes, so an oversize mask is refused here by its size rather than relayed from the provider -- and matching the dimensions of the first `image`. Same accepted forms as `image`. Ignored -- and meaningless -- when `image` is absent, because there is nothing to mask: Router strips it from the request rather than forwarding it to the text-to-image operation, which would reject it as an unknown parameter."},"model":{"description":"The gpt-image model to run. Router splices the addressed model id in, so a caller addressing /v2/models/openai/gpt-image-2 does not send this.","example":"gpt-image-2.5-flare","type":"string"},"moderation":{"description":"Content moderation setting","enum":["low","auto"],"example":"auto","type":"string"},"n":{"description":"The number of images to generate (1-10).","example":1,"maximum":10,"minimum":1,"type":"integer"},"output_compression":{"description":"Compression level for JPEG or WebP (0-100)","example":100,"maximum":100,"minimum":0,"type":"integer"},"output_format":{"description":"Format of the output image","enum":["png","webp","jpeg"],"example":"png","type":"string"},"partial_images":{"description":"Meaningful only alongside a live stream, which this surface cannot serve -- `stream` is settled to `false` even when you send it. On a generation, any value other than `0` or `null` is refused with a 400, whether or not the request also sends `stream: true`; on a request carrying `image` the field is dropped rather than forwarded. Omit it.","example":0,"maximum":3,"minimum":0,"nullable":true,"type":"integer"},"prompt":{"description":"A text description of the image to generate, or of the edit to make to `image`.","example":"Give the rocketship rainbow coloring","type":"string"},"quality":{"description":"The quality of the generated or edited image. `xhigh` and `max` are the two tiers gpt-image-2.5-flare and gpt-image-2.5-sunburst were added for; `standard` and `hd` are dall-e-era spellings no admitted model reads.","enum":["auto","low","medium","high","xhigh","max","standard","hd"],"example":"high","type":"string"},"size":{"description":"Size of the image (e.g., 1024x1024, 1536x1024, auto)","example":"1024x1536","type":"string"},"stream":{"description":"Declared so a caller who sends it is not refused, but INERT on this surface: Router SETTLES it to `false` before dispatch, because it captures the provider response rather than relaying a `text/event-stream` (a request carrying `image` drops the field instead, since the edit is re-encoded without it). No Comfy surface serves gpt-image partial-image streaming: the v1 `POST /proxy/openai/images/generations` ingress refuses `stream: true` with a 400 and never forwards the request upstream, so omit the field and read the completed response as one JSON body. Because the stream never opens, OpenAI's restriction on combining `stream: true` with `n` greater than 1 does not apply here; `n` is forwarded as sent.","example":false,"nullable":true,"type":"boolean"},"user":{"description":"A unique identifier for end-user monitoring","example":"user-1234","type":"string"}},"required":["prompt"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/OpenAIImageGenerationResponse"},{"properties":{"background":{"description":"Whether the generated image's background is `opaque` or `transparent`, as OpenAI resolved it for this generation. The request parameter defaults to `auto`, so this is where a caller who did not pin it learns which one was produced.","type":"string"},"created":{"description":"Unix timestamp, in seconds, of when the generation completed. It is the one member OpenAI declares REQUIRED on an image-generation response, so it is present whenever OpenAI is the producer; a fal-, wavespeed- or runware-served response on any of the alt-backed ids omits it entirely, like the other four resolved-parameter members. Declared `int64` because a present-day epoch value is close enough to 2^31 that an unformatted `integer` generates a 32-bit field in many SDK generators.","format":"int64","type":"integer"},"output_format":{"description":"The encoding of the bytes in `data[].b64_json` — `png`, `webp` or `jpeg`. It is the format OpenAI actually encoded, which is the request's `output_format` when one was sent and `png` when none was, so a decoder can key off it rather than re-deriving the format from the request.","type":"string"},"quality":{"description":"The quality tier the generation actually ran at — OpenAI reports one of `low`, `medium`, `high`, `xhigh` or `max`. It is the RESOLVED tier, not an echo of the request, whose own `quality` defaults to `auto`.","type":"string"},"size":{"description":"The pixel dimensions the generation actually ran at, as `\u003cwidth\u003ex\u003cheight\u003e`. It is the RESOLVED size: the request's `size` defaults to `auto` and explicitly admits `auto`, so this is the only place the dimensions actually used are reported.","type":"string"}},"type":"object"}],"description":"Comfy Router output schema for the OpenAI gpt-image models: OpenAI's own image-generation response, forwarded unchanged. The operation is direct-return (`routerresult/classification.go` classifies `openai /images/generations` as ReturnModeDirect with no poll route), so the body a caller receives is this finished document from the one call. That row also raises the read limit to `BinaryResultMaxBytes`, because the images can arrive INLINE and `n` admits up to 10 of them at a `size` and `quality` the operation does not bound.\nOn the ids with a single producer — `openai/gpt-image-1` and `openai/gpt-image-1.5` — and on any of the alt-backed ids whenever OpenAI itself serves it, the generated images are in `data`, always INLINE at `data[].b64_json`: OpenAI has no `url` response format at all on this surface, so `url` stays unpopulated there even though the shared response component declares it. That also means the bytes are durable in the only sense that matters here — they are in the body, not behind a link that expires. `revised_prompt` is the prompt OpenAI rewrote rather than an asset, and it is present on a response whose image never arrived, so it must not be read as one. `usage` is OpenAI's own token accounting — OpenAI's numbers, not the Comfy charge.\n`openai/gpt-image-2` (`fal`, `runware`, `wavespeed`), `openai/gpt-image-2.5-flare` (`fal`, `runware`, `wavespeed`) and `openai/gpt-image-2.5-sunburst` (`fal`, `runware`, `wavespeed`) have ALTERNATE producers: an eligible request on any of them may instead be served by fal, by wavespeed or by runware. Each partner is reachable explicitly, via `model_provider=fal`, `model_provider=wavespeed` or `model_provider=runware`. One may also serve a request that named nobody: `fallback_provider` (default ON) retries a GENERATION once against a registered alternate when the OpenAI attempt fails for a reason attributable to Router or to OpenAI, never to the caller's request. Do not read that retry as fal's alone — it walks the id's registered partners in a fixed order and takes the first whose provider gate, workspace partner-provider policy, eligibility valve and translation all succeed, so wavespeed or runware answers it whenever fal is ruled out. And it does not apply to an EDIT: a body carrying `image` selects the multipart edits operation, for which the automatic retry is disabled outright, so an edit that OpenAI fails comes back as OpenAI's own failure rather than being retried against a partner. (Naming a partner explicitly still serves an edit, wherever that partner's translator carries the media field itself.) Every one of those routes is the alt-provider dispatch in `server/implementation/router_run.go`, whose `routerAltTranslators` supplies the translator half against the matching `routeralt` leg that carries the alt id (`fal-gpt-image-2`, `wavespeed-gpt-image-2.5-flare`, `runware-gpt-image-2.5-sunburst` and so on — one leg per id-and-partner pair); the two halves are joined at init and a half-registration fails the build. None of the partners returns inline base64 on any of the alt-backed ids — each only returns hosted URLs, so an alt-provider-served response carries `data[].url` and never `data[].b64_json`, the opposite of the OpenAI-served shape above. A caller that requested `response_format: b64_json` on an alt-provider-served attempt is told so through `X-Comfy-Router-Dropped-Params` rather than being given inline bytes. Both of those statements describe the answer TRANSLATED BACK into this contract, which is what the default `strict_mode=false` returns. `strict_mode=true` runs no translation in either direction: the partner's own raw envelope comes back unchanged and carries no `X-Comfy-Router-Dropped-Params`, and Router returns that same raw envelope on the rarer path where a response cannot be translated back. Such a body is the PARTNER's contract, not this schema, and must not be parsed as this one — the call is billed either way. Their translators do not fabricate the OpenAI-specific fields they cannot produce: on an alt-provider-served response `usage`, `revised_prompt`, and all four resolved-parameter members below are entirely ABSENT, not merely empty. Branch on the keys a response actually carries rather than on an assumed producer; which partner served a given request on any of the alt-backed ids is a Comfy routing decision that can change without notice, and is not part of this contract.\nFor the single-producer ids, and for the alt-backed ids whenever OpenAI is the one that served it, the four resolved-parameter members (`background`, `output_format`, `quality`, `size`) report what OpenAI ACTUALLY RAN rather than echoing the request: `background`, `quality` and `size` each default to `auto` on the request side, so a caller who sends `auto` — or sends nothing — only learns the concrete background, tier and `\u003cwidth\u003ex\u003cheight\u003e` that were used by reading them here. `created` is the one member OpenAI declares required whenever OpenAI is the producer.","example":{"created":1767225600,"data":[{"b64_json":"PGJhc2U2ND4="}],"usage":{"input_tokens":12,"input_tokens_details":{"image_tokens":0,"text_tokens":12},"output_tokens":1056,"total_tokens":1068}}}}}}}}}},"components":{"schemas":{"OpenAIImageGenerationResponse":{"properties":{"data":{"items":{"properties":{"b64_json":{"description":"Base64 encoded image data","type":"string"},"revised_prompt":{"description":"Revised prompt","type":"string"},"url":{"description":"URL of the image","type":"string"}},"type":"object"},"type":"array"},"usage":{"properties":{"input_tokens":{"type":"integer"},"input_tokens_details":{"properties":{"image_tokens":{"type":"integer"},"text_tokens":{"type":"integer"}},"type":"object"},"output_tokens":{"type":"integer"},"output_tokens_details":{"properties":{"image_tokens":{"type":"integer"},"text_tokens":{"type":"integer"}},"type":"object"},"total_tokens":{"type":"integer"}},"type":"object"}},"type":"object"},"OpenAIImageRouterMedia":{"description":"One image, as either an https URL Router fetches on the caller's behalf or a `data:image/\u003cformat\u003e;base64,\u003cpayload\u003e` URI carrying the bytes inline. Accepted image types are png, webp and jpeg, and that set is ENFORCED at resolve time rather than left to the provider -- an svg, gif, avif or tiff is refused here, naming the image you sent, instead of becoming a partner error two hops from the cause.\nPrefer the URL form. A base64 payload inflates roughly 4/3 and the whole request body is capped at 100 MiB, so the inline form caps out near a 75 MB source image; a URL sidesteps that entirely. The per-image and per-request media ceilings stated at the end of this description are tighter than the body cap, so for an image of any real size they are what you meet first.\nTHE URL MUST RESOLVE WITHOUT YOUR CREDENTIALS. Router fetches it server-side and sends no caller credential with the request, so a presigned URL -- one whose signature is in the query string -- is the supported form. An authenticated endpoint such as `/api/assets/{id}/content` answers Router 401 rather than the image; request that URL yourself and pass the signed URL it redirects to.\nTHE FETCH IS CONFINED TO A NAMED SET OF HOSTS rather than the open internet, and the same list is re-applied to every redirect hop as well as to the URL you send -- so a URL on any other host, or one that redirects off the list, is refused before any provider is contacted. Today that list is Comfy's own asset delivery only: a signed `storage.googleapis.com` URL under a Comfy asset bucket, which is the form `/api/assets/{id}/content` redirects you to. A partner CDN or a plain https host is NOT on the list.\nTWO WIDENINGS ARE BEING ROLLED OUT AND ARE BOTH OFF BY DEFAULT TODAY. They ship behind ONE switch and are enabled together, so treat everything below as forthcoming rather than as something to build against yet -- ask before you rely on either.\nThe FIRST is the bucket `POST /customers/storage` signs your upload into. Until the rollout reaches your environment an upload URL from that endpoint is refused here, even though the bucket is Comfy's own and the host is the same one -- so \"upload to Comfy, then pass the URL\" does NOT work yet, and the form that does is the signed URL `/api/assets/{id}/content` redirects to.\nThe SECOND is ENABLED PER ACCOUNT rather than for everyone at once, because the bucket behind these hosts is yours rather than ours: even once the switch above is on, an account that has not had third-party inbound media enabled is refused, and told so in those words rather than told the host is unsupported. Ask your Comfy contact to enable it for your account. It additionally admits these third-party object-storage hosts, where the bucket is yours and Router makes no ownership claim on it: Cloudflare R2 (`\u003caccount\u003e.r2.cloudflarestorage.com`, `pub-\u003chash\u003e.r2.dev`), Amazon S3 (`s3.amazonaws.com` and `s3.\u003cregion\u003e.amazonaws.com`, in either the path-style or the `\u003cbucket\u003e.`-prefixed form), Azure Blob Storage (`\u003caccount\u003e.blob.core.windows.net`) and Alibaba Cloud OSS (`\u003cbucket\u003e.oss-\u003cregion\u003e.aliyuncs.com`, including the `oss-accelerate` endpoint). Those are object-storage endpoints specifically: a general-purpose endpoint on the same provider domain, such as an Alibaba Function Compute trigger, is not on the list and is refused.\nWHAT IS MATCHED IS THE HOSTNAME FORM, NOT THE PROVIDER -- so naming a provider above does NOT mean every URL that provider can issue is accepted, and three near-misses are worth stating outright because a caller can reasonably arrive with each of them. An R2 CUSTOM DOMAIN (`cdn.example.com`) is NOT accepted: Cloudflare documents `pub-\u003chash\u003e.r2.dev` as a rate-limited debug hostname and points production traffic at a custom domain instead, but a custom domain carries nothing in its hostname identifying it as R2, so Router cannot tell it from any other host and refuses it -- presign the `\u003caccount\u003e.r2.cloudflarestorage.com` endpoint, or use the `pub-\u003chash\u003e.r2.dev` form. S3's IPv6 dualstack endpoint (`s3.dualstack.\u003cregion\u003e.amazonaws.com`) is not accepted either; use the plain regional endpoint. Nor is Azure Blob's alternate DNS zone (`\u003caccount\u003e.z\u003cN\u003e.blob.storage.azure.net`); use `\u003caccount\u003e.blob.core.windows.net`. The refusal you get back names these same forms, so you do not have to come here to read them.\nThe `\u003caccount\u003e` and `pub-\u003chash\u003e` placeholders above show the form you will normally arrive with; they are not a depth restriction. Matching is on the object-storage domain suffix, so a deeper name under the same namespace (`a.b.pub-\u003chash\u003e.r2.dev`) is accepted as well. What is NOT widened by that is the namespace itself -- the suffix still has to be one of the ones listed.\nThe example below shows the shape only: a real value also carries the signing query parameters, which are deliberately omitted here because a published example lives in the spec forever and a real signature is both a leaked credential and a link that stops working.\nEach image is capped at 25 MiB and one request's images at 64 MiB in total.","example":"https://storage.googleapis.com/comfy-cloud-assets/0199b3f4-1d2e-7a3b-8c4d-5e6f70819234.png","type":"string"},"OpenAIImageRouterSource":{"description":"The source image(s) to edit. PRESENCE OF THIS FIELD SELECTS THE EDIT OPERATION -- omit it entirely for text-to-image generation.\nThe model's published example is the GENERATION call, which omits this field. Adding `image` is the whole of the difference between the two modes -- same endpoint, same model id -- except that an edit prompt describes the CHANGE you want rather than the scene, so the edit body below reads differently from the generation example even though only this field is structurally new.\nIt is copyable exactly as printed: `{\"prompt\": \"give the rocketship rainbow coloring\", \"image\": [\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==\"], \"size\": \"1024x1024\"}` -- the payload is a complete 1x1 PNG, not an abbreviation, and this body is pinned as an accepted case in Router's own request-validation tests. Swap in your own image's bytes.\nAccepts one image or an array of them; gpt-image-1 and later take up to 16, which Router enforces from this schema before dispatching.","oneOf":[{"$ref":"#/components/schemas/OpenAIImageRouterMedia"},{"items":{"$ref":"#/components/schemas/OpenAIImageRouterMedia"},"maxItems":16,"minItems":1,"type":"array"}]}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"openai/gpt-image-1.5","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image"],"x-comfy-router-output-modalities":["image"]}
+{"openapi":"3.0.2","info":{"title":"openai/gpt-image-1.5","description":"The request body Comfy Router accepts for the model \"openai/gpt-image-1.5\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"1786a32eea22"},"paths":{"/v2/models/openai/gpt-image-1.5":{"post":{"operationId":"runRouterModel","summary":"Run openai/gpt-image-1.5 synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"description":"The request body for a gpt-image model. ONE model id reaches TWO upstream OpenAI operations and this body is what selects between them: OMIT `image` for text-to-image generation, INCLUDE it to edit the image you send. The example below is the generation call; adding `image` to it is the whole of the structural difference, and `image`'s own description carries a copyable edit body.","example":{"prompt":"a rocketship on a launchpad","quality":"low","size":"1024x1024"},"properties":{"background":{"description":"Background transparency. `auto` lets the model choose.","enum":["transparent","opaque","auto"],"example":"opaque","type":"string"},"image":{"$ref":"#/components/schemas/OpenAIImageRouterSource"},"mask":{"allOf":[{"$ref":"#/components/schemas/OpenAIImageRouterMedia"}],"description":"Optional. Marks the region to edit: its fully transparent areas are what the model replaces. Must be a PNG with an alpha channel, under 4 MiB -- the bound routerOpenAIMaskMaxBytes ENFORCES on the resolved bytes, so an oversize mask is refused here by its size rather than relayed from the provider -- and matching the dimensions of the first `image`. Same accepted forms as `image`. Ignored -- and meaningless -- when `image` is absent, because there is nothing to mask: Router strips it from the request rather than forwarding it to the text-to-image operation, which would reject it as an unknown parameter."},"model":{"description":"The gpt-image model to run. Router splices the addressed model id in, so a caller addressing /v2/models/openai/gpt-image-2 does not send this.","example":"gpt-image-2.5-flare","type":"string"},"moderation":{"description":"Content moderation setting","enum":["low","auto"],"example":"auto","type":"string"},"n":{"description":"The number of images to generate (1-10).","example":1,"maximum":10,"minimum":1,"type":"integer"},"output_compression":{"description":"Compression level for JPEG or WebP (0-100)","example":100,"maximum":100,"minimum":0,"type":"integer"},"output_format":{"description":"Format of the output image","enum":["png","webp","jpeg"],"example":"png","type":"string"},"partial_images":{"description":"Meaningful only alongside a live stream, which this surface cannot serve -- `stream` is settled to `false` even when you send it. On a generation, any value other than `0` or `null` is refused with a 400, whether or not the request also sends `stream: true`; on a request carrying `image` the field is dropped rather than forwarded. Omit it.","example":0,"maximum":3,"minimum":0,"nullable":true,"type":"integer"},"prompt":{"description":"A text description of the image to generate, or of the edit to make to `image`.","example":"Give the rocketship rainbow coloring","type":"string"},"quality":{"description":"The quality of the generated or edited image. `xhigh` and `max` are the two tiers gpt-image-2.5-flare and gpt-image-2.5-sunburst were added for; `standard` and `hd` are dall-e-era spellings no admitted model reads.","enum":["auto","low","medium","high","xhigh","max","standard","hd"],"example":"high","type":"string"},"size":{"description":"Size of the image (e.g., 1024x1024, 1536x1024, auto)","example":"1024x1536","type":"string"},"stream":{"description":"Declared so a caller who sends it is not refused, but INERT on this surface: Router SETTLES it to `false` before dispatch, because it captures the provider response rather than relaying a `text/event-stream` (a request carrying `image` drops the field instead, since the edit is re-encoded without it). No Comfy surface serves gpt-image partial-image streaming: the v1 `POST /proxy/openai/images/generations` ingress refuses `stream: true` with a 400 and never forwards the request upstream, so omit the field and read the completed response as one JSON body. Because the stream never opens, OpenAI's restriction on combining `stream: true` with `n` greater than 1 does not apply here; `n` is forwarded as sent.","example":false,"nullable":true,"type":"boolean"},"user":{"description":"A unique identifier for end-user monitoring","example":"user-1234","type":"string"}},"required":["prompt"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/OpenAIImageGenerationResponse"},{"properties":{"background":{"description":"Whether the generated image's background is `opaque` or `transparent`, as OpenAI resolved it for this generation. The request parameter defaults to `auto`, so this is where a caller who did not pin it learns which one was produced.","type":"string"},"created":{"description":"Unix timestamp, in seconds, of when the generation completed. It is the one member OpenAI declares REQUIRED on an image-generation response, so it is present whenever OpenAI is the producer; a fal-, pika-, wavespeed- or runware-served response on any of the alt-backed ids omits it entirely, like the other four resolved-parameter members. Declared `int64` because a present-day epoch value is close enough to 2^31 that an unformatted `integer` generates a 32-bit field in many SDK generators.","format":"int64","type":"integer"},"output_format":{"description":"The encoding of the bytes in `data[].b64_json` — `png`, `webp` or `jpeg`. It is the format OpenAI actually encoded, which is the request's `output_format` when one was sent and `png` when none was, so a decoder can key off it rather than re-deriving the format from the request.","type":"string"},"quality":{"description":"The quality tier the generation actually ran at — OpenAI reports one of `low`, `medium`, `high`, `xhigh` or `max`. It is the RESOLVED tier, not an echo of the request, whose own `quality` defaults to `auto`.","type":"string"},"size":{"description":"The pixel dimensions the generation actually ran at, as `\u003cwidth\u003ex\u003cheight\u003e`. It is the RESOLVED size: the request's `size` defaults to `auto` and explicitly admits `auto`, so this is the only place the dimensions actually used are reported.","type":"string"}},"type":"object"}],"description":"Comfy Router output schema for the OpenAI gpt-image models: OpenAI's own image-generation response, forwarded unchanged. The operation is direct-return (`routerresult/classification.go` classifies `openai /images/generations` as ReturnModeDirect with no poll route), so the body a caller receives is this finished document from the one call. That row also raises the read limit to `BinaryResultMaxBytes`, because the images can arrive INLINE and `n` admits up to 10 of them at a `size` and `quality` the operation does not bound.\nOn the ids with a single producer — `openai/gpt-image-1` and `openai/gpt-image-1.5` — and on any of the alt-backed ids whenever OpenAI itself serves it, the generated images are in `data`, always INLINE at `data[].b64_json`: OpenAI has no `url` response format at all on this surface, so `url` stays unpopulated there even though the shared response component declares it. That also means the bytes are durable in the only sense that matters here — they are in the body, not behind a link that expires. `revised_prompt` is the prompt OpenAI rewrote rather than an asset, and it is present on a response whose image never arrived, so it must not be read as one. `usage` is OpenAI's own token accounting — OpenAI's numbers, not the Comfy charge.\n`openai/gpt-image-2` (`fal`, `pika`, `runware`, `wavespeed`), `openai/gpt-image-2.5-flare` (`fal`, `runware`, `wavespeed`) and `openai/gpt-image-2.5-sunburst` (`fal`, `runware`, `wavespeed`) have ALTERNATE producers: an eligible request on any of them may instead be served by fal, by wavespeed or by runware, and on `openai/gpt-image-2` also by pika. Each partner is reachable explicitly, via `model_provider=fal`, `model_provider=wavespeed`, `model_provider=runware` or (on `openai/gpt-image-2`) `model_provider=pika`; the pika leg is text-to-image only, so it never serves an edit. One may also serve a request that named nobody: `fallback_provider` (default ON) retries a GENERATION once against a registered alternate when the OpenAI attempt fails for a reason attributable to Router or to OpenAI, never to the caller's request. Do not read that retry as fal's alone — it walks the id's registered partners in a fixed order and takes the first whose provider gate, workspace partner-provider policy, eligibility valve and translation all succeed. That order is alphabetical by partner (fal, then pika on `openai/gpt-image-2`, then runware, then wavespeed), so the first of them the caller's gates admit answers it whenever fal is ruled out. And it does not apply to an EDIT: a body carrying `image` selects the multipart edits operation, for which the automatic retry is disabled outright, so an edit that OpenAI fails comes back as OpenAI's own failure rather than being retried against a partner. (Naming a partner explicitly still serves an edit, wherever that partner's translator carries the media field itself.) Every one of those routes is the alt-provider dispatch in `server/implementation/router_run.go`, whose `routerAltTranslators` supplies the translator half against the matching `routeralt` leg that carries the alt id (`fal-gpt-image-2`, `wavespeed-gpt-image-2.5-flare`, `runware-gpt-image-2.5-sunburst` and so on — one leg per id-and-partner pair); the two halves are joined at init and a half-registration fails the build. None of the partners returns inline base64 on any of the alt-backed ids — each only returns hosted URLs, so an alt-provider-served response carries `data[].url` and never `data[].b64_json`, the opposite of the OpenAI-served shape above. A caller that requested `response_format: b64_json` on an alt-provider-served attempt is told so through `X-Comfy-Router-Dropped-Params` rather than being given inline bytes. Both of those statements describe the answer TRANSLATED BACK into this contract, which is what the default `strict_mode=false` returns. `strict_mode=true` runs no translation in either direction: the partner's own raw envelope comes back unchanged (save pika's, from which Pika's own `billing` record is still removed) and carries no `X-Comfy-Router-Dropped-Params`, and Router returns that same raw envelope on the rarer path where a response cannot be translated back. Such a body is the PARTNER's contract, not this schema, and must not be parsed as this one — the call is billed either way. Their translators do not fabricate the OpenAI-specific fields they cannot produce: on an alt-provider-served response `usage`, `revised_prompt`, and all four resolved-parameter members below are entirely ABSENT, not merely empty. Branch on the keys a response actually carries rather than on an assumed producer; which partner served a given request on any of the alt-backed ids is a Comfy routing decision that can change without notice, and is not part of this contract.\nFor the single-producer ids, and for the alt-backed ids whenever OpenAI is the one that served it, the four resolved-parameter members (`background`, `output_format`, `quality`, `size`) report what OpenAI ACTUALLY RAN rather than echoing the request: `background`, `quality` and `size` each default to `auto` on the request side, so a caller who sends `auto` — or sends nothing — only learns the concrete background, tier and `\u003cwidth\u003ex\u003cheight\u003e` that were used by reading them here. `created` is the one member OpenAI declares required whenever OpenAI is the producer.","example":{"created":1767225600,"data":[{"b64_json":"PGJhc2U2ND4="}],"usage":{"input_tokens":12,"input_tokens_details":{"image_tokens":0,"text_tokens":12},"output_tokens":1056,"total_tokens":1068}}}}}}}}}},"components":{"schemas":{"OpenAIImageGenerationResponse":{"properties":{"data":{"items":{"properties":{"b64_json":{"description":"Base64 encoded image data","type":"string"},"revised_prompt":{"description":"Revised prompt","type":"string"},"url":{"description":"URL of the image","type":"string"}},"type":"object"},"type":"array"},"usage":{"properties":{"input_tokens":{"type":"integer"},"input_tokens_details":{"properties":{"image_tokens":{"type":"integer"},"text_tokens":{"type":"integer"}},"type":"object"},"output_tokens":{"type":"integer"},"output_tokens_details":{"properties":{"image_tokens":{"type":"integer"},"text_tokens":{"type":"integer"}},"type":"object"},"total_tokens":{"type":"integer"}},"type":"object"}},"type":"object"},"OpenAIImageRouterMedia":{"description":"One image, as either an https URL Router fetches on the caller's behalf or a `data:image/\u003cformat\u003e;base64,\u003cpayload\u003e` URI carrying the bytes inline. Accepted image types are png, webp and jpeg, and that set is ENFORCED at resolve time rather than left to the provider -- an svg, gif, avif or tiff is refused here, naming the image you sent, instead of becoming a partner error two hops from the cause.\nPrefer the URL form. A base64 payload inflates roughly 4/3 and the whole request body is capped at 100 MiB, so the inline form caps out near a 75 MB source image; a URL sidesteps that entirely. The per-image and per-request media ceilings stated at the end of this description are tighter than the body cap, so for an image of any real size they are what you meet first.\nTHE URL MUST RESOLVE WITHOUT YOUR CREDENTIALS. Router fetches it server-side and sends no caller credential with the request, so a presigned URL -- one whose signature is in the query string -- is the supported form. An authenticated endpoint such as `/api/assets/{id}/content` answers Router 401 rather than the image; request that URL yourself and pass the signed URL it redirects to.\nTHE FETCH IS CONFINED TO A NAMED SET OF HOSTS rather than the open internet, and the same list is re-applied to every redirect hop as well as to the URL you send -- so a URL on any other host, or one that redirects off the list, is refused before any provider is contacted. Today that list is Comfy's own asset delivery only: a signed `storage.googleapis.com` URL under a Comfy asset bucket, which is the form `/api/assets/{id}/content` redirects you to. A partner CDN or a plain https host is NOT on the list.\nTWO WIDENINGS ARE BEING ROLLED OUT AND ARE BOTH OFF BY DEFAULT TODAY. They ship behind ONE switch and are enabled together, so treat everything below as forthcoming rather than as something to build against yet -- ask before you rely on either.\nThe FIRST is the bucket `POST /customers/storage` signs your upload into. Until the rollout reaches your environment an upload URL from that endpoint is refused here, even though the bucket is Comfy's own and the host is the same one -- so \"upload to Comfy, then pass the URL\" does NOT work yet, and the form that does is the signed URL `/api/assets/{id}/content` redirects to.\nThe SECOND is ENABLED PER ACCOUNT rather than for everyone at once, because the bucket behind these hosts is yours rather than ours: even once the switch above is on, an account that has not had third-party inbound media enabled is refused, and told so in those words rather than told the host is unsupported. Ask your Comfy contact to enable it for your account. It additionally admits these third-party object-storage hosts, where the bucket is yours and Router makes no ownership claim on it: Cloudflare R2 (`\u003caccount\u003e.r2.cloudflarestorage.com`, `pub-\u003chash\u003e.r2.dev`), Amazon S3 (`s3.amazonaws.com` and `s3.\u003cregion\u003e.amazonaws.com`, in either the path-style or the `\u003cbucket\u003e.`-prefixed form), Azure Blob Storage (`\u003caccount\u003e.blob.core.windows.net`) and Alibaba Cloud OSS (`\u003cbucket\u003e.oss-\u003cregion\u003e.aliyuncs.com`, including the `oss-accelerate` endpoint). Those are object-storage endpoints specifically: a general-purpose endpoint on the same provider domain, such as an Alibaba Function Compute trigger, is not on the list and is refused.\nWHAT IS MATCHED IS THE HOSTNAME FORM, NOT THE PROVIDER -- so naming a provider above does NOT mean every URL that provider can issue is accepted, and three near-misses are worth stating outright because a caller can reasonably arrive with each of them. An R2 CUSTOM DOMAIN (`cdn.example.com`) is NOT accepted: Cloudflare documents `pub-\u003chash\u003e.r2.dev` as a rate-limited debug hostname and points production traffic at a custom domain instead, but a custom domain carries nothing in its hostname identifying it as R2, so Router cannot tell it from any other host and refuses it -- presign the `\u003caccount\u003e.r2.cloudflarestorage.com` endpoint, or use the `pub-\u003chash\u003e.r2.dev` form. S3's IPv6 dualstack endpoint (`s3.dualstack.\u003cregion\u003e.amazonaws.com`) is not accepted either; use the plain regional endpoint. Nor is Azure Blob's alternate DNS zone (`\u003caccount\u003e.z\u003cN\u003e.blob.storage.azure.net`); use `\u003caccount\u003e.blob.core.windows.net`. The refusal you get back names these same forms, so you do not have to come here to read them.\nThe `\u003caccount\u003e` and `pub-\u003chash\u003e` placeholders above show the form you will normally arrive with; they are not a depth restriction. Matching is on the object-storage domain suffix, so a deeper name under the same namespace (`a.b.pub-\u003chash\u003e.r2.dev`) is accepted as well. What is NOT widened by that is the namespace itself -- the suffix still has to be one of the ones listed.\nThe example below shows the shape only: a real value also carries the signing query parameters, which are deliberately omitted here because a published example lives in the spec forever and a real signature is both a leaked credential and a link that stops working.\nEach image is capped at 25 MiB and one request's images at 64 MiB in total.","example":"https://storage.googleapis.com/comfy-cloud-assets/0199b3f4-1d2e-7a3b-8c4d-5e6f70819234.png","type":"string"},"OpenAIImageRouterSource":{"description":"The source image(s) to edit. PRESENCE OF THIS FIELD SELECTS THE EDIT OPERATION -- omit it entirely for text-to-image generation.\nThe model's published example is the GENERATION call, which omits this field. Adding `image` is the whole of the difference between the two modes -- same endpoint, same model id -- except that an edit prompt describes the CHANGE you want rather than the scene, so the edit body below reads differently from the generation example even though only this field is structurally new.\nIt is copyable exactly as printed: `{\"prompt\": \"give the rocketship rainbow coloring\", \"image\": [\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==\"], \"size\": \"1024x1024\"}` -- the payload is a complete 1x1 PNG, not an abbreviation, and this body is pinned as an accepted case in Router's own request-validation tests. Swap in your own image's bytes.\nAccepts one image or an array of them; gpt-image-1 and later take up to 16, which Router enforces from this schema before dispatching.","oneOf":[{"$ref":"#/components/schemas/OpenAIImageRouterMedia"},{"items":{"$ref":"#/components/schemas/OpenAIImageRouterMedia"},"maxItems":16,"minItems":1,"type":"array"}]}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"openai/gpt-image-1.5","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image"],"x-comfy-router-output-modalities":["image"]}
diff --git a/router-schemas/openai/gpt-image-1.json b/router-schemas/openai/gpt-image-1.json
index 4e04579623..529d5f4613 100644
--- a/router-schemas/openai/gpt-image-1.json
+++ b/router-schemas/openai/gpt-image-1.json
@@ -1 +1 @@
-{"openapi":"3.0.2","info":{"title":"openai/gpt-image-1","description":"The request body Comfy Router accepts for the model \"openai/gpt-image-1\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"f44c74e2ce6b"},"paths":{"/v2/models/openai/gpt-image-1":{"post":{"operationId":"runRouterModel","summary":"Run openai/gpt-image-1 synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"description":"The request body for a gpt-image model. ONE model id reaches TWO upstream OpenAI operations and this body is what selects between them: OMIT `image` for text-to-image generation, INCLUDE it to edit the image you send. The example below is the generation call; adding `image` to it is the whole of the structural difference, and `image`'s own description carries a copyable edit body.","example":{"prompt":"a rocketship on a launchpad","quality":"low","size":"1024x1024"},"properties":{"background":{"description":"Background transparency. `auto` lets the model choose.","enum":["transparent","opaque","auto"],"example":"opaque","type":"string"},"image":{"$ref":"#/components/schemas/OpenAIImageRouterSource"},"mask":{"allOf":[{"$ref":"#/components/schemas/OpenAIImageRouterMedia"}],"description":"Optional. Marks the region to edit: its fully transparent areas are what the model replaces. Must be a PNG with an alpha channel, under 4 MiB -- the bound routerOpenAIMaskMaxBytes ENFORCES on the resolved bytes, so an oversize mask is refused here by its size rather than relayed from the provider -- and matching the dimensions of the first `image`. Same accepted forms as `image`. Ignored -- and meaningless -- when `image` is absent, because there is nothing to mask: Router strips it from the request rather than forwarding it to the text-to-image operation, which would reject it as an unknown parameter."},"model":{"description":"The gpt-image model to run. Router splices the addressed model id in, so a caller addressing /v2/models/openai/gpt-image-2 does not send this.","example":"gpt-image-2.5-flare","type":"string"},"moderation":{"description":"Content moderation setting","enum":["low","auto"],"example":"auto","type":"string"},"n":{"description":"The number of images to generate (1-10).","example":1,"maximum":10,"minimum":1,"type":"integer"},"output_compression":{"description":"Compression level for JPEG or WebP (0-100)","example":100,"maximum":100,"minimum":0,"type":"integer"},"output_format":{"description":"Format of the output image","enum":["png","webp","jpeg"],"example":"png","type":"string"},"partial_images":{"description":"Meaningful only alongside a live stream, which this surface cannot serve -- `stream` is settled to `false` even when you send it. On a generation, any value other than `0` or `null` is refused with a 400, whether or not the request also sends `stream: true`; on a request carrying `image` the field is dropped rather than forwarded. Omit it.","example":0,"maximum":3,"minimum":0,"nullable":true,"type":"integer"},"prompt":{"description":"A text description of the image to generate, or of the edit to make to `image`.","example":"Give the rocketship rainbow coloring","type":"string"},"quality":{"description":"The quality of the generated or edited image. `xhigh` and `max` are the two tiers gpt-image-2.5-flare and gpt-image-2.5-sunburst were added for; `standard` and `hd` are dall-e-era spellings no admitted model reads.","enum":["auto","low","medium","high","xhigh","max","standard","hd"],"example":"high","type":"string"},"size":{"description":"Size of the image (e.g., 1024x1024, 1536x1024, auto)","example":"1024x1536","type":"string"},"stream":{"description":"Declared so a caller who sends it is not refused, but INERT on this surface: Router SETTLES it to `false` before dispatch, because it captures the provider response rather than relaying a `text/event-stream` (a request carrying `image` drops the field instead, since the edit is re-encoded without it). No Comfy surface serves gpt-image partial-image streaming: the v1 `POST /proxy/openai/images/generations` ingress refuses `stream: true` with a 400 and never forwards the request upstream, so omit the field and read the completed response as one JSON body. Because the stream never opens, OpenAI's restriction on combining `stream: true` with `n` greater than 1 does not apply here; `n` is forwarded as sent.","example":false,"nullable":true,"type":"boolean"},"user":{"description":"A unique identifier for end-user monitoring","example":"user-1234","type":"string"}},"required":["prompt"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/OpenAIImageGenerationResponse"},{"properties":{"background":{"description":"Whether the generated image's background is `opaque` or `transparent`, as OpenAI resolved it for this generation. The request parameter defaults to `auto`, so this is where a caller who did not pin it learns which one was produced.","type":"string"},"created":{"description":"Unix timestamp, in seconds, of when the generation completed. It is the one member OpenAI declares REQUIRED on an image-generation response, so it is present whenever OpenAI is the producer; a fal-, wavespeed- or runware-served response on any of the alt-backed ids omits it entirely, like the other four resolved-parameter members. Declared `int64` because a present-day epoch value is close enough to 2^31 that an unformatted `integer` generates a 32-bit field in many SDK generators.","format":"int64","type":"integer"},"output_format":{"description":"The encoding of the bytes in `data[].b64_json` — `png`, `webp` or `jpeg`. It is the format OpenAI actually encoded, which is the request's `output_format` when one was sent and `png` when none was, so a decoder can key off it rather than re-deriving the format from the request.","type":"string"},"quality":{"description":"The quality tier the generation actually ran at — OpenAI reports one of `low`, `medium`, `high`, `xhigh` or `max`. It is the RESOLVED tier, not an echo of the request, whose own `quality` defaults to `auto`.","type":"string"},"size":{"description":"The pixel dimensions the generation actually ran at, as `\u003cwidth\u003ex\u003cheight\u003e`. It is the RESOLVED size: the request's `size` defaults to `auto` and explicitly admits `auto`, so this is the only place the dimensions actually used are reported.","type":"string"}},"type":"object"}],"description":"Comfy Router output schema for the OpenAI gpt-image models: OpenAI's own image-generation response, forwarded unchanged. The operation is direct-return (`routerresult/classification.go` classifies `openai /images/generations` as ReturnModeDirect with no poll route), so the body a caller receives is this finished document from the one call. That row also raises the read limit to `BinaryResultMaxBytes`, because the images can arrive INLINE and `n` admits up to 10 of them at a `size` and `quality` the operation does not bound.\nOn the ids with a single producer — `openai/gpt-image-1` and `openai/gpt-image-1.5` — and on any of the alt-backed ids whenever OpenAI itself serves it, the generated images are in `data`, always INLINE at `data[].b64_json`: OpenAI has no `url` response format at all on this surface, so `url` stays unpopulated there even though the shared response component declares it. That also means the bytes are durable in the only sense that matters here — they are in the body, not behind a link that expires. `revised_prompt` is the prompt OpenAI rewrote rather than an asset, and it is present on a response whose image never arrived, so it must not be read as one. `usage` is OpenAI's own token accounting — OpenAI's numbers, not the Comfy charge.\n`openai/gpt-image-2` (`fal`, `runware`, `wavespeed`), `openai/gpt-image-2.5-flare` (`fal`, `runware`, `wavespeed`) and `openai/gpt-image-2.5-sunburst` (`fal`, `runware`, `wavespeed`) have ALTERNATE producers: an eligible request on any of them may instead be served by fal, by wavespeed or by runware. Each partner is reachable explicitly, via `model_provider=fal`, `model_provider=wavespeed` or `model_provider=runware`. One may also serve a request that named nobody: `fallback_provider` (default ON) retries a GENERATION once against a registered alternate when the OpenAI attempt fails for a reason attributable to Router or to OpenAI, never to the caller's request. Do not read that retry as fal's alone — it walks the id's registered partners in a fixed order and takes the first whose provider gate, workspace partner-provider policy, eligibility valve and translation all succeed, so wavespeed or runware answers it whenever fal is ruled out. And it does not apply to an EDIT: a body carrying `image` selects the multipart edits operation, for which the automatic retry is disabled outright, so an edit that OpenAI fails comes back as OpenAI's own failure rather than being retried against a partner. (Naming a partner explicitly still serves an edit, wherever that partner's translator carries the media field itself.) Every one of those routes is the alt-provider dispatch in `server/implementation/router_run.go`, whose `routerAltTranslators` supplies the translator half against the matching `routeralt` leg that carries the alt id (`fal-gpt-image-2`, `wavespeed-gpt-image-2.5-flare`, `runware-gpt-image-2.5-sunburst` and so on — one leg per id-and-partner pair); the two halves are joined at init and a half-registration fails the build. None of the partners returns inline base64 on any of the alt-backed ids — each only returns hosted URLs, so an alt-provider-served response carries `data[].url` and never `data[].b64_json`, the opposite of the OpenAI-served shape above. A caller that requested `response_format: b64_json` on an alt-provider-served attempt is told so through `X-Comfy-Router-Dropped-Params` rather than being given inline bytes. Both of those statements describe the answer TRANSLATED BACK into this contract, which is what the default `strict_mode=false` returns. `strict_mode=true` runs no translation in either direction: the partner's own raw envelope comes back unchanged and carries no `X-Comfy-Router-Dropped-Params`, and Router returns that same raw envelope on the rarer path where a response cannot be translated back. Such a body is the PARTNER's contract, not this schema, and must not be parsed as this one — the call is billed either way. Their translators do not fabricate the OpenAI-specific fields they cannot produce: on an alt-provider-served response `usage`, `revised_prompt`, and all four resolved-parameter members below are entirely ABSENT, not merely empty. Branch on the keys a response actually carries rather than on an assumed producer; which partner served a given request on any of the alt-backed ids is a Comfy routing decision that can change without notice, and is not part of this contract.\nFor the single-producer ids, and for the alt-backed ids whenever OpenAI is the one that served it, the four resolved-parameter members (`background`, `output_format`, `quality`, `size`) report what OpenAI ACTUALLY RAN rather than echoing the request: `background`, `quality` and `size` each default to `auto` on the request side, so a caller who sends `auto` — or sends nothing — only learns the concrete background, tier and `\u003cwidth\u003ex\u003cheight\u003e` that were used by reading them here. `created` is the one member OpenAI declares required whenever OpenAI is the producer.","example":{"created":1767225600,"data":[{"b64_json":"PGJhc2U2ND4="}],"usage":{"input_tokens":12,"input_tokens_details":{"image_tokens":0,"text_tokens":12},"output_tokens":1056,"total_tokens":1068}}}}}}}}}},"components":{"schemas":{"OpenAIImageGenerationResponse":{"properties":{"data":{"items":{"properties":{"b64_json":{"description":"Base64 encoded image data","type":"string"},"revised_prompt":{"description":"Revised prompt","type":"string"},"url":{"description":"URL of the image","type":"string"}},"type":"object"},"type":"array"},"usage":{"properties":{"input_tokens":{"type":"integer"},"input_tokens_details":{"properties":{"image_tokens":{"type":"integer"},"text_tokens":{"type":"integer"}},"type":"object"},"output_tokens":{"type":"integer"},"output_tokens_details":{"properties":{"image_tokens":{"type":"integer"},"text_tokens":{"type":"integer"}},"type":"object"},"total_tokens":{"type":"integer"}},"type":"object"}},"type":"object"},"OpenAIImageRouterMedia":{"description":"One image, as either an https URL Router fetches on the caller's behalf or a `data:image/\u003cformat\u003e;base64,\u003cpayload\u003e` URI carrying the bytes inline. Accepted image types are png, webp and jpeg, and that set is ENFORCED at resolve time rather than left to the provider -- an svg, gif, avif or tiff is refused here, naming the image you sent, instead of becoming a partner error two hops from the cause.\nPrefer the URL form. A base64 payload inflates roughly 4/3 and the whole request body is capped at 100 MiB, so the inline form caps out near a 75 MB source image; a URL sidesteps that entirely. The per-image and per-request media ceilings stated at the end of this description are tighter than the body cap, so for an image of any real size they are what you meet first.\nTHE URL MUST RESOLVE WITHOUT YOUR CREDENTIALS. Router fetches it server-side and sends no caller credential with the request, so a presigned URL -- one whose signature is in the query string -- is the supported form. An authenticated endpoint such as `/api/assets/{id}/content` answers Router 401 rather than the image; request that URL yourself and pass the signed URL it redirects to.\nTHE FETCH IS CONFINED TO A NAMED SET OF HOSTS rather than the open internet, and the same list is re-applied to every redirect hop as well as to the URL you send -- so a URL on any other host, or one that redirects off the list, is refused before any provider is contacted. Today that list is Comfy's own asset delivery only: a signed `storage.googleapis.com` URL under a Comfy asset bucket, which is the form `/api/assets/{id}/content` redirects you to. A partner CDN or a plain https host is NOT on the list.\nTWO WIDENINGS ARE BEING ROLLED OUT AND ARE BOTH OFF BY DEFAULT TODAY. They ship behind ONE switch and are enabled together, so treat everything below as forthcoming rather than as something to build against yet -- ask before you rely on either.\nThe FIRST is the bucket `POST /customers/storage` signs your upload into. Until the rollout reaches your environment an upload URL from that endpoint is refused here, even though the bucket is Comfy's own and the host is the same one -- so \"upload to Comfy, then pass the URL\" does NOT work yet, and the form that does is the signed URL `/api/assets/{id}/content` redirects to.\nThe SECOND is ENABLED PER ACCOUNT rather than for everyone at once, because the bucket behind these hosts is yours rather than ours: even once the switch above is on, an account that has not had third-party inbound media enabled is refused, and told so in those words rather than told the host is unsupported. Ask your Comfy contact to enable it for your account. It additionally admits these third-party object-storage hosts, where the bucket is yours and Router makes no ownership claim on it: Cloudflare R2 (`\u003caccount\u003e.r2.cloudflarestorage.com`, `pub-\u003chash\u003e.r2.dev`), Amazon S3 (`s3.amazonaws.com` and `s3.\u003cregion\u003e.amazonaws.com`, in either the path-style or the `\u003cbucket\u003e.`-prefixed form), Azure Blob Storage (`\u003caccount\u003e.blob.core.windows.net`) and Alibaba Cloud OSS (`\u003cbucket\u003e.oss-\u003cregion\u003e.aliyuncs.com`, including the `oss-accelerate` endpoint). Those are object-storage endpoints specifically: a general-purpose endpoint on the same provider domain, such as an Alibaba Function Compute trigger, is not on the list and is refused.\nWHAT IS MATCHED IS THE HOSTNAME FORM, NOT THE PROVIDER -- so naming a provider above does NOT mean every URL that provider can issue is accepted, and three near-misses are worth stating outright because a caller can reasonably arrive with each of them. An R2 CUSTOM DOMAIN (`cdn.example.com`) is NOT accepted: Cloudflare documents `pub-\u003chash\u003e.r2.dev` as a rate-limited debug hostname and points production traffic at a custom domain instead, but a custom domain carries nothing in its hostname identifying it as R2, so Router cannot tell it from any other host and refuses it -- presign the `\u003caccount\u003e.r2.cloudflarestorage.com` endpoint, or use the `pub-\u003chash\u003e.r2.dev` form. S3's IPv6 dualstack endpoint (`s3.dualstack.\u003cregion\u003e.amazonaws.com`) is not accepted either; use the plain regional endpoint. Nor is Azure Blob's alternate DNS zone (`\u003caccount\u003e.z\u003cN\u003e.blob.storage.azure.net`); use `\u003caccount\u003e.blob.core.windows.net`. The refusal you get back names these same forms, so you do not have to come here to read them.\nThe `\u003caccount\u003e` and `pub-\u003chash\u003e` placeholders above show the form you will normally arrive with; they are not a depth restriction. Matching is on the object-storage domain suffix, so a deeper name under the same namespace (`a.b.pub-\u003chash\u003e.r2.dev`) is accepted as well. What is NOT widened by that is the namespace itself -- the suffix still has to be one of the ones listed.\nThe example below shows the shape only: a real value also carries the signing query parameters, which are deliberately omitted here because a published example lives in the spec forever and a real signature is both a leaked credential and a link that stops working.\nEach image is capped at 25 MiB and one request's images at 64 MiB in total.","example":"https://storage.googleapis.com/comfy-cloud-assets/0199b3f4-1d2e-7a3b-8c4d-5e6f70819234.png","type":"string"},"OpenAIImageRouterSource":{"description":"The source image(s) to edit. PRESENCE OF THIS FIELD SELECTS THE EDIT OPERATION -- omit it entirely for text-to-image generation.\nThe model's published example is the GENERATION call, which omits this field. Adding `image` is the whole of the difference between the two modes -- same endpoint, same model id -- except that an edit prompt describes the CHANGE you want rather than the scene, so the edit body below reads differently from the generation example even though only this field is structurally new.\nIt is copyable exactly as printed: `{\"prompt\": \"give the rocketship rainbow coloring\", \"image\": [\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==\"], \"size\": \"1024x1024\"}` -- the payload is a complete 1x1 PNG, not an abbreviation, and this body is pinned as an accepted case in Router's own request-validation tests. Swap in your own image's bytes.\nAccepts one image or an array of them; gpt-image-1 and later take up to 16, which Router enforces from this schema before dispatching.","oneOf":[{"$ref":"#/components/schemas/OpenAIImageRouterMedia"},{"items":{"$ref":"#/components/schemas/OpenAIImageRouterMedia"},"maxItems":16,"minItems":1,"type":"array"}]}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"openai/gpt-image-1","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image"],"x-comfy-router-output-modalities":["image"]}
+{"openapi":"3.0.2","info":{"title":"openai/gpt-image-1","description":"The request body Comfy Router accepts for the model \"openai/gpt-image-1\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"1786a32eea22"},"paths":{"/v2/models/openai/gpt-image-1":{"post":{"operationId":"runRouterModel","summary":"Run openai/gpt-image-1 synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"description":"The request body for a gpt-image model. ONE model id reaches TWO upstream OpenAI operations and this body is what selects between them: OMIT `image` for text-to-image generation, INCLUDE it to edit the image you send. The example below is the generation call; adding `image` to it is the whole of the structural difference, and `image`'s own description carries a copyable edit body.","example":{"prompt":"a rocketship on a launchpad","quality":"low","size":"1024x1024"},"properties":{"background":{"description":"Background transparency. `auto` lets the model choose.","enum":["transparent","opaque","auto"],"example":"opaque","type":"string"},"image":{"$ref":"#/components/schemas/OpenAIImageRouterSource"},"mask":{"allOf":[{"$ref":"#/components/schemas/OpenAIImageRouterMedia"}],"description":"Optional. Marks the region to edit: its fully transparent areas are what the model replaces. Must be a PNG with an alpha channel, under 4 MiB -- the bound routerOpenAIMaskMaxBytes ENFORCES on the resolved bytes, so an oversize mask is refused here by its size rather than relayed from the provider -- and matching the dimensions of the first `image`. Same accepted forms as `image`. Ignored -- and meaningless -- when `image` is absent, because there is nothing to mask: Router strips it from the request rather than forwarding it to the text-to-image operation, which would reject it as an unknown parameter."},"model":{"description":"The gpt-image model to run. Router splices the addressed model id in, so a caller addressing /v2/models/openai/gpt-image-2 does not send this.","example":"gpt-image-2.5-flare","type":"string"},"moderation":{"description":"Content moderation setting","enum":["low","auto"],"example":"auto","type":"string"},"n":{"description":"The number of images to generate (1-10).","example":1,"maximum":10,"minimum":1,"type":"integer"},"output_compression":{"description":"Compression level for JPEG or WebP (0-100)","example":100,"maximum":100,"minimum":0,"type":"integer"},"output_format":{"description":"Format of the output image","enum":["png","webp","jpeg"],"example":"png","type":"string"},"partial_images":{"description":"Meaningful only alongside a live stream, which this surface cannot serve -- `stream` is settled to `false` even when you send it. On a generation, any value other than `0` or `null` is refused with a 400, whether or not the request also sends `stream: true`; on a request carrying `image` the field is dropped rather than forwarded. Omit it.","example":0,"maximum":3,"minimum":0,"nullable":true,"type":"integer"},"prompt":{"description":"A text description of the image to generate, or of the edit to make to `image`.","example":"Give the rocketship rainbow coloring","type":"string"},"quality":{"description":"The quality of the generated or edited image. `xhigh` and `max` are the two tiers gpt-image-2.5-flare and gpt-image-2.5-sunburst were added for; `standard` and `hd` are dall-e-era spellings no admitted model reads.","enum":["auto","low","medium","high","xhigh","max","standard","hd"],"example":"high","type":"string"},"size":{"description":"Size of the image (e.g., 1024x1024, 1536x1024, auto)","example":"1024x1536","type":"string"},"stream":{"description":"Declared so a caller who sends it is not refused, but INERT on this surface: Router SETTLES it to `false` before dispatch, because it captures the provider response rather than relaying a `text/event-stream` (a request carrying `image` drops the field instead, since the edit is re-encoded without it). No Comfy surface serves gpt-image partial-image streaming: the v1 `POST /proxy/openai/images/generations` ingress refuses `stream: true` with a 400 and never forwards the request upstream, so omit the field and read the completed response as one JSON body. Because the stream never opens, OpenAI's restriction on combining `stream: true` with `n` greater than 1 does not apply here; `n` is forwarded as sent.","example":false,"nullable":true,"type":"boolean"},"user":{"description":"A unique identifier for end-user monitoring","example":"user-1234","type":"string"}},"required":["prompt"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/OpenAIImageGenerationResponse"},{"properties":{"background":{"description":"Whether the generated image's background is `opaque` or `transparent`, as OpenAI resolved it for this generation. The request parameter defaults to `auto`, so this is where a caller who did not pin it learns which one was produced.","type":"string"},"created":{"description":"Unix timestamp, in seconds, of when the generation completed. It is the one member OpenAI declares REQUIRED on an image-generation response, so it is present whenever OpenAI is the producer; a fal-, pika-, wavespeed- or runware-served response on any of the alt-backed ids omits it entirely, like the other four resolved-parameter members. Declared `int64` because a present-day epoch value is close enough to 2^31 that an unformatted `integer` generates a 32-bit field in many SDK generators.","format":"int64","type":"integer"},"output_format":{"description":"The encoding of the bytes in `data[].b64_json` — `png`, `webp` or `jpeg`. It is the format OpenAI actually encoded, which is the request's `output_format` when one was sent and `png` when none was, so a decoder can key off it rather than re-deriving the format from the request.","type":"string"},"quality":{"description":"The quality tier the generation actually ran at — OpenAI reports one of `low`, `medium`, `high`, `xhigh` or `max`. It is the RESOLVED tier, not an echo of the request, whose own `quality` defaults to `auto`.","type":"string"},"size":{"description":"The pixel dimensions the generation actually ran at, as `\u003cwidth\u003ex\u003cheight\u003e`. It is the RESOLVED size: the request's `size` defaults to `auto` and explicitly admits `auto`, so this is the only place the dimensions actually used are reported.","type":"string"}},"type":"object"}],"description":"Comfy Router output schema for the OpenAI gpt-image models: OpenAI's own image-generation response, forwarded unchanged. The operation is direct-return (`routerresult/classification.go` classifies `openai /images/generations` as ReturnModeDirect with no poll route), so the body a caller receives is this finished document from the one call. That row also raises the read limit to `BinaryResultMaxBytes`, because the images can arrive INLINE and `n` admits up to 10 of them at a `size` and `quality` the operation does not bound.\nOn the ids with a single producer — `openai/gpt-image-1` and `openai/gpt-image-1.5` — and on any of the alt-backed ids whenever OpenAI itself serves it, the generated images are in `data`, always INLINE at `data[].b64_json`: OpenAI has no `url` response format at all on this surface, so `url` stays unpopulated there even though the shared response component declares it. That also means the bytes are durable in the only sense that matters here — they are in the body, not behind a link that expires. `revised_prompt` is the prompt OpenAI rewrote rather than an asset, and it is present on a response whose image never arrived, so it must not be read as one. `usage` is OpenAI's own token accounting — OpenAI's numbers, not the Comfy charge.\n`openai/gpt-image-2` (`fal`, `pika`, `runware`, `wavespeed`), `openai/gpt-image-2.5-flare` (`fal`, `runware`, `wavespeed`) and `openai/gpt-image-2.5-sunburst` (`fal`, `runware`, `wavespeed`) have ALTERNATE producers: an eligible request on any of them may instead be served by fal, by wavespeed or by runware, and on `openai/gpt-image-2` also by pika. Each partner is reachable explicitly, via `model_provider=fal`, `model_provider=wavespeed`, `model_provider=runware` or (on `openai/gpt-image-2`) `model_provider=pika`; the pika leg is text-to-image only, so it never serves an edit. One may also serve a request that named nobody: `fallback_provider` (default ON) retries a GENERATION once against a registered alternate when the OpenAI attempt fails for a reason attributable to Router or to OpenAI, never to the caller's request. Do not read that retry as fal's alone — it walks the id's registered partners in a fixed order and takes the first whose provider gate, workspace partner-provider policy, eligibility valve and translation all succeed. That order is alphabetical by partner (fal, then pika on `openai/gpt-image-2`, then runware, then wavespeed), so the first of them the caller's gates admit answers it whenever fal is ruled out. And it does not apply to an EDIT: a body carrying `image` selects the multipart edits operation, for which the automatic retry is disabled outright, so an edit that OpenAI fails comes back as OpenAI's own failure rather than being retried against a partner. (Naming a partner explicitly still serves an edit, wherever that partner's translator carries the media field itself.) Every one of those routes is the alt-provider dispatch in `server/implementation/router_run.go`, whose `routerAltTranslators` supplies the translator half against the matching `routeralt` leg that carries the alt id (`fal-gpt-image-2`, `wavespeed-gpt-image-2.5-flare`, `runware-gpt-image-2.5-sunburst` and so on — one leg per id-and-partner pair); the two halves are joined at init and a half-registration fails the build. None of the partners returns inline base64 on any of the alt-backed ids — each only returns hosted URLs, so an alt-provider-served response carries `data[].url` and never `data[].b64_json`, the opposite of the OpenAI-served shape above. A caller that requested `response_format: b64_json` on an alt-provider-served attempt is told so through `X-Comfy-Router-Dropped-Params` rather than being given inline bytes. Both of those statements describe the answer TRANSLATED BACK into this contract, which is what the default `strict_mode=false` returns. `strict_mode=true` runs no translation in either direction: the partner's own raw envelope comes back unchanged (save pika's, from which Pika's own `billing` record is still removed) and carries no `X-Comfy-Router-Dropped-Params`, and Router returns that same raw envelope on the rarer path where a response cannot be translated back. Such a body is the PARTNER's contract, not this schema, and must not be parsed as this one — the call is billed either way. Their translators do not fabricate the OpenAI-specific fields they cannot produce: on an alt-provider-served response `usage`, `revised_prompt`, and all four resolved-parameter members below are entirely ABSENT, not merely empty. Branch on the keys a response actually carries rather than on an assumed producer; which partner served a given request on any of the alt-backed ids is a Comfy routing decision that can change without notice, and is not part of this contract.\nFor the single-producer ids, and for the alt-backed ids whenever OpenAI is the one that served it, the four resolved-parameter members (`background`, `output_format`, `quality`, `size`) report what OpenAI ACTUALLY RAN rather than echoing the request: `background`, `quality` and `size` each default to `auto` on the request side, so a caller who sends `auto` — or sends nothing — only learns the concrete background, tier and `\u003cwidth\u003ex\u003cheight\u003e` that were used by reading them here. `created` is the one member OpenAI declares required whenever OpenAI is the producer.","example":{"created":1767225600,"data":[{"b64_json":"PGJhc2U2ND4="}],"usage":{"input_tokens":12,"input_tokens_details":{"image_tokens":0,"text_tokens":12},"output_tokens":1056,"total_tokens":1068}}}}}}}}}},"components":{"schemas":{"OpenAIImageGenerationResponse":{"properties":{"data":{"items":{"properties":{"b64_json":{"description":"Base64 encoded image data","type":"string"},"revised_prompt":{"description":"Revised prompt","type":"string"},"url":{"description":"URL of the image","type":"string"}},"type":"object"},"type":"array"},"usage":{"properties":{"input_tokens":{"type":"integer"},"input_tokens_details":{"properties":{"image_tokens":{"type":"integer"},"text_tokens":{"type":"integer"}},"type":"object"},"output_tokens":{"type":"integer"},"output_tokens_details":{"properties":{"image_tokens":{"type":"integer"},"text_tokens":{"type":"integer"}},"type":"object"},"total_tokens":{"type":"integer"}},"type":"object"}},"type":"object"},"OpenAIImageRouterMedia":{"description":"One image, as either an https URL Router fetches on the caller's behalf or a `data:image/\u003cformat\u003e;base64,\u003cpayload\u003e` URI carrying the bytes inline. Accepted image types are png, webp and jpeg, and that set is ENFORCED at resolve time rather than left to the provider -- an svg, gif, avif or tiff is refused here, naming the image you sent, instead of becoming a partner error two hops from the cause.\nPrefer the URL form. A base64 payload inflates roughly 4/3 and the whole request body is capped at 100 MiB, so the inline form caps out near a 75 MB source image; a URL sidesteps that entirely. The per-image and per-request media ceilings stated at the end of this description are tighter than the body cap, so for an image of any real size they are what you meet first.\nTHE URL MUST RESOLVE WITHOUT YOUR CREDENTIALS. Router fetches it server-side and sends no caller credential with the request, so a presigned URL -- one whose signature is in the query string -- is the supported form. An authenticated endpoint such as `/api/assets/{id}/content` answers Router 401 rather than the image; request that URL yourself and pass the signed URL it redirects to.\nTHE FETCH IS CONFINED TO A NAMED SET OF HOSTS rather than the open internet, and the same list is re-applied to every redirect hop as well as to the URL you send -- so a URL on any other host, or one that redirects off the list, is refused before any provider is contacted. Today that list is Comfy's own asset delivery only: a signed `storage.googleapis.com` URL under a Comfy asset bucket, which is the form `/api/assets/{id}/content` redirects you to. A partner CDN or a plain https host is NOT on the list.\nTWO WIDENINGS ARE BEING ROLLED OUT AND ARE BOTH OFF BY DEFAULT TODAY. They ship behind ONE switch and are enabled together, so treat everything below as forthcoming rather than as something to build against yet -- ask before you rely on either.\nThe FIRST is the bucket `POST /customers/storage` signs your upload into. Until the rollout reaches your environment an upload URL from that endpoint is refused here, even though the bucket is Comfy's own and the host is the same one -- so \"upload to Comfy, then pass the URL\" does NOT work yet, and the form that does is the signed URL `/api/assets/{id}/content` redirects to.\nThe SECOND is ENABLED PER ACCOUNT rather than for everyone at once, because the bucket behind these hosts is yours rather than ours: even once the switch above is on, an account that has not had third-party inbound media enabled is refused, and told so in those words rather than told the host is unsupported. Ask your Comfy contact to enable it for your account. It additionally admits these third-party object-storage hosts, where the bucket is yours and Router makes no ownership claim on it: Cloudflare R2 (`\u003caccount\u003e.r2.cloudflarestorage.com`, `pub-\u003chash\u003e.r2.dev`), Amazon S3 (`s3.amazonaws.com` and `s3.\u003cregion\u003e.amazonaws.com`, in either the path-style or the `\u003cbucket\u003e.`-prefixed form), Azure Blob Storage (`\u003caccount\u003e.blob.core.windows.net`) and Alibaba Cloud OSS (`\u003cbucket\u003e.oss-\u003cregion\u003e.aliyuncs.com`, including the `oss-accelerate` endpoint). Those are object-storage endpoints specifically: a general-purpose endpoint on the same provider domain, such as an Alibaba Function Compute trigger, is not on the list and is refused.\nWHAT IS MATCHED IS THE HOSTNAME FORM, NOT THE PROVIDER -- so naming a provider above does NOT mean every URL that provider can issue is accepted, and three near-misses are worth stating outright because a caller can reasonably arrive with each of them. An R2 CUSTOM DOMAIN (`cdn.example.com`) is NOT accepted: Cloudflare documents `pub-\u003chash\u003e.r2.dev` as a rate-limited debug hostname and points production traffic at a custom domain instead, but a custom domain carries nothing in its hostname identifying it as R2, so Router cannot tell it from any other host and refuses it -- presign the `\u003caccount\u003e.r2.cloudflarestorage.com` endpoint, or use the `pub-\u003chash\u003e.r2.dev` form. S3's IPv6 dualstack endpoint (`s3.dualstack.\u003cregion\u003e.amazonaws.com`) is not accepted either; use the plain regional endpoint. Nor is Azure Blob's alternate DNS zone (`\u003caccount\u003e.z\u003cN\u003e.blob.storage.azure.net`); use `\u003caccount\u003e.blob.core.windows.net`. The refusal you get back names these same forms, so you do not have to come here to read them.\nThe `\u003caccount\u003e` and `pub-\u003chash\u003e` placeholders above show the form you will normally arrive with; they are not a depth restriction. Matching is on the object-storage domain suffix, so a deeper name under the same namespace (`a.b.pub-\u003chash\u003e.r2.dev`) is accepted as well. What is NOT widened by that is the namespace itself -- the suffix still has to be one of the ones listed.\nThe example below shows the shape only: a real value also carries the signing query parameters, which are deliberately omitted here because a published example lives in the spec forever and a real signature is both a leaked credential and a link that stops working.\nEach image is capped at 25 MiB and one request's images at 64 MiB in total.","example":"https://storage.googleapis.com/comfy-cloud-assets/0199b3f4-1d2e-7a3b-8c4d-5e6f70819234.png","type":"string"},"OpenAIImageRouterSource":{"description":"The source image(s) to edit. PRESENCE OF THIS FIELD SELECTS THE EDIT OPERATION -- omit it entirely for text-to-image generation.\nThe model's published example is the GENERATION call, which omits this field. Adding `image` is the whole of the difference between the two modes -- same endpoint, same model id -- except that an edit prompt describes the CHANGE you want rather than the scene, so the edit body below reads differently from the generation example even though only this field is structurally new.\nIt is copyable exactly as printed: `{\"prompt\": \"give the rocketship rainbow coloring\", \"image\": [\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==\"], \"size\": \"1024x1024\"}` -- the payload is a complete 1x1 PNG, not an abbreviation, and this body is pinned as an accepted case in Router's own request-validation tests. Swap in your own image's bytes.\nAccepts one image or an array of them; gpt-image-1 and later take up to 16, which Router enforces from this schema before dispatching.","oneOf":[{"$ref":"#/components/schemas/OpenAIImageRouterMedia"},{"items":{"$ref":"#/components/schemas/OpenAIImageRouterMedia"},"maxItems":16,"minItems":1,"type":"array"}]}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"openai/gpt-image-1","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image"],"x-comfy-router-output-modalities":["image"]}
diff --git a/router-schemas/openai/gpt-image-2.5-flare.json b/router-schemas/openai/gpt-image-2.5-flare.json
index a2c5d9fe31..ce6aacc8db 100644
--- a/router-schemas/openai/gpt-image-2.5-flare.json
+++ b/router-schemas/openai/gpt-image-2.5-flare.json
@@ -1 +1 @@
-{"openapi":"3.0.2","info":{"title":"openai/gpt-image-2.5-flare","description":"The request body Comfy Router accepts for the model \"openai/gpt-image-2.5-flare\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"f44c74e2ce6b"},"paths":{"/v2/models/openai/gpt-image-2.5-flare":{"post":{"operationId":"runRouterModel","summary":"Run openai/gpt-image-2.5-flare synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"description":"The request body for a gpt-image model. ONE model id reaches TWO upstream OpenAI operations and this body is what selects between them: OMIT `image` for text-to-image generation, INCLUDE it to edit the image you send. The example below is the generation call; adding `image` to it is the whole of the structural difference, and `image`'s own description carries a copyable edit body.","example":{"prompt":"a rocketship on a launchpad","quality":"low","size":"1024x1024"},"properties":{"background":{"description":"Background transparency. `auto` lets the model choose.","enum":["transparent","opaque","auto"],"example":"opaque","type":"string"},"image":{"$ref":"#/components/schemas/OpenAIImageRouterSource"},"mask":{"allOf":[{"$ref":"#/components/schemas/OpenAIImageRouterMedia"}],"description":"Optional. Marks the region to edit: its fully transparent areas are what the model replaces. Must be a PNG with an alpha channel, under 4 MiB -- the bound routerOpenAIMaskMaxBytes ENFORCES on the resolved bytes, so an oversize mask is refused here by its size rather than relayed from the provider -- and matching the dimensions of the first `image`. Same accepted forms as `image`. Ignored -- and meaningless -- when `image` is absent, because there is nothing to mask: Router strips it from the request rather than forwarding it to the text-to-image operation, which would reject it as an unknown parameter."},"model":{"description":"The gpt-image model to run. Router splices the addressed model id in, so a caller addressing /v2/models/openai/gpt-image-2 does not send this.","example":"gpt-image-2.5-flare","type":"string"},"moderation":{"description":"Content moderation setting","enum":["low","auto"],"example":"auto","type":"string"},"n":{"description":"The number of images to generate (1-10).","example":1,"maximum":10,"minimum":1,"type":"integer"},"output_compression":{"description":"Compression level for JPEG or WebP (0-100)","example":100,"maximum":100,"minimum":0,"type":"integer"},"output_format":{"description":"Format of the output image","enum":["png","webp","jpeg"],"example":"png","type":"string"},"partial_images":{"description":"Meaningful only alongside a live stream, which this surface cannot serve -- `stream` is settled to `false` even when you send it. On a generation, any value other than `0` or `null` is refused with a 400, whether or not the request also sends `stream: true`; on a request carrying `image` the field is dropped rather than forwarded. Omit it.","example":0,"maximum":3,"minimum":0,"nullable":true,"type":"integer"},"prompt":{"description":"A text description of the image to generate, or of the edit to make to `image`.","example":"Give the rocketship rainbow coloring","type":"string"},"quality":{"description":"The quality of the generated or edited image. `xhigh` and `max` are the two tiers gpt-image-2.5-flare and gpt-image-2.5-sunburst were added for; `standard` and `hd` are dall-e-era spellings no admitted model reads.","enum":["auto","low","medium","high","xhigh","max","standard","hd"],"example":"high","type":"string"},"size":{"description":"Size of the image (e.g., 1024x1024, 1536x1024, auto)","example":"1024x1536","type":"string"},"stream":{"description":"Declared so a caller who sends it is not refused, but INERT on this surface: Router SETTLES it to `false` before dispatch, because it captures the provider response rather than relaying a `text/event-stream` (a request carrying `image` drops the field instead, since the edit is re-encoded without it). No Comfy surface serves gpt-image partial-image streaming: the v1 `POST /proxy/openai/images/generations` ingress refuses `stream: true` with a 400 and never forwards the request upstream, so omit the field and read the completed response as one JSON body. Because the stream never opens, OpenAI's restriction on combining `stream: true` with `n` greater than 1 does not apply here; `n` is forwarded as sent.","example":false,"nullable":true,"type":"boolean"},"user":{"description":"A unique identifier for end-user monitoring","example":"user-1234","type":"string"}},"required":["prompt"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/OpenAIImageGenerationResponse"},{"properties":{"background":{"description":"Whether the generated image's background is `opaque` or `transparent`, as OpenAI resolved it for this generation. The request parameter defaults to `auto`, so this is where a caller who did not pin it learns which one was produced.","type":"string"},"created":{"description":"Unix timestamp, in seconds, of when the generation completed. It is the one member OpenAI declares REQUIRED on an image-generation response, so it is present whenever OpenAI is the producer; a fal-, wavespeed- or runware-served response on any of the alt-backed ids omits it entirely, like the other four resolved-parameter members. Declared `int64` because a present-day epoch value is close enough to 2^31 that an unformatted `integer` generates a 32-bit field in many SDK generators.","format":"int64","type":"integer"},"output_format":{"description":"The encoding of the bytes in `data[].b64_json` — `png`, `webp` or `jpeg`. It is the format OpenAI actually encoded, which is the request's `output_format` when one was sent and `png` when none was, so a decoder can key off it rather than re-deriving the format from the request.","type":"string"},"quality":{"description":"The quality tier the generation actually ran at — OpenAI reports one of `low`, `medium`, `high`, `xhigh` or `max`. It is the RESOLVED tier, not an echo of the request, whose own `quality` defaults to `auto`.","type":"string"},"size":{"description":"The pixel dimensions the generation actually ran at, as `\u003cwidth\u003ex\u003cheight\u003e`. It is the RESOLVED size: the request's `size` defaults to `auto` and explicitly admits `auto`, so this is the only place the dimensions actually used are reported.","type":"string"}},"type":"object"}],"description":"Comfy Router output schema for the OpenAI gpt-image models: OpenAI's own image-generation response, forwarded unchanged. The operation is direct-return (`routerresult/classification.go` classifies `openai /images/generations` as ReturnModeDirect with no poll route), so the body a caller receives is this finished document from the one call. That row also raises the read limit to `BinaryResultMaxBytes`, because the images can arrive INLINE and `n` admits up to 10 of them at a `size` and `quality` the operation does not bound.\nOn the ids with a single producer — `openai/gpt-image-1` and `openai/gpt-image-1.5` — and on any of the alt-backed ids whenever OpenAI itself serves it, the generated images are in `data`, always INLINE at `data[].b64_json`: OpenAI has no `url` response format at all on this surface, so `url` stays unpopulated there even though the shared response component declares it. That also means the bytes are durable in the only sense that matters here — they are in the body, not behind a link that expires. `revised_prompt` is the prompt OpenAI rewrote rather than an asset, and it is present on a response whose image never arrived, so it must not be read as one. `usage` is OpenAI's own token accounting — OpenAI's numbers, not the Comfy charge.\n`openai/gpt-image-2` (`fal`, `runware`, `wavespeed`), `openai/gpt-image-2.5-flare` (`fal`, `runware`, `wavespeed`) and `openai/gpt-image-2.5-sunburst` (`fal`, `runware`, `wavespeed`) have ALTERNATE producers: an eligible request on any of them may instead be served by fal, by wavespeed or by runware. Each partner is reachable explicitly, via `model_provider=fal`, `model_provider=wavespeed` or `model_provider=runware`. One may also serve a request that named nobody: `fallback_provider` (default ON) retries a GENERATION once against a registered alternate when the OpenAI attempt fails for a reason attributable to Router or to OpenAI, never to the caller's request. Do not read that retry as fal's alone — it walks the id's registered partners in a fixed order and takes the first whose provider gate, workspace partner-provider policy, eligibility valve and translation all succeed, so wavespeed or runware answers it whenever fal is ruled out. And it does not apply to an EDIT: a body carrying `image` selects the multipart edits operation, for which the automatic retry is disabled outright, so an edit that OpenAI fails comes back as OpenAI's own failure rather than being retried against a partner. (Naming a partner explicitly still serves an edit, wherever that partner's translator carries the media field itself.) Every one of those routes is the alt-provider dispatch in `server/implementation/router_run.go`, whose `routerAltTranslators` supplies the translator half against the matching `routeralt` leg that carries the alt id (`fal-gpt-image-2`, `wavespeed-gpt-image-2.5-flare`, `runware-gpt-image-2.5-sunburst` and so on — one leg per id-and-partner pair); the two halves are joined at init and a half-registration fails the build. None of the partners returns inline base64 on any of the alt-backed ids — each only returns hosted URLs, so an alt-provider-served response carries `data[].url` and never `data[].b64_json`, the opposite of the OpenAI-served shape above. A caller that requested `response_format: b64_json` on an alt-provider-served attempt is told so through `X-Comfy-Router-Dropped-Params` rather than being given inline bytes. Both of those statements describe the answer TRANSLATED BACK into this contract, which is what the default `strict_mode=false` returns. `strict_mode=true` runs no translation in either direction: the partner's own raw envelope comes back unchanged and carries no `X-Comfy-Router-Dropped-Params`, and Router returns that same raw envelope on the rarer path where a response cannot be translated back. Such a body is the PARTNER's contract, not this schema, and must not be parsed as this one — the call is billed either way. Their translators do not fabricate the OpenAI-specific fields they cannot produce: on an alt-provider-served response `usage`, `revised_prompt`, and all four resolved-parameter members below are entirely ABSENT, not merely empty. Branch on the keys a response actually carries rather than on an assumed producer; which partner served a given request on any of the alt-backed ids is a Comfy routing decision that can change without notice, and is not part of this contract.\nFor the single-producer ids, and for the alt-backed ids whenever OpenAI is the one that served it, the four resolved-parameter members (`background`, `output_format`, `quality`, `size`) report what OpenAI ACTUALLY RAN rather than echoing the request: `background`, `quality` and `size` each default to `auto` on the request side, so a caller who sends `auto` — or sends nothing — only learns the concrete background, tier and `\u003cwidth\u003ex\u003cheight\u003e` that were used by reading them here. `created` is the one member OpenAI declares required whenever OpenAI is the producer.","example":{"created":1767225600,"data":[{"b64_json":"PGJhc2U2ND4="}],"usage":{"input_tokens":12,"input_tokens_details":{"image_tokens":0,"text_tokens":12},"output_tokens":1056,"total_tokens":1068}}}}}}}}}},"components":{"schemas":{"OpenAIImageGenerationResponse":{"properties":{"data":{"items":{"properties":{"b64_json":{"description":"Base64 encoded image data","type":"string"},"revised_prompt":{"description":"Revised prompt","type":"string"},"url":{"description":"URL of the image","type":"string"}},"type":"object"},"type":"array"},"usage":{"properties":{"input_tokens":{"type":"integer"},"input_tokens_details":{"properties":{"image_tokens":{"type":"integer"},"text_tokens":{"type":"integer"}},"type":"object"},"output_tokens":{"type":"integer"},"output_tokens_details":{"properties":{"image_tokens":{"type":"integer"},"text_tokens":{"type":"integer"}},"type":"object"},"total_tokens":{"type":"integer"}},"type":"object"}},"type":"object"},"OpenAIImageRouterMedia":{"description":"One image, as either an https URL Router fetches on the caller's behalf or a `data:image/\u003cformat\u003e;base64,\u003cpayload\u003e` URI carrying the bytes inline. Accepted image types are png, webp and jpeg, and that set is ENFORCED at resolve time rather than left to the provider -- an svg, gif, avif or tiff is refused here, naming the image you sent, instead of becoming a partner error two hops from the cause.\nPrefer the URL form. A base64 payload inflates roughly 4/3 and the whole request body is capped at 100 MiB, so the inline form caps out near a 75 MB source image; a URL sidesteps that entirely. The per-image and per-request media ceilings stated at the end of this description are tighter than the body cap, so for an image of any real size they are what you meet first.\nTHE URL MUST RESOLVE WITHOUT YOUR CREDENTIALS. Router fetches it server-side and sends no caller credential with the request, so a presigned URL -- one whose signature is in the query string -- is the supported form. An authenticated endpoint such as `/api/assets/{id}/content` answers Router 401 rather than the image; request that URL yourself and pass the signed URL it redirects to.\nTHE FETCH IS CONFINED TO A NAMED SET OF HOSTS rather than the open internet, and the same list is re-applied to every redirect hop as well as to the URL you send -- so a URL on any other host, or one that redirects off the list, is refused before any provider is contacted. Today that list is Comfy's own asset delivery only: a signed `storage.googleapis.com` URL under a Comfy asset bucket, which is the form `/api/assets/{id}/content` redirects you to. A partner CDN or a plain https host is NOT on the list.\nTWO WIDENINGS ARE BEING ROLLED OUT AND ARE BOTH OFF BY DEFAULT TODAY. They ship behind ONE switch and are enabled together, so treat everything below as forthcoming rather than as something to build against yet -- ask before you rely on either.\nThe FIRST is the bucket `POST /customers/storage` signs your upload into. Until the rollout reaches your environment an upload URL from that endpoint is refused here, even though the bucket is Comfy's own and the host is the same one -- so \"upload to Comfy, then pass the URL\" does NOT work yet, and the form that does is the signed URL `/api/assets/{id}/content` redirects to.\nThe SECOND is ENABLED PER ACCOUNT rather than for everyone at once, because the bucket behind these hosts is yours rather than ours: even once the switch above is on, an account that has not had third-party inbound media enabled is refused, and told so in those words rather than told the host is unsupported. Ask your Comfy contact to enable it for your account. It additionally admits these third-party object-storage hosts, where the bucket is yours and Router makes no ownership claim on it: Cloudflare R2 (`\u003caccount\u003e.r2.cloudflarestorage.com`, `pub-\u003chash\u003e.r2.dev`), Amazon S3 (`s3.amazonaws.com` and `s3.\u003cregion\u003e.amazonaws.com`, in either the path-style or the `\u003cbucket\u003e.`-prefixed form), Azure Blob Storage (`\u003caccount\u003e.blob.core.windows.net`) and Alibaba Cloud OSS (`\u003cbucket\u003e.oss-\u003cregion\u003e.aliyuncs.com`, including the `oss-accelerate` endpoint). Those are object-storage endpoints specifically: a general-purpose endpoint on the same provider domain, such as an Alibaba Function Compute trigger, is not on the list and is refused.\nWHAT IS MATCHED IS THE HOSTNAME FORM, NOT THE PROVIDER -- so naming a provider above does NOT mean every URL that provider can issue is accepted, and three near-misses are worth stating outright because a caller can reasonably arrive with each of them. An R2 CUSTOM DOMAIN (`cdn.example.com`) is NOT accepted: Cloudflare documents `pub-\u003chash\u003e.r2.dev` as a rate-limited debug hostname and points production traffic at a custom domain instead, but a custom domain carries nothing in its hostname identifying it as R2, so Router cannot tell it from any other host and refuses it -- presign the `\u003caccount\u003e.r2.cloudflarestorage.com` endpoint, or use the `pub-\u003chash\u003e.r2.dev` form. S3's IPv6 dualstack endpoint (`s3.dualstack.\u003cregion\u003e.amazonaws.com`) is not accepted either; use the plain regional endpoint. Nor is Azure Blob's alternate DNS zone (`\u003caccount\u003e.z\u003cN\u003e.blob.storage.azure.net`); use `\u003caccount\u003e.blob.core.windows.net`. The refusal you get back names these same forms, so you do not have to come here to read them.\nThe `\u003caccount\u003e` and `pub-\u003chash\u003e` placeholders above show the form you will normally arrive with; they are not a depth restriction. Matching is on the object-storage domain suffix, so a deeper name under the same namespace (`a.b.pub-\u003chash\u003e.r2.dev`) is accepted as well. What is NOT widened by that is the namespace itself -- the suffix still has to be one of the ones listed.\nThe example below shows the shape only: a real value also carries the signing query parameters, which are deliberately omitted here because a published example lives in the spec forever and a real signature is both a leaked credential and a link that stops working.\nEach image is capped at 25 MiB and one request's images at 64 MiB in total.","example":"https://storage.googleapis.com/comfy-cloud-assets/0199b3f4-1d2e-7a3b-8c4d-5e6f70819234.png","type":"string"},"OpenAIImageRouterSource":{"description":"The source image(s) to edit. PRESENCE OF THIS FIELD SELECTS THE EDIT OPERATION -- omit it entirely for text-to-image generation.\nThe model's published example is the GENERATION call, which omits this field. Adding `image` is the whole of the difference between the two modes -- same endpoint, same model id -- except that an edit prompt describes the CHANGE you want rather than the scene, so the edit body below reads differently from the generation example even though only this field is structurally new.\nIt is copyable exactly as printed: `{\"prompt\": \"give the rocketship rainbow coloring\", \"image\": [\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==\"], \"size\": \"1024x1024\"}` -- the payload is a complete 1x1 PNG, not an abbreviation, and this body is pinned as an accepted case in Router's own request-validation tests. Swap in your own image's bytes.\nAccepts one image or an array of them; gpt-image-1 and later take up to 16, which Router enforces from this schema before dispatching.","oneOf":[{"$ref":"#/components/schemas/OpenAIImageRouterMedia"},{"items":{"$ref":"#/components/schemas/OpenAIImageRouterMedia"},"maxItems":16,"minItems":1,"type":"array"}]}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"openai/gpt-image-2.5-flare","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image"],"x-comfy-router-output-modalities":["image"],"x-comfy-router-alt-providers":[{"provider":"fal","model_id":"fal/fal-gpt-image-2.5-flare"},{"provider":"runware","model_id":"runware/runware-gpt-image-2.5-flare"},{"provider":"wavespeed","model_id":"wavespeed/wavespeed-gpt-image-2.5-flare"}]}
+{"openapi":"3.0.2","info":{"title":"openai/gpt-image-2.5-flare","description":"The request body Comfy Router accepts for the model \"openai/gpt-image-2.5-flare\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"1786a32eea22"},"paths":{"/v2/models/openai/gpt-image-2.5-flare":{"post":{"operationId":"runRouterModel","summary":"Run openai/gpt-image-2.5-flare synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"description":"The request body for a gpt-image model. ONE model id reaches TWO upstream OpenAI operations and this body is what selects between them: OMIT `image` for text-to-image generation, INCLUDE it to edit the image you send. The example below is the generation call; adding `image` to it is the whole of the structural difference, and `image`'s own description carries a copyable edit body.","example":{"prompt":"a rocketship on a launchpad","quality":"low","size":"1024x1024"},"properties":{"background":{"description":"Background transparency. `auto` lets the model choose.","enum":["transparent","opaque","auto"],"example":"opaque","type":"string"},"image":{"$ref":"#/components/schemas/OpenAIImageRouterSource"},"mask":{"allOf":[{"$ref":"#/components/schemas/OpenAIImageRouterMedia"}],"description":"Optional. Marks the region to edit: its fully transparent areas are what the model replaces. Must be a PNG with an alpha channel, under 4 MiB -- the bound routerOpenAIMaskMaxBytes ENFORCES on the resolved bytes, so an oversize mask is refused here by its size rather than relayed from the provider -- and matching the dimensions of the first `image`. Same accepted forms as `image`. Ignored -- and meaningless -- when `image` is absent, because there is nothing to mask: Router strips it from the request rather than forwarding it to the text-to-image operation, which would reject it as an unknown parameter."},"model":{"description":"The gpt-image model to run. Router splices the addressed model id in, so a caller addressing /v2/models/openai/gpt-image-2 does not send this.","example":"gpt-image-2.5-flare","type":"string"},"moderation":{"description":"Content moderation setting","enum":["low","auto"],"example":"auto","type":"string"},"n":{"description":"The number of images to generate (1-10).","example":1,"maximum":10,"minimum":1,"type":"integer"},"output_compression":{"description":"Compression level for JPEG or WebP (0-100)","example":100,"maximum":100,"minimum":0,"type":"integer"},"output_format":{"description":"Format of the output image","enum":["png","webp","jpeg"],"example":"png","type":"string"},"partial_images":{"description":"Meaningful only alongside a live stream, which this surface cannot serve -- `stream` is settled to `false` even when you send it. On a generation, any value other than `0` or `null` is refused with a 400, whether or not the request also sends `stream: true`; on a request carrying `image` the field is dropped rather than forwarded. Omit it.","example":0,"maximum":3,"minimum":0,"nullable":true,"type":"integer"},"prompt":{"description":"A text description of the image to generate, or of the edit to make to `image`.","example":"Give the rocketship rainbow coloring","type":"string"},"quality":{"description":"The quality of the generated or edited image. `xhigh` and `max` are the two tiers gpt-image-2.5-flare and gpt-image-2.5-sunburst were added for; `standard` and `hd` are dall-e-era spellings no admitted model reads.","enum":["auto","low","medium","high","xhigh","max","standard","hd"],"example":"high","type":"string"},"size":{"description":"Size of the image (e.g., 1024x1024, 1536x1024, auto)","example":"1024x1536","type":"string"},"stream":{"description":"Declared so a caller who sends it is not refused, but INERT on this surface: Router SETTLES it to `false` before dispatch, because it captures the provider response rather than relaying a `text/event-stream` (a request carrying `image` drops the field instead, since the edit is re-encoded without it). No Comfy surface serves gpt-image partial-image streaming: the v1 `POST /proxy/openai/images/generations` ingress refuses `stream: true` with a 400 and never forwards the request upstream, so omit the field and read the completed response as one JSON body. Because the stream never opens, OpenAI's restriction on combining `stream: true` with `n` greater than 1 does not apply here; `n` is forwarded as sent.","example":false,"nullable":true,"type":"boolean"},"user":{"description":"A unique identifier for end-user monitoring","example":"user-1234","type":"string"}},"required":["prompt"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/OpenAIImageGenerationResponse"},{"properties":{"background":{"description":"Whether the generated image's background is `opaque` or `transparent`, as OpenAI resolved it for this generation. The request parameter defaults to `auto`, so this is where a caller who did not pin it learns which one was produced.","type":"string"},"created":{"description":"Unix timestamp, in seconds, of when the generation completed. It is the one member OpenAI declares REQUIRED on an image-generation response, so it is present whenever OpenAI is the producer; a fal-, pika-, wavespeed- or runware-served response on any of the alt-backed ids omits it entirely, like the other four resolved-parameter members. Declared `int64` because a present-day epoch value is close enough to 2^31 that an unformatted `integer` generates a 32-bit field in many SDK generators.","format":"int64","type":"integer"},"output_format":{"description":"The encoding of the bytes in `data[].b64_json` — `png`, `webp` or `jpeg`. It is the format OpenAI actually encoded, which is the request's `output_format` when one was sent and `png` when none was, so a decoder can key off it rather than re-deriving the format from the request.","type":"string"},"quality":{"description":"The quality tier the generation actually ran at — OpenAI reports one of `low`, `medium`, `high`, `xhigh` or `max`. It is the RESOLVED tier, not an echo of the request, whose own `quality` defaults to `auto`.","type":"string"},"size":{"description":"The pixel dimensions the generation actually ran at, as `\u003cwidth\u003ex\u003cheight\u003e`. It is the RESOLVED size: the request's `size` defaults to `auto` and explicitly admits `auto`, so this is the only place the dimensions actually used are reported.","type":"string"}},"type":"object"}],"description":"Comfy Router output schema for the OpenAI gpt-image models: OpenAI's own image-generation response, forwarded unchanged. The operation is direct-return (`routerresult/classification.go` classifies `openai /images/generations` as ReturnModeDirect with no poll route), so the body a caller receives is this finished document from the one call. That row also raises the read limit to `BinaryResultMaxBytes`, because the images can arrive INLINE and `n` admits up to 10 of them at a `size` and `quality` the operation does not bound.\nOn the ids with a single producer — `openai/gpt-image-1` and `openai/gpt-image-1.5` — and on any of the alt-backed ids whenever OpenAI itself serves it, the generated images are in `data`, always INLINE at `data[].b64_json`: OpenAI has no `url` response format at all on this surface, so `url` stays unpopulated there even though the shared response component declares it. That also means the bytes are durable in the only sense that matters here — they are in the body, not behind a link that expires. `revised_prompt` is the prompt OpenAI rewrote rather than an asset, and it is present on a response whose image never arrived, so it must not be read as one. `usage` is OpenAI's own token accounting — OpenAI's numbers, not the Comfy charge.\n`openai/gpt-image-2` (`fal`, `pika`, `runware`, `wavespeed`), `openai/gpt-image-2.5-flare` (`fal`, `runware`, `wavespeed`) and `openai/gpt-image-2.5-sunburst` (`fal`, `runware`, `wavespeed`) have ALTERNATE producers: an eligible request on any of them may instead be served by fal, by wavespeed or by runware, and on `openai/gpt-image-2` also by pika. Each partner is reachable explicitly, via `model_provider=fal`, `model_provider=wavespeed`, `model_provider=runware` or (on `openai/gpt-image-2`) `model_provider=pika`; the pika leg is text-to-image only, so it never serves an edit. One may also serve a request that named nobody: `fallback_provider` (default ON) retries a GENERATION once against a registered alternate when the OpenAI attempt fails for a reason attributable to Router or to OpenAI, never to the caller's request. Do not read that retry as fal's alone — it walks the id's registered partners in a fixed order and takes the first whose provider gate, workspace partner-provider policy, eligibility valve and translation all succeed. That order is alphabetical by partner (fal, then pika on `openai/gpt-image-2`, then runware, then wavespeed), so the first of them the caller's gates admit answers it whenever fal is ruled out. And it does not apply to an EDIT: a body carrying `image` selects the multipart edits operation, for which the automatic retry is disabled outright, so an edit that OpenAI fails comes back as OpenAI's own failure rather than being retried against a partner. (Naming a partner explicitly still serves an edit, wherever that partner's translator carries the media field itself.) Every one of those routes is the alt-provider dispatch in `server/implementation/router_run.go`, whose `routerAltTranslators` supplies the translator half against the matching `routeralt` leg that carries the alt id (`fal-gpt-image-2`, `wavespeed-gpt-image-2.5-flare`, `runware-gpt-image-2.5-sunburst` and so on — one leg per id-and-partner pair); the two halves are joined at init and a half-registration fails the build. None of the partners returns inline base64 on any of the alt-backed ids — each only returns hosted URLs, so an alt-provider-served response carries `data[].url` and never `data[].b64_json`, the opposite of the OpenAI-served shape above. A caller that requested `response_format: b64_json` on an alt-provider-served attempt is told so through `X-Comfy-Router-Dropped-Params` rather than being given inline bytes. Both of those statements describe the answer TRANSLATED BACK into this contract, which is what the default `strict_mode=false` returns. `strict_mode=true` runs no translation in either direction: the partner's own raw envelope comes back unchanged (save pika's, from which Pika's own `billing` record is still removed) and carries no `X-Comfy-Router-Dropped-Params`, and Router returns that same raw envelope on the rarer path where a response cannot be translated back. Such a body is the PARTNER's contract, not this schema, and must not be parsed as this one — the call is billed either way. Their translators do not fabricate the OpenAI-specific fields they cannot produce: on an alt-provider-served response `usage`, `revised_prompt`, and all four resolved-parameter members below are entirely ABSENT, not merely empty. Branch on the keys a response actually carries rather than on an assumed producer; which partner served a given request on any of the alt-backed ids is a Comfy routing decision that can change without notice, and is not part of this contract.\nFor the single-producer ids, and for the alt-backed ids whenever OpenAI is the one that served it, the four resolved-parameter members (`background`, `output_format`, `quality`, `size`) report what OpenAI ACTUALLY RAN rather than echoing the request: `background`, `quality` and `size` each default to `auto` on the request side, so a caller who sends `auto` — or sends nothing — only learns the concrete background, tier and `\u003cwidth\u003ex\u003cheight\u003e` that were used by reading them here. `created` is the one member OpenAI declares required whenever OpenAI is the producer.","example":{"created":1767225600,"data":[{"b64_json":"PGJhc2U2ND4="}],"usage":{"input_tokens":12,"input_tokens_details":{"image_tokens":0,"text_tokens":12},"output_tokens":1056,"total_tokens":1068}}}}}}}}}},"components":{"schemas":{"OpenAIImageGenerationResponse":{"properties":{"data":{"items":{"properties":{"b64_json":{"description":"Base64 encoded image data","type":"string"},"revised_prompt":{"description":"Revised prompt","type":"string"},"url":{"description":"URL of the image","type":"string"}},"type":"object"},"type":"array"},"usage":{"properties":{"input_tokens":{"type":"integer"},"input_tokens_details":{"properties":{"image_tokens":{"type":"integer"},"text_tokens":{"type":"integer"}},"type":"object"},"output_tokens":{"type":"integer"},"output_tokens_details":{"properties":{"image_tokens":{"type":"integer"},"text_tokens":{"type":"integer"}},"type":"object"},"total_tokens":{"type":"integer"}},"type":"object"}},"type":"object"},"OpenAIImageRouterMedia":{"description":"One image, as either an https URL Router fetches on the caller's behalf or a `data:image/\u003cformat\u003e;base64,\u003cpayload\u003e` URI carrying the bytes inline. Accepted image types are png, webp and jpeg, and that set is ENFORCED at resolve time rather than left to the provider -- an svg, gif, avif or tiff is refused here, naming the image you sent, instead of becoming a partner error two hops from the cause.\nPrefer the URL form. A base64 payload inflates roughly 4/3 and the whole request body is capped at 100 MiB, so the inline form caps out near a 75 MB source image; a URL sidesteps that entirely. The per-image and per-request media ceilings stated at the end of this description are tighter than the body cap, so for an image of any real size they are what you meet first.\nTHE URL MUST RESOLVE WITHOUT YOUR CREDENTIALS. Router fetches it server-side and sends no caller credential with the request, so a presigned URL -- one whose signature is in the query string -- is the supported form. An authenticated endpoint such as `/api/assets/{id}/content` answers Router 401 rather than the image; request that URL yourself and pass the signed URL it redirects to.\nTHE FETCH IS CONFINED TO A NAMED SET OF HOSTS rather than the open internet, and the same list is re-applied to every redirect hop as well as to the URL you send -- so a URL on any other host, or one that redirects off the list, is refused before any provider is contacted. Today that list is Comfy's own asset delivery only: a signed `storage.googleapis.com` URL under a Comfy asset bucket, which is the form `/api/assets/{id}/content` redirects you to. A partner CDN or a plain https host is NOT on the list.\nTWO WIDENINGS ARE BEING ROLLED OUT AND ARE BOTH OFF BY DEFAULT TODAY. They ship behind ONE switch and are enabled together, so treat everything below as forthcoming rather than as something to build against yet -- ask before you rely on either.\nThe FIRST is the bucket `POST /customers/storage` signs your upload into. Until the rollout reaches your environment an upload URL from that endpoint is refused here, even though the bucket is Comfy's own and the host is the same one -- so \"upload to Comfy, then pass the URL\" does NOT work yet, and the form that does is the signed URL `/api/assets/{id}/content` redirects to.\nThe SECOND is ENABLED PER ACCOUNT rather than for everyone at once, because the bucket behind these hosts is yours rather than ours: even once the switch above is on, an account that has not had third-party inbound media enabled is refused, and told so in those words rather than told the host is unsupported. Ask your Comfy contact to enable it for your account. It additionally admits these third-party object-storage hosts, where the bucket is yours and Router makes no ownership claim on it: Cloudflare R2 (`\u003caccount\u003e.r2.cloudflarestorage.com`, `pub-\u003chash\u003e.r2.dev`), Amazon S3 (`s3.amazonaws.com` and `s3.\u003cregion\u003e.amazonaws.com`, in either the path-style or the `\u003cbucket\u003e.`-prefixed form), Azure Blob Storage (`\u003caccount\u003e.blob.core.windows.net`) and Alibaba Cloud OSS (`\u003cbucket\u003e.oss-\u003cregion\u003e.aliyuncs.com`, including the `oss-accelerate` endpoint). Those are object-storage endpoints specifically: a general-purpose endpoint on the same provider domain, such as an Alibaba Function Compute trigger, is not on the list and is refused.\nWHAT IS MATCHED IS THE HOSTNAME FORM, NOT THE PROVIDER -- so naming a provider above does NOT mean every URL that provider can issue is accepted, and three near-misses are worth stating outright because a caller can reasonably arrive with each of them. An R2 CUSTOM DOMAIN (`cdn.example.com`) is NOT accepted: Cloudflare documents `pub-\u003chash\u003e.r2.dev` as a rate-limited debug hostname and points production traffic at a custom domain instead, but a custom domain carries nothing in its hostname identifying it as R2, so Router cannot tell it from any other host and refuses it -- presign the `\u003caccount\u003e.r2.cloudflarestorage.com` endpoint, or use the `pub-\u003chash\u003e.r2.dev` form. S3's IPv6 dualstack endpoint (`s3.dualstack.\u003cregion\u003e.amazonaws.com`) is not accepted either; use the plain regional endpoint. Nor is Azure Blob's alternate DNS zone (`\u003caccount\u003e.z\u003cN\u003e.blob.storage.azure.net`); use `\u003caccount\u003e.blob.core.windows.net`. The refusal you get back names these same forms, so you do not have to come here to read them.\nThe `\u003caccount\u003e` and `pub-\u003chash\u003e` placeholders above show the form you will normally arrive with; they are not a depth restriction. Matching is on the object-storage domain suffix, so a deeper name under the same namespace (`a.b.pub-\u003chash\u003e.r2.dev`) is accepted as well. What is NOT widened by that is the namespace itself -- the suffix still has to be one of the ones listed.\nThe example below shows the shape only: a real value also carries the signing query parameters, which are deliberately omitted here because a published example lives in the spec forever and a real signature is both a leaked credential and a link that stops working.\nEach image is capped at 25 MiB and one request's images at 64 MiB in total.","example":"https://storage.googleapis.com/comfy-cloud-assets/0199b3f4-1d2e-7a3b-8c4d-5e6f70819234.png","type":"string"},"OpenAIImageRouterSource":{"description":"The source image(s) to edit. PRESENCE OF THIS FIELD SELECTS THE EDIT OPERATION -- omit it entirely for text-to-image generation.\nThe model's published example is the GENERATION call, which omits this field. Adding `image` is the whole of the difference between the two modes -- same endpoint, same model id -- except that an edit prompt describes the CHANGE you want rather than the scene, so the edit body below reads differently from the generation example even though only this field is structurally new.\nIt is copyable exactly as printed: `{\"prompt\": \"give the rocketship rainbow coloring\", \"image\": [\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==\"], \"size\": \"1024x1024\"}` -- the payload is a complete 1x1 PNG, not an abbreviation, and this body is pinned as an accepted case in Router's own request-validation tests. Swap in your own image's bytes.\nAccepts one image or an array of them; gpt-image-1 and later take up to 16, which Router enforces from this schema before dispatching.","oneOf":[{"$ref":"#/components/schemas/OpenAIImageRouterMedia"},{"items":{"$ref":"#/components/schemas/OpenAIImageRouterMedia"},"maxItems":16,"minItems":1,"type":"array"}]}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"openai/gpt-image-2.5-flare","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image"],"x-comfy-router-output-modalities":["image"],"x-comfy-router-alt-providers":[{"provider":"fal","model_id":"fal/fal-gpt-image-2.5-flare"},{"provider":"runware","model_id":"runware/runware-gpt-image-2.5-flare"},{"provider":"wavespeed","model_id":"wavespeed/wavespeed-gpt-image-2.5-flare"}]}
diff --git a/router-schemas/openai/gpt-image-2.5-sunburst.json b/router-schemas/openai/gpt-image-2.5-sunburst.json
index 855d950487..eab02496e2 100644
--- a/router-schemas/openai/gpt-image-2.5-sunburst.json
+++ b/router-schemas/openai/gpt-image-2.5-sunburst.json
@@ -1 +1 @@
-{"openapi":"3.0.2","info":{"title":"openai/gpt-image-2.5-sunburst","description":"The request body Comfy Router accepts for the model \"openai/gpt-image-2.5-sunburst\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"f44c74e2ce6b"},"paths":{"/v2/models/openai/gpt-image-2.5-sunburst":{"post":{"operationId":"runRouterModel","summary":"Run openai/gpt-image-2.5-sunburst synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"description":"The request body for a gpt-image model. ONE model id reaches TWO upstream OpenAI operations and this body is what selects between them: OMIT `image` for text-to-image generation, INCLUDE it to edit the image you send. The example below is the generation call; adding `image` to it is the whole of the structural difference, and `image`'s own description carries a copyable edit body.","example":{"prompt":"a rocketship on a launchpad","quality":"low","size":"1024x1024"},"properties":{"background":{"description":"Background transparency. `auto` lets the model choose.","enum":["transparent","opaque","auto"],"example":"opaque","type":"string"},"image":{"$ref":"#/components/schemas/OpenAIImageRouterSource"},"mask":{"allOf":[{"$ref":"#/components/schemas/OpenAIImageRouterMedia"}],"description":"Optional. Marks the region to edit: its fully transparent areas are what the model replaces. Must be a PNG with an alpha channel, under 4 MiB -- the bound routerOpenAIMaskMaxBytes ENFORCES on the resolved bytes, so an oversize mask is refused here by its size rather than relayed from the provider -- and matching the dimensions of the first `image`. Same accepted forms as `image`. Ignored -- and meaningless -- when `image` is absent, because there is nothing to mask: Router strips it from the request rather than forwarding it to the text-to-image operation, which would reject it as an unknown parameter."},"model":{"description":"The gpt-image model to run. Router splices the addressed model id in, so a caller addressing /v2/models/openai/gpt-image-2 does not send this.","example":"gpt-image-2.5-flare","type":"string"},"moderation":{"description":"Content moderation setting","enum":["low","auto"],"example":"auto","type":"string"},"n":{"description":"The number of images to generate (1-10).","example":1,"maximum":10,"minimum":1,"type":"integer"},"output_compression":{"description":"Compression level for JPEG or WebP (0-100)","example":100,"maximum":100,"minimum":0,"type":"integer"},"output_format":{"description":"Format of the output image","enum":["png","webp","jpeg"],"example":"png","type":"string"},"partial_images":{"description":"Meaningful only alongside a live stream, which this surface cannot serve -- `stream` is settled to `false` even when you send it. On a generation, any value other than `0` or `null` is refused with a 400, whether or not the request also sends `stream: true`; on a request carrying `image` the field is dropped rather than forwarded. Omit it.","example":0,"maximum":3,"minimum":0,"nullable":true,"type":"integer"},"prompt":{"description":"A text description of the image to generate, or of the edit to make to `image`.","example":"Give the rocketship rainbow coloring","type":"string"},"quality":{"description":"The quality of the generated or edited image. `xhigh` and `max` are the two tiers gpt-image-2.5-flare and gpt-image-2.5-sunburst were added for; `standard` and `hd` are dall-e-era spellings no admitted model reads.","enum":["auto","low","medium","high","xhigh","max","standard","hd"],"example":"high","type":"string"},"size":{"description":"Size of the image (e.g., 1024x1024, 1536x1024, auto)","example":"1024x1536","type":"string"},"stream":{"description":"Declared so a caller who sends it is not refused, but INERT on this surface: Router SETTLES it to `false` before dispatch, because it captures the provider response rather than relaying a `text/event-stream` (a request carrying `image` drops the field instead, since the edit is re-encoded without it). No Comfy surface serves gpt-image partial-image streaming: the v1 `POST /proxy/openai/images/generations` ingress refuses `stream: true` with a 400 and never forwards the request upstream, so omit the field and read the completed response as one JSON body. Because the stream never opens, OpenAI's restriction on combining `stream: true` with `n` greater than 1 does not apply here; `n` is forwarded as sent.","example":false,"nullable":true,"type":"boolean"},"user":{"description":"A unique identifier for end-user monitoring","example":"user-1234","type":"string"}},"required":["prompt"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/OpenAIImageGenerationResponse"},{"properties":{"background":{"description":"Whether the generated image's background is `opaque` or `transparent`, as OpenAI resolved it for this generation. The request parameter defaults to `auto`, so this is where a caller who did not pin it learns which one was produced.","type":"string"},"created":{"description":"Unix timestamp, in seconds, of when the generation completed. It is the one member OpenAI declares REQUIRED on an image-generation response, so it is present whenever OpenAI is the producer; a fal-, wavespeed- or runware-served response on any of the alt-backed ids omits it entirely, like the other four resolved-parameter members. Declared `int64` because a present-day epoch value is close enough to 2^31 that an unformatted `integer` generates a 32-bit field in many SDK generators.","format":"int64","type":"integer"},"output_format":{"description":"The encoding of the bytes in `data[].b64_json` — `png`, `webp` or `jpeg`. It is the format OpenAI actually encoded, which is the request's `output_format` when one was sent and `png` when none was, so a decoder can key off it rather than re-deriving the format from the request.","type":"string"},"quality":{"description":"The quality tier the generation actually ran at — OpenAI reports one of `low`, `medium`, `high`, `xhigh` or `max`. It is the RESOLVED tier, not an echo of the request, whose own `quality` defaults to `auto`.","type":"string"},"size":{"description":"The pixel dimensions the generation actually ran at, as `\u003cwidth\u003ex\u003cheight\u003e`. It is the RESOLVED size: the request's `size` defaults to `auto` and explicitly admits `auto`, so this is the only place the dimensions actually used are reported.","type":"string"}},"type":"object"}],"description":"Comfy Router output schema for the OpenAI gpt-image models: OpenAI's own image-generation response, forwarded unchanged. The operation is direct-return (`routerresult/classification.go` classifies `openai /images/generations` as ReturnModeDirect with no poll route), so the body a caller receives is this finished document from the one call. That row also raises the read limit to `BinaryResultMaxBytes`, because the images can arrive INLINE and `n` admits up to 10 of them at a `size` and `quality` the operation does not bound.\nOn the ids with a single producer — `openai/gpt-image-1` and `openai/gpt-image-1.5` — and on any of the alt-backed ids whenever OpenAI itself serves it, the generated images are in `data`, always INLINE at `data[].b64_json`: OpenAI has no `url` response format at all on this surface, so `url` stays unpopulated there even though the shared response component declares it. That also means the bytes are durable in the only sense that matters here — they are in the body, not behind a link that expires. `revised_prompt` is the prompt OpenAI rewrote rather than an asset, and it is present on a response whose image never arrived, so it must not be read as one. `usage` is OpenAI's own token accounting — OpenAI's numbers, not the Comfy charge.\n`openai/gpt-image-2` (`fal`, `runware`, `wavespeed`), `openai/gpt-image-2.5-flare` (`fal`, `runware`, `wavespeed`) and `openai/gpt-image-2.5-sunburst` (`fal`, `runware`, `wavespeed`) have ALTERNATE producers: an eligible request on any of them may instead be served by fal, by wavespeed or by runware. Each partner is reachable explicitly, via `model_provider=fal`, `model_provider=wavespeed` or `model_provider=runware`. One may also serve a request that named nobody: `fallback_provider` (default ON) retries a GENERATION once against a registered alternate when the OpenAI attempt fails for a reason attributable to Router or to OpenAI, never to the caller's request. Do not read that retry as fal's alone — it walks the id's registered partners in a fixed order and takes the first whose provider gate, workspace partner-provider policy, eligibility valve and translation all succeed, so wavespeed or runware answers it whenever fal is ruled out. And it does not apply to an EDIT: a body carrying `image` selects the multipart edits operation, for which the automatic retry is disabled outright, so an edit that OpenAI fails comes back as OpenAI's own failure rather than being retried against a partner. (Naming a partner explicitly still serves an edit, wherever that partner's translator carries the media field itself.) Every one of those routes is the alt-provider dispatch in `server/implementation/router_run.go`, whose `routerAltTranslators` supplies the translator half against the matching `routeralt` leg that carries the alt id (`fal-gpt-image-2`, `wavespeed-gpt-image-2.5-flare`, `runware-gpt-image-2.5-sunburst` and so on — one leg per id-and-partner pair); the two halves are joined at init and a half-registration fails the build. None of the partners returns inline base64 on any of the alt-backed ids — each only returns hosted URLs, so an alt-provider-served response carries `data[].url` and never `data[].b64_json`, the opposite of the OpenAI-served shape above. A caller that requested `response_format: b64_json` on an alt-provider-served attempt is told so through `X-Comfy-Router-Dropped-Params` rather than being given inline bytes. Both of those statements describe the answer TRANSLATED BACK into this contract, which is what the default `strict_mode=false` returns. `strict_mode=true` runs no translation in either direction: the partner's own raw envelope comes back unchanged and carries no `X-Comfy-Router-Dropped-Params`, and Router returns that same raw envelope on the rarer path where a response cannot be translated back. Such a body is the PARTNER's contract, not this schema, and must not be parsed as this one — the call is billed either way. Their translators do not fabricate the OpenAI-specific fields they cannot produce: on an alt-provider-served response `usage`, `revised_prompt`, and all four resolved-parameter members below are entirely ABSENT, not merely empty. Branch on the keys a response actually carries rather than on an assumed producer; which partner served a given request on any of the alt-backed ids is a Comfy routing decision that can change without notice, and is not part of this contract.\nFor the single-producer ids, and for the alt-backed ids whenever OpenAI is the one that served it, the four resolved-parameter members (`background`, `output_format`, `quality`, `size`) report what OpenAI ACTUALLY RAN rather than echoing the request: `background`, `quality` and `size` each default to `auto` on the request side, so a caller who sends `auto` — or sends nothing — only learns the concrete background, tier and `\u003cwidth\u003ex\u003cheight\u003e` that were used by reading them here. `created` is the one member OpenAI declares required whenever OpenAI is the producer.","example":{"created":1767225600,"data":[{"b64_json":"PGJhc2U2ND4="}],"usage":{"input_tokens":12,"input_tokens_details":{"image_tokens":0,"text_tokens":12},"output_tokens":1056,"total_tokens":1068}}}}}}}}}},"components":{"schemas":{"OpenAIImageGenerationResponse":{"properties":{"data":{"items":{"properties":{"b64_json":{"description":"Base64 encoded image data","type":"string"},"revised_prompt":{"description":"Revised prompt","type":"string"},"url":{"description":"URL of the image","type":"string"}},"type":"object"},"type":"array"},"usage":{"properties":{"input_tokens":{"type":"integer"},"input_tokens_details":{"properties":{"image_tokens":{"type":"integer"},"text_tokens":{"type":"integer"}},"type":"object"},"output_tokens":{"type":"integer"},"output_tokens_details":{"properties":{"image_tokens":{"type":"integer"},"text_tokens":{"type":"integer"}},"type":"object"},"total_tokens":{"type":"integer"}},"type":"object"}},"type":"object"},"OpenAIImageRouterMedia":{"description":"One image, as either an https URL Router fetches on the caller's behalf or a `data:image/\u003cformat\u003e;base64,\u003cpayload\u003e` URI carrying the bytes inline. Accepted image types are png, webp and jpeg, and that set is ENFORCED at resolve time rather than left to the provider -- an svg, gif, avif or tiff is refused here, naming the image you sent, instead of becoming a partner error two hops from the cause.\nPrefer the URL form. A base64 payload inflates roughly 4/3 and the whole request body is capped at 100 MiB, so the inline form caps out near a 75 MB source image; a URL sidesteps that entirely. The per-image and per-request media ceilings stated at the end of this description are tighter than the body cap, so for an image of any real size they are what you meet first.\nTHE URL MUST RESOLVE WITHOUT YOUR CREDENTIALS. Router fetches it server-side and sends no caller credential with the request, so a presigned URL -- one whose signature is in the query string -- is the supported form. An authenticated endpoint such as `/api/assets/{id}/content` answers Router 401 rather than the image; request that URL yourself and pass the signed URL it redirects to.\nTHE FETCH IS CONFINED TO A NAMED SET OF HOSTS rather than the open internet, and the same list is re-applied to every redirect hop as well as to the URL you send -- so a URL on any other host, or one that redirects off the list, is refused before any provider is contacted. Today that list is Comfy's own asset delivery only: a signed `storage.googleapis.com` URL under a Comfy asset bucket, which is the form `/api/assets/{id}/content` redirects you to. A partner CDN or a plain https host is NOT on the list.\nTWO WIDENINGS ARE BEING ROLLED OUT AND ARE BOTH OFF BY DEFAULT TODAY. They ship behind ONE switch and are enabled together, so treat everything below as forthcoming rather than as something to build against yet -- ask before you rely on either.\nThe FIRST is the bucket `POST /customers/storage` signs your upload into. Until the rollout reaches your environment an upload URL from that endpoint is refused here, even though the bucket is Comfy's own and the host is the same one -- so \"upload to Comfy, then pass the URL\" does NOT work yet, and the form that does is the signed URL `/api/assets/{id}/content` redirects to.\nThe SECOND is ENABLED PER ACCOUNT rather than for everyone at once, because the bucket behind these hosts is yours rather than ours: even once the switch above is on, an account that has not had third-party inbound media enabled is refused, and told so in those words rather than told the host is unsupported. Ask your Comfy contact to enable it for your account. It additionally admits these third-party object-storage hosts, where the bucket is yours and Router makes no ownership claim on it: Cloudflare R2 (`\u003caccount\u003e.r2.cloudflarestorage.com`, `pub-\u003chash\u003e.r2.dev`), Amazon S3 (`s3.amazonaws.com` and `s3.\u003cregion\u003e.amazonaws.com`, in either the path-style or the `\u003cbucket\u003e.`-prefixed form), Azure Blob Storage (`\u003caccount\u003e.blob.core.windows.net`) and Alibaba Cloud OSS (`\u003cbucket\u003e.oss-\u003cregion\u003e.aliyuncs.com`, including the `oss-accelerate` endpoint). Those are object-storage endpoints specifically: a general-purpose endpoint on the same provider domain, such as an Alibaba Function Compute trigger, is not on the list and is refused.\nWHAT IS MATCHED IS THE HOSTNAME FORM, NOT THE PROVIDER -- so naming a provider above does NOT mean every URL that provider can issue is accepted, and three near-misses are worth stating outright because a caller can reasonably arrive with each of them. An R2 CUSTOM DOMAIN (`cdn.example.com`) is NOT accepted: Cloudflare documents `pub-\u003chash\u003e.r2.dev` as a rate-limited debug hostname and points production traffic at a custom domain instead, but a custom domain carries nothing in its hostname identifying it as R2, so Router cannot tell it from any other host and refuses it -- presign the `\u003caccount\u003e.r2.cloudflarestorage.com` endpoint, or use the `pub-\u003chash\u003e.r2.dev` form. S3's IPv6 dualstack endpoint (`s3.dualstack.\u003cregion\u003e.amazonaws.com`) is not accepted either; use the plain regional endpoint. Nor is Azure Blob's alternate DNS zone (`\u003caccount\u003e.z\u003cN\u003e.blob.storage.azure.net`); use `\u003caccount\u003e.blob.core.windows.net`. The refusal you get back names these same forms, so you do not have to come here to read them.\nThe `\u003caccount\u003e` and `pub-\u003chash\u003e` placeholders above show the form you will normally arrive with; they are not a depth restriction. Matching is on the object-storage domain suffix, so a deeper name under the same namespace (`a.b.pub-\u003chash\u003e.r2.dev`) is accepted as well. What is NOT widened by that is the namespace itself -- the suffix still has to be one of the ones listed.\nThe example below shows the shape only: a real value also carries the signing query parameters, which are deliberately omitted here because a published example lives in the spec forever and a real signature is both a leaked credential and a link that stops working.\nEach image is capped at 25 MiB and one request's images at 64 MiB in total.","example":"https://storage.googleapis.com/comfy-cloud-assets/0199b3f4-1d2e-7a3b-8c4d-5e6f70819234.png","type":"string"},"OpenAIImageRouterSource":{"description":"The source image(s) to edit. PRESENCE OF THIS FIELD SELECTS THE EDIT OPERATION -- omit it entirely for text-to-image generation.\nThe model's published example is the GENERATION call, which omits this field. Adding `image` is the whole of the difference between the two modes -- same endpoint, same model id -- except that an edit prompt describes the CHANGE you want rather than the scene, so the edit body below reads differently from the generation example even though only this field is structurally new.\nIt is copyable exactly as printed: `{\"prompt\": \"give the rocketship rainbow coloring\", \"image\": [\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==\"], \"size\": \"1024x1024\"}` -- the payload is a complete 1x1 PNG, not an abbreviation, and this body is pinned as an accepted case in Router's own request-validation tests. Swap in your own image's bytes.\nAccepts one image or an array of them; gpt-image-1 and later take up to 16, which Router enforces from this schema before dispatching.","oneOf":[{"$ref":"#/components/schemas/OpenAIImageRouterMedia"},{"items":{"$ref":"#/components/schemas/OpenAIImageRouterMedia"},"maxItems":16,"minItems":1,"type":"array"}]}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"openai/gpt-image-2.5-sunburst","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image"],"x-comfy-router-output-modalities":["image"],"x-comfy-router-alt-providers":[{"provider":"fal","model_id":"fal/fal-gpt-image-2.5-sunburst"},{"provider":"runware","model_id":"runware/runware-gpt-image-2.5-sunburst"},{"provider":"wavespeed","model_id":"wavespeed/wavespeed-gpt-image-2.5-sunburst"}]}
+{"openapi":"3.0.2","info":{"title":"openai/gpt-image-2.5-sunburst","description":"The request body Comfy Router accepts for the model \"openai/gpt-image-2.5-sunburst\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"1786a32eea22"},"paths":{"/v2/models/openai/gpt-image-2.5-sunburst":{"post":{"operationId":"runRouterModel","summary":"Run openai/gpt-image-2.5-sunburst synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"description":"The request body for a gpt-image model. ONE model id reaches TWO upstream OpenAI operations and this body is what selects between them: OMIT `image` for text-to-image generation, INCLUDE it to edit the image you send. The example below is the generation call; adding `image` to it is the whole of the structural difference, and `image`'s own description carries a copyable edit body.","example":{"prompt":"a rocketship on a launchpad","quality":"low","size":"1024x1024"},"properties":{"background":{"description":"Background transparency. `auto` lets the model choose.","enum":["transparent","opaque","auto"],"example":"opaque","type":"string"},"image":{"$ref":"#/components/schemas/OpenAIImageRouterSource"},"mask":{"allOf":[{"$ref":"#/components/schemas/OpenAIImageRouterMedia"}],"description":"Optional. Marks the region to edit: its fully transparent areas are what the model replaces. Must be a PNG with an alpha channel, under 4 MiB -- the bound routerOpenAIMaskMaxBytes ENFORCES on the resolved bytes, so an oversize mask is refused here by its size rather than relayed from the provider -- and matching the dimensions of the first `image`. Same accepted forms as `image`. Ignored -- and meaningless -- when `image` is absent, because there is nothing to mask: Router strips it from the request rather than forwarding it to the text-to-image operation, which would reject it as an unknown parameter."},"model":{"description":"The gpt-image model to run. Router splices the addressed model id in, so a caller addressing /v2/models/openai/gpt-image-2 does not send this.","example":"gpt-image-2.5-flare","type":"string"},"moderation":{"description":"Content moderation setting","enum":["low","auto"],"example":"auto","type":"string"},"n":{"description":"The number of images to generate (1-10).","example":1,"maximum":10,"minimum":1,"type":"integer"},"output_compression":{"description":"Compression level for JPEG or WebP (0-100)","example":100,"maximum":100,"minimum":0,"type":"integer"},"output_format":{"description":"Format of the output image","enum":["png","webp","jpeg"],"example":"png","type":"string"},"partial_images":{"description":"Meaningful only alongside a live stream, which this surface cannot serve -- `stream` is settled to `false` even when you send it. On a generation, any value other than `0` or `null` is refused with a 400, whether or not the request also sends `stream: true`; on a request carrying `image` the field is dropped rather than forwarded. Omit it.","example":0,"maximum":3,"minimum":0,"nullable":true,"type":"integer"},"prompt":{"description":"A text description of the image to generate, or of the edit to make to `image`.","example":"Give the rocketship rainbow coloring","type":"string"},"quality":{"description":"The quality of the generated or edited image. `xhigh` and `max` are the two tiers gpt-image-2.5-flare and gpt-image-2.5-sunburst were added for; `standard` and `hd` are dall-e-era spellings no admitted model reads.","enum":["auto","low","medium","high","xhigh","max","standard","hd"],"example":"high","type":"string"},"size":{"description":"Size of the image (e.g., 1024x1024, 1536x1024, auto)","example":"1024x1536","type":"string"},"stream":{"description":"Declared so a caller who sends it is not refused, but INERT on this surface: Router SETTLES it to `false` before dispatch, because it captures the provider response rather than relaying a `text/event-stream` (a request carrying `image` drops the field instead, since the edit is re-encoded without it). No Comfy surface serves gpt-image partial-image streaming: the v1 `POST /proxy/openai/images/generations` ingress refuses `stream: true` with a 400 and never forwards the request upstream, so omit the field and read the completed response as one JSON body. Because the stream never opens, OpenAI's restriction on combining `stream: true` with `n` greater than 1 does not apply here; `n` is forwarded as sent.","example":false,"nullable":true,"type":"boolean"},"user":{"description":"A unique identifier for end-user monitoring","example":"user-1234","type":"string"}},"required":["prompt"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/OpenAIImageGenerationResponse"},{"properties":{"background":{"description":"Whether the generated image's background is `opaque` or `transparent`, as OpenAI resolved it for this generation. The request parameter defaults to `auto`, so this is where a caller who did not pin it learns which one was produced.","type":"string"},"created":{"description":"Unix timestamp, in seconds, of when the generation completed. It is the one member OpenAI declares REQUIRED on an image-generation response, so it is present whenever OpenAI is the producer; a fal-, pika-, wavespeed- or runware-served response on any of the alt-backed ids omits it entirely, like the other four resolved-parameter members. Declared `int64` because a present-day epoch value is close enough to 2^31 that an unformatted `integer` generates a 32-bit field in many SDK generators.","format":"int64","type":"integer"},"output_format":{"description":"The encoding of the bytes in `data[].b64_json` — `png`, `webp` or `jpeg`. It is the format OpenAI actually encoded, which is the request's `output_format` when one was sent and `png` when none was, so a decoder can key off it rather than re-deriving the format from the request.","type":"string"},"quality":{"description":"The quality tier the generation actually ran at — OpenAI reports one of `low`, `medium`, `high`, `xhigh` or `max`. It is the RESOLVED tier, not an echo of the request, whose own `quality` defaults to `auto`.","type":"string"},"size":{"description":"The pixel dimensions the generation actually ran at, as `\u003cwidth\u003ex\u003cheight\u003e`. It is the RESOLVED size: the request's `size` defaults to `auto` and explicitly admits `auto`, so this is the only place the dimensions actually used are reported.","type":"string"}},"type":"object"}],"description":"Comfy Router output schema for the OpenAI gpt-image models: OpenAI's own image-generation response, forwarded unchanged. The operation is direct-return (`routerresult/classification.go` classifies `openai /images/generations` as ReturnModeDirect with no poll route), so the body a caller receives is this finished document from the one call. That row also raises the read limit to `BinaryResultMaxBytes`, because the images can arrive INLINE and `n` admits up to 10 of them at a `size` and `quality` the operation does not bound.\nOn the ids with a single producer — `openai/gpt-image-1` and `openai/gpt-image-1.5` — and on any of the alt-backed ids whenever OpenAI itself serves it, the generated images are in `data`, always INLINE at `data[].b64_json`: OpenAI has no `url` response format at all on this surface, so `url` stays unpopulated there even though the shared response component declares it. That also means the bytes are durable in the only sense that matters here — they are in the body, not behind a link that expires. `revised_prompt` is the prompt OpenAI rewrote rather than an asset, and it is present on a response whose image never arrived, so it must not be read as one. `usage` is OpenAI's own token accounting — OpenAI's numbers, not the Comfy charge.\n`openai/gpt-image-2` (`fal`, `pika`, `runware`, `wavespeed`), `openai/gpt-image-2.5-flare` (`fal`, `runware`, `wavespeed`) and `openai/gpt-image-2.5-sunburst` (`fal`, `runware`, `wavespeed`) have ALTERNATE producers: an eligible request on any of them may instead be served by fal, by wavespeed or by runware, and on `openai/gpt-image-2` also by pika. Each partner is reachable explicitly, via `model_provider=fal`, `model_provider=wavespeed`, `model_provider=runware` or (on `openai/gpt-image-2`) `model_provider=pika`; the pika leg is text-to-image only, so it never serves an edit. One may also serve a request that named nobody: `fallback_provider` (default ON) retries a GENERATION once against a registered alternate when the OpenAI attempt fails for a reason attributable to Router or to OpenAI, never to the caller's request. Do not read that retry as fal's alone — it walks the id's registered partners in a fixed order and takes the first whose provider gate, workspace partner-provider policy, eligibility valve and translation all succeed. That order is alphabetical by partner (fal, then pika on `openai/gpt-image-2`, then runware, then wavespeed), so the first of them the caller's gates admit answers it whenever fal is ruled out. And it does not apply to an EDIT: a body carrying `image` selects the multipart edits operation, for which the automatic retry is disabled outright, so an edit that OpenAI fails comes back as OpenAI's own failure rather than being retried against a partner. (Naming a partner explicitly still serves an edit, wherever that partner's translator carries the media field itself.) Every one of those routes is the alt-provider dispatch in `server/implementation/router_run.go`, whose `routerAltTranslators` supplies the translator half against the matching `routeralt` leg that carries the alt id (`fal-gpt-image-2`, `wavespeed-gpt-image-2.5-flare`, `runware-gpt-image-2.5-sunburst` and so on — one leg per id-and-partner pair); the two halves are joined at init and a half-registration fails the build. None of the partners returns inline base64 on any of the alt-backed ids — each only returns hosted URLs, so an alt-provider-served response carries `data[].url` and never `data[].b64_json`, the opposite of the OpenAI-served shape above. A caller that requested `response_format: b64_json` on an alt-provider-served attempt is told so through `X-Comfy-Router-Dropped-Params` rather than being given inline bytes. Both of those statements describe the answer TRANSLATED BACK into this contract, which is what the default `strict_mode=false` returns. `strict_mode=true` runs no translation in either direction: the partner's own raw envelope comes back unchanged (save pika's, from which Pika's own `billing` record is still removed) and carries no `X-Comfy-Router-Dropped-Params`, and Router returns that same raw envelope on the rarer path where a response cannot be translated back. Such a body is the PARTNER's contract, not this schema, and must not be parsed as this one — the call is billed either way. Their translators do not fabricate the OpenAI-specific fields they cannot produce: on an alt-provider-served response `usage`, `revised_prompt`, and all four resolved-parameter members below are entirely ABSENT, not merely empty. Branch on the keys a response actually carries rather than on an assumed producer; which partner served a given request on any of the alt-backed ids is a Comfy routing decision that can change without notice, and is not part of this contract.\nFor the single-producer ids, and for the alt-backed ids whenever OpenAI is the one that served it, the four resolved-parameter members (`background`, `output_format`, `quality`, `size`) report what OpenAI ACTUALLY RAN rather than echoing the request: `background`, `quality` and `size` each default to `auto` on the request side, so a caller who sends `auto` — or sends nothing — only learns the concrete background, tier and `\u003cwidth\u003ex\u003cheight\u003e` that were used by reading them here. `created` is the one member OpenAI declares required whenever OpenAI is the producer.","example":{"created":1767225600,"data":[{"b64_json":"PGJhc2U2ND4="}],"usage":{"input_tokens":12,"input_tokens_details":{"image_tokens":0,"text_tokens":12},"output_tokens":1056,"total_tokens":1068}}}}}}}}}},"components":{"schemas":{"OpenAIImageGenerationResponse":{"properties":{"data":{"items":{"properties":{"b64_json":{"description":"Base64 encoded image data","type":"string"},"revised_prompt":{"description":"Revised prompt","type":"string"},"url":{"description":"URL of the image","type":"string"}},"type":"object"},"type":"array"},"usage":{"properties":{"input_tokens":{"type":"integer"},"input_tokens_details":{"properties":{"image_tokens":{"type":"integer"},"text_tokens":{"type":"integer"}},"type":"object"},"output_tokens":{"type":"integer"},"output_tokens_details":{"properties":{"image_tokens":{"type":"integer"},"text_tokens":{"type":"integer"}},"type":"object"},"total_tokens":{"type":"integer"}},"type":"object"}},"type":"object"},"OpenAIImageRouterMedia":{"description":"One image, as either an https URL Router fetches on the caller's behalf or a `data:image/\u003cformat\u003e;base64,\u003cpayload\u003e` URI carrying the bytes inline. Accepted image types are png, webp and jpeg, and that set is ENFORCED at resolve time rather than left to the provider -- an svg, gif, avif or tiff is refused here, naming the image you sent, instead of becoming a partner error two hops from the cause.\nPrefer the URL form. A base64 payload inflates roughly 4/3 and the whole request body is capped at 100 MiB, so the inline form caps out near a 75 MB source image; a URL sidesteps that entirely. The per-image and per-request media ceilings stated at the end of this description are tighter than the body cap, so for an image of any real size they are what you meet first.\nTHE URL MUST RESOLVE WITHOUT YOUR CREDENTIALS. Router fetches it server-side and sends no caller credential with the request, so a presigned URL -- one whose signature is in the query string -- is the supported form. An authenticated endpoint such as `/api/assets/{id}/content` answers Router 401 rather than the image; request that URL yourself and pass the signed URL it redirects to.\nTHE FETCH IS CONFINED TO A NAMED SET OF HOSTS rather than the open internet, and the same list is re-applied to every redirect hop as well as to the URL you send -- so a URL on any other host, or one that redirects off the list, is refused before any provider is contacted. Today that list is Comfy's own asset delivery only: a signed `storage.googleapis.com` URL under a Comfy asset bucket, which is the form `/api/assets/{id}/content` redirects you to. A partner CDN or a plain https host is NOT on the list.\nTWO WIDENINGS ARE BEING ROLLED OUT AND ARE BOTH OFF BY DEFAULT TODAY. They ship behind ONE switch and are enabled together, so treat everything below as forthcoming rather than as something to build against yet -- ask before you rely on either.\nThe FIRST is the bucket `POST /customers/storage` signs your upload into. Until the rollout reaches your environment an upload URL from that endpoint is refused here, even though the bucket is Comfy's own and the host is the same one -- so \"upload to Comfy, then pass the URL\" does NOT work yet, and the form that does is the signed URL `/api/assets/{id}/content` redirects to.\nThe SECOND is ENABLED PER ACCOUNT rather than for everyone at once, because the bucket behind these hosts is yours rather than ours: even once the switch above is on, an account that has not had third-party inbound media enabled is refused, and told so in those words rather than told the host is unsupported. Ask your Comfy contact to enable it for your account. It additionally admits these third-party object-storage hosts, where the bucket is yours and Router makes no ownership claim on it: Cloudflare R2 (`\u003caccount\u003e.r2.cloudflarestorage.com`, `pub-\u003chash\u003e.r2.dev`), Amazon S3 (`s3.amazonaws.com` and `s3.\u003cregion\u003e.amazonaws.com`, in either the path-style or the `\u003cbucket\u003e.`-prefixed form), Azure Blob Storage (`\u003caccount\u003e.blob.core.windows.net`) and Alibaba Cloud OSS (`\u003cbucket\u003e.oss-\u003cregion\u003e.aliyuncs.com`, including the `oss-accelerate` endpoint). Those are object-storage endpoints specifically: a general-purpose endpoint on the same provider domain, such as an Alibaba Function Compute trigger, is not on the list and is refused.\nWHAT IS MATCHED IS THE HOSTNAME FORM, NOT THE PROVIDER -- so naming a provider above does NOT mean every URL that provider can issue is accepted, and three near-misses are worth stating outright because a caller can reasonably arrive with each of them. An R2 CUSTOM DOMAIN (`cdn.example.com`) is NOT accepted: Cloudflare documents `pub-\u003chash\u003e.r2.dev` as a rate-limited debug hostname and points production traffic at a custom domain instead, but a custom domain carries nothing in its hostname identifying it as R2, so Router cannot tell it from any other host and refuses it -- presign the `\u003caccount\u003e.r2.cloudflarestorage.com` endpoint, or use the `pub-\u003chash\u003e.r2.dev` form. S3's IPv6 dualstack endpoint (`s3.dualstack.\u003cregion\u003e.amazonaws.com`) is not accepted either; use the plain regional endpoint. Nor is Azure Blob's alternate DNS zone (`\u003caccount\u003e.z\u003cN\u003e.blob.storage.azure.net`); use `\u003caccount\u003e.blob.core.windows.net`. The refusal you get back names these same forms, so you do not have to come here to read them.\nThe `\u003caccount\u003e` and `pub-\u003chash\u003e` placeholders above show the form you will normally arrive with; they are not a depth restriction. Matching is on the object-storage domain suffix, so a deeper name under the same namespace (`a.b.pub-\u003chash\u003e.r2.dev`) is accepted as well. What is NOT widened by that is the namespace itself -- the suffix still has to be one of the ones listed.\nThe example below shows the shape only: a real value also carries the signing query parameters, which are deliberately omitted here because a published example lives in the spec forever and a real signature is both a leaked credential and a link that stops working.\nEach image is capped at 25 MiB and one request's images at 64 MiB in total.","example":"https://storage.googleapis.com/comfy-cloud-assets/0199b3f4-1d2e-7a3b-8c4d-5e6f70819234.png","type":"string"},"OpenAIImageRouterSource":{"description":"The source image(s) to edit. PRESENCE OF THIS FIELD SELECTS THE EDIT OPERATION -- omit it entirely for text-to-image generation.\nThe model's published example is the GENERATION call, which omits this field. Adding `image` is the whole of the difference between the two modes -- same endpoint, same model id -- except that an edit prompt describes the CHANGE you want rather than the scene, so the edit body below reads differently from the generation example even though only this field is structurally new.\nIt is copyable exactly as printed: `{\"prompt\": \"give the rocketship rainbow coloring\", \"image\": [\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==\"], \"size\": \"1024x1024\"}` -- the payload is a complete 1x1 PNG, not an abbreviation, and this body is pinned as an accepted case in Router's own request-validation tests. Swap in your own image's bytes.\nAccepts one image or an array of them; gpt-image-1 and later take up to 16, which Router enforces from this schema before dispatching.","oneOf":[{"$ref":"#/components/schemas/OpenAIImageRouterMedia"},{"items":{"$ref":"#/components/schemas/OpenAIImageRouterMedia"},"maxItems":16,"minItems":1,"type":"array"}]}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"openai/gpt-image-2.5-sunburst","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image"],"x-comfy-router-output-modalities":["image"],"x-comfy-router-alt-providers":[{"provider":"fal","model_id":"fal/fal-gpt-image-2.5-sunburst"},{"provider":"runware","model_id":"runware/runware-gpt-image-2.5-sunburst"},{"provider":"wavespeed","model_id":"wavespeed/wavespeed-gpt-image-2.5-sunburst"}]}
diff --git a/router-schemas/openai/gpt-image-2.json b/router-schemas/openai/gpt-image-2.json
index 2e27be2138..fa54336464 100644
--- a/router-schemas/openai/gpt-image-2.json
+++ b/router-schemas/openai/gpt-image-2.json
@@ -1 +1 @@
-{"openapi":"3.0.2","info":{"title":"openai/gpt-image-2","description":"The request body Comfy Router accepts for the model \"openai/gpt-image-2\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"f44c74e2ce6b"},"paths":{"/v2/models/openai/gpt-image-2":{"post":{"operationId":"runRouterModel","summary":"Run openai/gpt-image-2 synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"description":"The request body for a gpt-image model. ONE model id reaches TWO upstream OpenAI operations and this body is what selects between them: OMIT `image` for text-to-image generation, INCLUDE it to edit the image you send. The example below is the generation call; adding `image` to it is the whole of the structural difference, and `image`'s own description carries a copyable edit body.","example":{"prompt":"a rocketship on a launchpad","quality":"low","size":"1024x1024"},"properties":{"background":{"description":"Background transparency. `auto` lets the model choose.","enum":["transparent","opaque","auto"],"example":"opaque","type":"string"},"image":{"$ref":"#/components/schemas/OpenAIImageRouterSource"},"mask":{"allOf":[{"$ref":"#/components/schemas/OpenAIImageRouterMedia"}],"description":"Optional. Marks the region to edit: its fully transparent areas are what the model replaces. Must be a PNG with an alpha channel, under 4 MiB -- the bound routerOpenAIMaskMaxBytes ENFORCES on the resolved bytes, so an oversize mask is refused here by its size rather than relayed from the provider -- and matching the dimensions of the first `image`. Same accepted forms as `image`. Ignored -- and meaningless -- when `image` is absent, because there is nothing to mask: Router strips it from the request rather than forwarding it to the text-to-image operation, which would reject it as an unknown parameter."},"model":{"description":"The gpt-image model to run. Router splices the addressed model id in, so a caller addressing /v2/models/openai/gpt-image-2 does not send this.","example":"gpt-image-2.5-flare","type":"string"},"moderation":{"description":"Content moderation setting","enum":["low","auto"],"example":"auto","type":"string"},"n":{"description":"The number of images to generate (1-10).","example":1,"maximum":10,"minimum":1,"type":"integer"},"output_compression":{"description":"Compression level for JPEG or WebP (0-100)","example":100,"maximum":100,"minimum":0,"type":"integer"},"output_format":{"description":"Format of the output image","enum":["png","webp","jpeg"],"example":"png","type":"string"},"partial_images":{"description":"Meaningful only alongside a live stream, which this surface cannot serve -- `stream` is settled to `false` even when you send it. On a generation, any value other than `0` or `null` is refused with a 400, whether or not the request also sends `stream: true`; on a request carrying `image` the field is dropped rather than forwarded. Omit it.","example":0,"maximum":3,"minimum":0,"nullable":true,"type":"integer"},"prompt":{"description":"A text description of the image to generate, or of the edit to make to `image`.","example":"Give the rocketship rainbow coloring","type":"string"},"quality":{"description":"The quality of the generated or edited image. `xhigh` and `max` are the two tiers gpt-image-2.5-flare and gpt-image-2.5-sunburst were added for; `standard` and `hd` are dall-e-era spellings no admitted model reads.","enum":["auto","low","medium","high","xhigh","max","standard","hd"],"example":"high","type":"string"},"size":{"description":"Size of the image (e.g., 1024x1024, 1536x1024, auto)","example":"1024x1536","type":"string"},"stream":{"description":"Declared so a caller who sends it is not refused, but INERT on this surface: Router SETTLES it to `false` before dispatch, because it captures the provider response rather than relaying a `text/event-stream` (a request carrying `image` drops the field instead, since the edit is re-encoded without it). No Comfy surface serves gpt-image partial-image streaming: the v1 `POST /proxy/openai/images/generations` ingress refuses `stream: true` with a 400 and never forwards the request upstream, so omit the field and read the completed response as one JSON body. Because the stream never opens, OpenAI's restriction on combining `stream: true` with `n` greater than 1 does not apply here; `n` is forwarded as sent.","example":false,"nullable":true,"type":"boolean"},"user":{"description":"A unique identifier for end-user monitoring","example":"user-1234","type":"string"}},"required":["prompt"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/OpenAIImageGenerationResponse"},{"properties":{"background":{"description":"Whether the generated image's background is `opaque` or `transparent`, as OpenAI resolved it for this generation. The request parameter defaults to `auto`, so this is where a caller who did not pin it learns which one was produced.","type":"string"},"created":{"description":"Unix timestamp, in seconds, of when the generation completed. It is the one member OpenAI declares REQUIRED on an image-generation response, so it is present whenever OpenAI is the producer; a fal-, wavespeed- or runware-served response on any of the alt-backed ids omits it entirely, like the other four resolved-parameter members. Declared `int64` because a present-day epoch value is close enough to 2^31 that an unformatted `integer` generates a 32-bit field in many SDK generators.","format":"int64","type":"integer"},"output_format":{"description":"The encoding of the bytes in `data[].b64_json` — `png`, `webp` or `jpeg`. It is the format OpenAI actually encoded, which is the request's `output_format` when one was sent and `png` when none was, so a decoder can key off it rather than re-deriving the format from the request.","type":"string"},"quality":{"description":"The quality tier the generation actually ran at — OpenAI reports one of `low`, `medium`, `high`, `xhigh` or `max`. It is the RESOLVED tier, not an echo of the request, whose own `quality` defaults to `auto`.","type":"string"},"size":{"description":"The pixel dimensions the generation actually ran at, as `\u003cwidth\u003ex\u003cheight\u003e`. It is the RESOLVED size: the request's `size` defaults to `auto` and explicitly admits `auto`, so this is the only place the dimensions actually used are reported.","type":"string"}},"type":"object"}],"description":"Comfy Router output schema for the OpenAI gpt-image models: OpenAI's own image-generation response, forwarded unchanged. The operation is direct-return (`routerresult/classification.go` classifies `openai /images/generations` as ReturnModeDirect with no poll route), so the body a caller receives is this finished document from the one call. That row also raises the read limit to `BinaryResultMaxBytes`, because the images can arrive INLINE and `n` admits up to 10 of them at a `size` and `quality` the operation does not bound.\nOn the ids with a single producer — `openai/gpt-image-1` and `openai/gpt-image-1.5` — and on any of the alt-backed ids whenever OpenAI itself serves it, the generated images are in `data`, always INLINE at `data[].b64_json`: OpenAI has no `url` response format at all on this surface, so `url` stays unpopulated there even though the shared response component declares it. That also means the bytes are durable in the only sense that matters here — they are in the body, not behind a link that expires. `revised_prompt` is the prompt OpenAI rewrote rather than an asset, and it is present on a response whose image never arrived, so it must not be read as one. `usage` is OpenAI's own token accounting — OpenAI's numbers, not the Comfy charge.\n`openai/gpt-image-2` (`fal`, `runware`, `wavespeed`), `openai/gpt-image-2.5-flare` (`fal`, `runware`, `wavespeed`) and `openai/gpt-image-2.5-sunburst` (`fal`, `runware`, `wavespeed`) have ALTERNATE producers: an eligible request on any of them may instead be served by fal, by wavespeed or by runware. Each partner is reachable explicitly, via `model_provider=fal`, `model_provider=wavespeed` or `model_provider=runware`. One may also serve a request that named nobody: `fallback_provider` (default ON) retries a GENERATION once against a registered alternate when the OpenAI attempt fails for a reason attributable to Router or to OpenAI, never to the caller's request. Do not read that retry as fal's alone — it walks the id's registered partners in a fixed order and takes the first whose provider gate, workspace partner-provider policy, eligibility valve and translation all succeed, so wavespeed or runware answers it whenever fal is ruled out. And it does not apply to an EDIT: a body carrying `image` selects the multipart edits operation, for which the automatic retry is disabled outright, so an edit that OpenAI fails comes back as OpenAI's own failure rather than being retried against a partner. (Naming a partner explicitly still serves an edit, wherever that partner's translator carries the media field itself.) Every one of those routes is the alt-provider dispatch in `server/implementation/router_run.go`, whose `routerAltTranslators` supplies the translator half against the matching `routeralt` leg that carries the alt id (`fal-gpt-image-2`, `wavespeed-gpt-image-2.5-flare`, `runware-gpt-image-2.5-sunburst` and so on — one leg per id-and-partner pair); the two halves are joined at init and a half-registration fails the build. None of the partners returns inline base64 on any of the alt-backed ids — each only returns hosted URLs, so an alt-provider-served response carries `data[].url` and never `data[].b64_json`, the opposite of the OpenAI-served shape above. A caller that requested `response_format: b64_json` on an alt-provider-served attempt is told so through `X-Comfy-Router-Dropped-Params` rather than being given inline bytes. Both of those statements describe the answer TRANSLATED BACK into this contract, which is what the default `strict_mode=false` returns. `strict_mode=true` runs no translation in either direction: the partner's own raw envelope comes back unchanged and carries no `X-Comfy-Router-Dropped-Params`, and Router returns that same raw envelope on the rarer path where a response cannot be translated back. Such a body is the PARTNER's contract, not this schema, and must not be parsed as this one — the call is billed either way. Their translators do not fabricate the OpenAI-specific fields they cannot produce: on an alt-provider-served response `usage`, `revised_prompt`, and all four resolved-parameter members below are entirely ABSENT, not merely empty. Branch on the keys a response actually carries rather than on an assumed producer; which partner served a given request on any of the alt-backed ids is a Comfy routing decision that can change without notice, and is not part of this contract.\nFor the single-producer ids, and for the alt-backed ids whenever OpenAI is the one that served it, the four resolved-parameter members (`background`, `output_format`, `quality`, `size`) report what OpenAI ACTUALLY RAN rather than echoing the request: `background`, `quality` and `size` each default to `auto` on the request side, so a caller who sends `auto` — or sends nothing — only learns the concrete background, tier and `\u003cwidth\u003ex\u003cheight\u003e` that were used by reading them here. `created` is the one member OpenAI declares required whenever OpenAI is the producer.","example":{"created":1767225600,"data":[{"b64_json":"PGJhc2U2ND4="}],"usage":{"input_tokens":12,"input_tokens_details":{"image_tokens":0,"text_tokens":12},"output_tokens":1056,"total_tokens":1068}}}}}}}}}},"components":{"schemas":{"OpenAIImageGenerationResponse":{"properties":{"data":{"items":{"properties":{"b64_json":{"description":"Base64 encoded image data","type":"string"},"revised_prompt":{"description":"Revised prompt","type":"string"},"url":{"description":"URL of the image","type":"string"}},"type":"object"},"type":"array"},"usage":{"properties":{"input_tokens":{"type":"integer"},"input_tokens_details":{"properties":{"image_tokens":{"type":"integer"},"text_tokens":{"type":"integer"}},"type":"object"},"output_tokens":{"type":"integer"},"output_tokens_details":{"properties":{"image_tokens":{"type":"integer"},"text_tokens":{"type":"integer"}},"type":"object"},"total_tokens":{"type":"integer"}},"type":"object"}},"type":"object"},"OpenAIImageRouterMedia":{"description":"One image, as either an https URL Router fetches on the caller's behalf or a `data:image/\u003cformat\u003e;base64,\u003cpayload\u003e` URI carrying the bytes inline. Accepted image types are png, webp and jpeg, and that set is ENFORCED at resolve time rather than left to the provider -- an svg, gif, avif or tiff is refused here, naming the image you sent, instead of becoming a partner error two hops from the cause.\nPrefer the URL form. A base64 payload inflates roughly 4/3 and the whole request body is capped at 100 MiB, so the inline form caps out near a 75 MB source image; a URL sidesteps that entirely. The per-image and per-request media ceilings stated at the end of this description are tighter than the body cap, so for an image of any real size they are what you meet first.\nTHE URL MUST RESOLVE WITHOUT YOUR CREDENTIALS. Router fetches it server-side and sends no caller credential with the request, so a presigned URL -- one whose signature is in the query string -- is the supported form. An authenticated endpoint such as `/api/assets/{id}/content` answers Router 401 rather than the image; request that URL yourself and pass the signed URL it redirects to.\nTHE FETCH IS CONFINED TO A NAMED SET OF HOSTS rather than the open internet, and the same list is re-applied to every redirect hop as well as to the URL you send -- so a URL on any other host, or one that redirects off the list, is refused before any provider is contacted. Today that list is Comfy's own asset delivery only: a signed `storage.googleapis.com` URL under a Comfy asset bucket, which is the form `/api/assets/{id}/content` redirects you to. A partner CDN or a plain https host is NOT on the list.\nTWO WIDENINGS ARE BEING ROLLED OUT AND ARE BOTH OFF BY DEFAULT TODAY. They ship behind ONE switch and are enabled together, so treat everything below as forthcoming rather than as something to build against yet -- ask before you rely on either.\nThe FIRST is the bucket `POST /customers/storage` signs your upload into. Until the rollout reaches your environment an upload URL from that endpoint is refused here, even though the bucket is Comfy's own and the host is the same one -- so \"upload to Comfy, then pass the URL\" does NOT work yet, and the form that does is the signed URL `/api/assets/{id}/content` redirects to.\nThe SECOND is ENABLED PER ACCOUNT rather than for everyone at once, because the bucket behind these hosts is yours rather than ours: even once the switch above is on, an account that has not had third-party inbound media enabled is refused, and told so in those words rather than told the host is unsupported. Ask your Comfy contact to enable it for your account. It additionally admits these third-party object-storage hosts, where the bucket is yours and Router makes no ownership claim on it: Cloudflare R2 (`\u003caccount\u003e.r2.cloudflarestorage.com`, `pub-\u003chash\u003e.r2.dev`), Amazon S3 (`s3.amazonaws.com` and `s3.\u003cregion\u003e.amazonaws.com`, in either the path-style or the `\u003cbucket\u003e.`-prefixed form), Azure Blob Storage (`\u003caccount\u003e.blob.core.windows.net`) and Alibaba Cloud OSS (`\u003cbucket\u003e.oss-\u003cregion\u003e.aliyuncs.com`, including the `oss-accelerate` endpoint). Those are object-storage endpoints specifically: a general-purpose endpoint on the same provider domain, such as an Alibaba Function Compute trigger, is not on the list and is refused.\nWHAT IS MATCHED IS THE HOSTNAME FORM, NOT THE PROVIDER -- so naming a provider above does NOT mean every URL that provider can issue is accepted, and three near-misses are worth stating outright because a caller can reasonably arrive with each of them. An R2 CUSTOM DOMAIN (`cdn.example.com`) is NOT accepted: Cloudflare documents `pub-\u003chash\u003e.r2.dev` as a rate-limited debug hostname and points production traffic at a custom domain instead, but a custom domain carries nothing in its hostname identifying it as R2, so Router cannot tell it from any other host and refuses it -- presign the `\u003caccount\u003e.r2.cloudflarestorage.com` endpoint, or use the `pub-\u003chash\u003e.r2.dev` form. S3's IPv6 dualstack endpoint (`s3.dualstack.\u003cregion\u003e.amazonaws.com`) is not accepted either; use the plain regional endpoint. Nor is Azure Blob's alternate DNS zone (`\u003caccount\u003e.z\u003cN\u003e.blob.storage.azure.net`); use `\u003caccount\u003e.blob.core.windows.net`. The refusal you get back names these same forms, so you do not have to come here to read them.\nThe `\u003caccount\u003e` and `pub-\u003chash\u003e` placeholders above show the form you will normally arrive with; they are not a depth restriction. Matching is on the object-storage domain suffix, so a deeper name under the same namespace (`a.b.pub-\u003chash\u003e.r2.dev`) is accepted as well. What is NOT widened by that is the namespace itself -- the suffix still has to be one of the ones listed.\nThe example below shows the shape only: a real value also carries the signing query parameters, which are deliberately omitted here because a published example lives in the spec forever and a real signature is both a leaked credential and a link that stops working.\nEach image is capped at 25 MiB and one request's images at 64 MiB in total.","example":"https://storage.googleapis.com/comfy-cloud-assets/0199b3f4-1d2e-7a3b-8c4d-5e6f70819234.png","type":"string"},"OpenAIImageRouterSource":{"description":"The source image(s) to edit. PRESENCE OF THIS FIELD SELECTS THE EDIT OPERATION -- omit it entirely for text-to-image generation.\nThe model's published example is the GENERATION call, which omits this field. Adding `image` is the whole of the difference between the two modes -- same endpoint, same model id -- except that an edit prompt describes the CHANGE you want rather than the scene, so the edit body below reads differently from the generation example even though only this field is structurally new.\nIt is copyable exactly as printed: `{\"prompt\": \"give the rocketship rainbow coloring\", \"image\": [\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==\"], \"size\": \"1024x1024\"}` -- the payload is a complete 1x1 PNG, not an abbreviation, and this body is pinned as an accepted case in Router's own request-validation tests. Swap in your own image's bytes.\nAccepts one image or an array of them; gpt-image-1 and later take up to 16, which Router enforces from this schema before dispatching.","oneOf":[{"$ref":"#/components/schemas/OpenAIImageRouterMedia"},{"items":{"$ref":"#/components/schemas/OpenAIImageRouterMedia"},"maxItems":16,"minItems":1,"type":"array"}]}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"openai/gpt-image-2","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image"],"x-comfy-router-output-modalities":["image"],"x-comfy-router-alt-providers":[{"provider":"fal","model_id":"fal/fal-gpt-image-2"},{"provider":"runware","model_id":"runware/runware-gpt-image-2"},{"provider":"wavespeed","model_id":"wavespeed/wavespeed-gpt-image-2"}]}
+{"openapi":"3.0.2","info":{"title":"openai/gpt-image-2","description":"The request body Comfy Router accepts for the model \"openai/gpt-image-2\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"1786a32eea22"},"paths":{"/v2/models/openai/gpt-image-2":{"post":{"operationId":"runRouterModel","summary":"Run openai/gpt-image-2 synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"description":"The request body for a gpt-image model. ONE model id reaches TWO upstream OpenAI operations and this body is what selects between them: OMIT `image` for text-to-image generation, INCLUDE it to edit the image you send. The example below is the generation call; adding `image` to it is the whole of the structural difference, and `image`'s own description carries a copyable edit body.","example":{"prompt":"a rocketship on a launchpad","quality":"low","size":"1024x1024"},"properties":{"background":{"description":"Background transparency. `auto` lets the model choose.","enum":["transparent","opaque","auto"],"example":"opaque","type":"string"},"image":{"$ref":"#/components/schemas/OpenAIImageRouterSource"},"mask":{"allOf":[{"$ref":"#/components/schemas/OpenAIImageRouterMedia"}],"description":"Optional. Marks the region to edit: its fully transparent areas are what the model replaces. Must be a PNG with an alpha channel, under 4 MiB -- the bound routerOpenAIMaskMaxBytes ENFORCES on the resolved bytes, so an oversize mask is refused here by its size rather than relayed from the provider -- and matching the dimensions of the first `image`. Same accepted forms as `image`. Ignored -- and meaningless -- when `image` is absent, because there is nothing to mask: Router strips it from the request rather than forwarding it to the text-to-image operation, which would reject it as an unknown parameter."},"model":{"description":"The gpt-image model to run. Router splices the addressed model id in, so a caller addressing /v2/models/openai/gpt-image-2 does not send this.","example":"gpt-image-2.5-flare","type":"string"},"moderation":{"description":"Content moderation setting","enum":["low","auto"],"example":"auto","type":"string"},"n":{"description":"The number of images to generate (1-10).","example":1,"maximum":10,"minimum":1,"type":"integer"},"output_compression":{"description":"Compression level for JPEG or WebP (0-100)","example":100,"maximum":100,"minimum":0,"type":"integer"},"output_format":{"description":"Format of the output image","enum":["png","webp","jpeg"],"example":"png","type":"string"},"partial_images":{"description":"Meaningful only alongside a live stream, which this surface cannot serve -- `stream` is settled to `false` even when you send it. On a generation, any value other than `0` or `null` is refused with a 400, whether or not the request also sends `stream: true`; on a request carrying `image` the field is dropped rather than forwarded. Omit it.","example":0,"maximum":3,"minimum":0,"nullable":true,"type":"integer"},"prompt":{"description":"A text description of the image to generate, or of the edit to make to `image`.","example":"Give the rocketship rainbow coloring","type":"string"},"quality":{"description":"The quality of the generated or edited image. `xhigh` and `max` are the two tiers gpt-image-2.5-flare and gpt-image-2.5-sunburst were added for; `standard` and `hd` are dall-e-era spellings no admitted model reads.","enum":["auto","low","medium","high","xhigh","max","standard","hd"],"example":"high","type":"string"},"size":{"description":"Size of the image (e.g., 1024x1024, 1536x1024, auto)","example":"1024x1536","type":"string"},"stream":{"description":"Declared so a caller who sends it is not refused, but INERT on this surface: Router SETTLES it to `false` before dispatch, because it captures the provider response rather than relaying a `text/event-stream` (a request carrying `image` drops the field instead, since the edit is re-encoded without it). No Comfy surface serves gpt-image partial-image streaming: the v1 `POST /proxy/openai/images/generations` ingress refuses `stream: true` with a 400 and never forwards the request upstream, so omit the field and read the completed response as one JSON body. Because the stream never opens, OpenAI's restriction on combining `stream: true` with `n` greater than 1 does not apply here; `n` is forwarded as sent.","example":false,"nullable":true,"type":"boolean"},"user":{"description":"A unique identifier for end-user monitoring","example":"user-1234","type":"string"}},"required":["prompt"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/OpenAIImageGenerationResponse"},{"properties":{"background":{"description":"Whether the generated image's background is `opaque` or `transparent`, as OpenAI resolved it for this generation. The request parameter defaults to `auto`, so this is where a caller who did not pin it learns which one was produced.","type":"string"},"created":{"description":"Unix timestamp, in seconds, of when the generation completed. It is the one member OpenAI declares REQUIRED on an image-generation response, so it is present whenever OpenAI is the producer; a fal-, pika-, wavespeed- or runware-served response on any of the alt-backed ids omits it entirely, like the other four resolved-parameter members. Declared `int64` because a present-day epoch value is close enough to 2^31 that an unformatted `integer` generates a 32-bit field in many SDK generators.","format":"int64","type":"integer"},"output_format":{"description":"The encoding of the bytes in `data[].b64_json` — `png`, `webp` or `jpeg`. It is the format OpenAI actually encoded, which is the request's `output_format` when one was sent and `png` when none was, so a decoder can key off it rather than re-deriving the format from the request.","type":"string"},"quality":{"description":"The quality tier the generation actually ran at — OpenAI reports one of `low`, `medium`, `high`, `xhigh` or `max`. It is the RESOLVED tier, not an echo of the request, whose own `quality` defaults to `auto`.","type":"string"},"size":{"description":"The pixel dimensions the generation actually ran at, as `\u003cwidth\u003ex\u003cheight\u003e`. It is the RESOLVED size: the request's `size` defaults to `auto` and explicitly admits `auto`, so this is the only place the dimensions actually used are reported.","type":"string"}},"type":"object"}],"description":"Comfy Router output schema for the OpenAI gpt-image models: OpenAI's own image-generation response, forwarded unchanged. The operation is direct-return (`routerresult/classification.go` classifies `openai /images/generations` as ReturnModeDirect with no poll route), so the body a caller receives is this finished document from the one call. That row also raises the read limit to `BinaryResultMaxBytes`, because the images can arrive INLINE and `n` admits up to 10 of them at a `size` and `quality` the operation does not bound.\nOn the ids with a single producer — `openai/gpt-image-1` and `openai/gpt-image-1.5` — and on any of the alt-backed ids whenever OpenAI itself serves it, the generated images are in `data`, always INLINE at `data[].b64_json`: OpenAI has no `url` response format at all on this surface, so `url` stays unpopulated there even though the shared response component declares it. That also means the bytes are durable in the only sense that matters here — they are in the body, not behind a link that expires. `revised_prompt` is the prompt OpenAI rewrote rather than an asset, and it is present on a response whose image never arrived, so it must not be read as one. `usage` is OpenAI's own token accounting — OpenAI's numbers, not the Comfy charge.\n`openai/gpt-image-2` (`fal`, `pika`, `runware`, `wavespeed`), `openai/gpt-image-2.5-flare` (`fal`, `runware`, `wavespeed`) and `openai/gpt-image-2.5-sunburst` (`fal`, `runware`, `wavespeed`) have ALTERNATE producers: an eligible request on any of them may instead be served by fal, by wavespeed or by runware, and on `openai/gpt-image-2` also by pika. Each partner is reachable explicitly, via `model_provider=fal`, `model_provider=wavespeed`, `model_provider=runware` or (on `openai/gpt-image-2`) `model_provider=pika`; the pika leg is text-to-image only, so it never serves an edit. One may also serve a request that named nobody: `fallback_provider` (default ON) retries a GENERATION once against a registered alternate when the OpenAI attempt fails for a reason attributable to Router or to OpenAI, never to the caller's request. Do not read that retry as fal's alone — it walks the id's registered partners in a fixed order and takes the first whose provider gate, workspace partner-provider policy, eligibility valve and translation all succeed. That order is alphabetical by partner (fal, then pika on `openai/gpt-image-2`, then runware, then wavespeed), so the first of them the caller's gates admit answers it whenever fal is ruled out. And it does not apply to an EDIT: a body carrying `image` selects the multipart edits operation, for which the automatic retry is disabled outright, so an edit that OpenAI fails comes back as OpenAI's own failure rather than being retried against a partner. (Naming a partner explicitly still serves an edit, wherever that partner's translator carries the media field itself.) Every one of those routes is the alt-provider dispatch in `server/implementation/router_run.go`, whose `routerAltTranslators` supplies the translator half against the matching `routeralt` leg that carries the alt id (`fal-gpt-image-2`, `wavespeed-gpt-image-2.5-flare`, `runware-gpt-image-2.5-sunburst` and so on — one leg per id-and-partner pair); the two halves are joined at init and a half-registration fails the build. None of the partners returns inline base64 on any of the alt-backed ids — each only returns hosted URLs, so an alt-provider-served response carries `data[].url` and never `data[].b64_json`, the opposite of the OpenAI-served shape above. A caller that requested `response_format: b64_json` on an alt-provider-served attempt is told so through `X-Comfy-Router-Dropped-Params` rather than being given inline bytes. Both of those statements describe the answer TRANSLATED BACK into this contract, which is what the default `strict_mode=false` returns. `strict_mode=true` runs no translation in either direction: the partner's own raw envelope comes back unchanged (save pika's, from which Pika's own `billing` record is still removed) and carries no `X-Comfy-Router-Dropped-Params`, and Router returns that same raw envelope on the rarer path where a response cannot be translated back. Such a body is the PARTNER's contract, not this schema, and must not be parsed as this one — the call is billed either way. Their translators do not fabricate the OpenAI-specific fields they cannot produce: on an alt-provider-served response `usage`, `revised_prompt`, and all four resolved-parameter members below are entirely ABSENT, not merely empty. Branch on the keys a response actually carries rather than on an assumed producer; which partner served a given request on any of the alt-backed ids is a Comfy routing decision that can change without notice, and is not part of this contract.\nFor the single-producer ids, and for the alt-backed ids whenever OpenAI is the one that served it, the four resolved-parameter members (`background`, `output_format`, `quality`, `size`) report what OpenAI ACTUALLY RAN rather than echoing the request: `background`, `quality` and `size` each default to `auto` on the request side, so a caller who sends `auto` — or sends nothing — only learns the concrete background, tier and `\u003cwidth\u003ex\u003cheight\u003e` that were used by reading them here. `created` is the one member OpenAI declares required whenever OpenAI is the producer.","example":{"created":1767225600,"data":[{"b64_json":"PGJhc2U2ND4="}],"usage":{"input_tokens":12,"input_tokens_details":{"image_tokens":0,"text_tokens":12},"output_tokens":1056,"total_tokens":1068}}}}}}}}}},"components":{"schemas":{"OpenAIImageGenerationResponse":{"properties":{"data":{"items":{"properties":{"b64_json":{"description":"Base64 encoded image data","type":"string"},"revised_prompt":{"description":"Revised prompt","type":"string"},"url":{"description":"URL of the image","type":"string"}},"type":"object"},"type":"array"},"usage":{"properties":{"input_tokens":{"type":"integer"},"input_tokens_details":{"properties":{"image_tokens":{"type":"integer"},"text_tokens":{"type":"integer"}},"type":"object"},"output_tokens":{"type":"integer"},"output_tokens_details":{"properties":{"image_tokens":{"type":"integer"},"text_tokens":{"type":"integer"}},"type":"object"},"total_tokens":{"type":"integer"}},"type":"object"}},"type":"object"},"OpenAIImageRouterMedia":{"description":"One image, as either an https URL Router fetches on the caller's behalf or a `data:image/\u003cformat\u003e;base64,\u003cpayload\u003e` URI carrying the bytes inline. Accepted image types are png, webp and jpeg, and that set is ENFORCED at resolve time rather than left to the provider -- an svg, gif, avif or tiff is refused here, naming the image you sent, instead of becoming a partner error two hops from the cause.\nPrefer the URL form. A base64 payload inflates roughly 4/3 and the whole request body is capped at 100 MiB, so the inline form caps out near a 75 MB source image; a URL sidesteps that entirely. The per-image and per-request media ceilings stated at the end of this description are tighter than the body cap, so for an image of any real size they are what you meet first.\nTHE URL MUST RESOLVE WITHOUT YOUR CREDENTIALS. Router fetches it server-side and sends no caller credential with the request, so a presigned URL -- one whose signature is in the query string -- is the supported form. An authenticated endpoint such as `/api/assets/{id}/content` answers Router 401 rather than the image; request that URL yourself and pass the signed URL it redirects to.\nTHE FETCH IS CONFINED TO A NAMED SET OF HOSTS rather than the open internet, and the same list is re-applied to every redirect hop as well as to the URL you send -- so a URL on any other host, or one that redirects off the list, is refused before any provider is contacted. Today that list is Comfy's own asset delivery only: a signed `storage.googleapis.com` URL under a Comfy asset bucket, which is the form `/api/assets/{id}/content` redirects you to. A partner CDN or a plain https host is NOT on the list.\nTWO WIDENINGS ARE BEING ROLLED OUT AND ARE BOTH OFF BY DEFAULT TODAY. They ship behind ONE switch and are enabled together, so treat everything below as forthcoming rather than as something to build against yet -- ask before you rely on either.\nThe FIRST is the bucket `POST /customers/storage` signs your upload into. Until the rollout reaches your environment an upload URL from that endpoint is refused here, even though the bucket is Comfy's own and the host is the same one -- so \"upload to Comfy, then pass the URL\" does NOT work yet, and the form that does is the signed URL `/api/assets/{id}/content` redirects to.\nThe SECOND is ENABLED PER ACCOUNT rather than for everyone at once, because the bucket behind these hosts is yours rather than ours: even once the switch above is on, an account that has not had third-party inbound media enabled is refused, and told so in those words rather than told the host is unsupported. Ask your Comfy contact to enable it for your account. It additionally admits these third-party object-storage hosts, where the bucket is yours and Router makes no ownership claim on it: Cloudflare R2 (`\u003caccount\u003e.r2.cloudflarestorage.com`, `pub-\u003chash\u003e.r2.dev`), Amazon S3 (`s3.amazonaws.com` and `s3.\u003cregion\u003e.amazonaws.com`, in either the path-style or the `\u003cbucket\u003e.`-prefixed form), Azure Blob Storage (`\u003caccount\u003e.blob.core.windows.net`) and Alibaba Cloud OSS (`\u003cbucket\u003e.oss-\u003cregion\u003e.aliyuncs.com`, including the `oss-accelerate` endpoint). Those are object-storage endpoints specifically: a general-purpose endpoint on the same provider domain, such as an Alibaba Function Compute trigger, is not on the list and is refused.\nWHAT IS MATCHED IS THE HOSTNAME FORM, NOT THE PROVIDER -- so naming a provider above does NOT mean every URL that provider can issue is accepted, and three near-misses are worth stating outright because a caller can reasonably arrive with each of them. An R2 CUSTOM DOMAIN (`cdn.example.com`) is NOT accepted: Cloudflare documents `pub-\u003chash\u003e.r2.dev` as a rate-limited debug hostname and points production traffic at a custom domain instead, but a custom domain carries nothing in its hostname identifying it as R2, so Router cannot tell it from any other host and refuses it -- presign the `\u003caccount\u003e.r2.cloudflarestorage.com` endpoint, or use the `pub-\u003chash\u003e.r2.dev` form. S3's IPv6 dualstack endpoint (`s3.dualstack.\u003cregion\u003e.amazonaws.com`) is not accepted either; use the plain regional endpoint. Nor is Azure Blob's alternate DNS zone (`\u003caccount\u003e.z\u003cN\u003e.blob.storage.azure.net`); use `\u003caccount\u003e.blob.core.windows.net`. The refusal you get back names these same forms, so you do not have to come here to read them.\nThe `\u003caccount\u003e` and `pub-\u003chash\u003e` placeholders above show the form you will normally arrive with; they are not a depth restriction. Matching is on the object-storage domain suffix, so a deeper name under the same namespace (`a.b.pub-\u003chash\u003e.r2.dev`) is accepted as well. What is NOT widened by that is the namespace itself -- the suffix still has to be one of the ones listed.\nThe example below shows the shape only: a real value also carries the signing query parameters, which are deliberately omitted here because a published example lives in the spec forever and a real signature is both a leaked credential and a link that stops working.\nEach image is capped at 25 MiB and one request's images at 64 MiB in total.","example":"https://storage.googleapis.com/comfy-cloud-assets/0199b3f4-1d2e-7a3b-8c4d-5e6f70819234.png","type":"string"},"OpenAIImageRouterSource":{"description":"The source image(s) to edit. PRESENCE OF THIS FIELD SELECTS THE EDIT OPERATION -- omit it entirely for text-to-image generation.\nThe model's published example is the GENERATION call, which omits this field. Adding `image` is the whole of the difference between the two modes -- same endpoint, same model id -- except that an edit prompt describes the CHANGE you want rather than the scene, so the edit body below reads differently from the generation example even though only this field is structurally new.\nIt is copyable exactly as printed: `{\"prompt\": \"give the rocketship rainbow coloring\", \"image\": [\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==\"], \"size\": \"1024x1024\"}` -- the payload is a complete 1x1 PNG, not an abbreviation, and this body is pinned as an accepted case in Router's own request-validation tests. Swap in your own image's bytes.\nAccepts one image or an array of them; gpt-image-1 and later take up to 16, which Router enforces from this schema before dispatching.","oneOf":[{"$ref":"#/components/schemas/OpenAIImageRouterMedia"},{"items":{"$ref":"#/components/schemas/OpenAIImageRouterMedia"},"maxItems":16,"minItems":1,"type":"array"}]}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"openai/gpt-image-2","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image"],"x-comfy-router-output-modalities":["image"],"x-comfy-router-alt-providers":[{"provider":"fal","model_id":"fal/fal-gpt-image-2"},{"provider":"pika","model_id":"pika/pika-gpt-image-2"},{"provider":"runware","model_id":"runware/runware-gpt-image-2"},{"provider":"wavespeed","model_id":"wavespeed/wavespeed-gpt-image-2"}]}
diff --git a/router-schemas/pika/pika-2.5-image-to-video.json b/router-schemas/pika/pika-2.5-image-to-video.json
new file mode 100644
index 0000000000..1c52321363
--- /dev/null
+++ b/router-schemas/pika/pika-2.5-image-to-video.json
@@ -0,0 +1 @@
+{"openapi":"3.0.2","info":{"title":"pika/pika-2.5-image-to-video","description":"The request body Comfy Router accepts for the model \"pika/pika-2.5-image-to-video\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"13d6b79e4907"},"paths":{"/v2/models/pika/pika-2.5-image-to-video":{"post":{"operationId":"runRouterModel","summary":"Run pika/pika-2.5-image-to-video synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"additionalProperties":false,"description":"Request body for Pika 2.5 image-to-video: animate a source image, with an optional prompt guiding the motion. Comfy Router forwards this body to Pika's `pika/pika-2.5/image-to-video` endpoint unchanged and polls the job on the caller's behalf, so the response is the finished job document. A body outside the bounds declared here is refused with `422 invalid_input` before any provider is dialled.","example":{"duration_s":5,"image":"https://example.invalid/pika/source.png","prompt":"The camera slowly pushes in as the leaves begin to sway","resolution":"720p"},"properties":{"duration_s":{"default":5,"description":"Video length in seconds.","enum":[5,10],"type":"integer"},"image":{"description":"Public https URL of the source image to animate. The image carries the subject. Plain `http://` is refused: Pika fetches the URL on a request sent under Comfy's key.","pattern":"^https://","type":"string"},"negative_prompt":{"description":"Text describing what to avoid in the video.","nullable":true,"type":"string"},"prompt":{"description":"Text prompt guiding the motion.","nullable":true,"type":"string"},"resolution":{"default":"1080p","description":"Output resolution.","enum":["720p","1080p"],"type":"string"},"seed":{"description":"Seed for reproducible generation. Omit it (or send null) for a random seed.","nullable":true,"type":"integer"}},"required":["image"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"description":"Pika's job document, the response for every Pika model Comfy Router serves. All of them are SUBMIT-AND-POLL: Router polls Pika's job status route on the caller's behalf, so the body a caller receives is the TERMINAL document rather than the queued handle the submit answers with. A caller therefore reads `status: \"completed\"` with the result under `output` — `output.images[].url` for an image model, `output.video.url` for a video model, or `output.videos[].url` when Pika delivers the video job as a list (`media_type: videos`), which Router treats as the same success. A job Pika fails, or one that completes without its media, does NOT come back as this document: Router answers it with its own error response instead. `queued`, `running` and `failed` are states of Pika's job that Router does not return in this body. Pika's own `billing` record is removed before the document reaches the caller; the credits a call consumed are reported on the response's `X-Comfy-Credits-Used` header.","example":{"id":"2d8e4b71-9c05-4f3a-b6d2-81e7a5c09f44","model_id":"pika/pika-2.5/image-to-video","output":{"media_type":"video","seed":42,"video":{"content_type":"video/mp4","height":720,"url":"https://example.invalid/pika/pika-2.5-image-to-video/generated.mp4","width":1280}},"status":"completed"},"properties":{"error":{"$ref":"#/components/schemas/PikaRouterJobError"},"id":{"description":"Pika's job id.","type":"string"},"model_id":{"description":"The Pika catalog entry the job ran.","nullable":true,"type":"string"},"output":{"$ref":"#/components/schemas/PikaRouterJobOutput"},"runtime_s":{"description":"Seconds the generation took, from dispatch to terminal. Best-effort; may be null.","nullable":true,"type":"number"},"status":{"description":"Lifecycle state. A body Router returns always reads `completed`; a `failed` job is answered with Router's error response instead.","enum":["queued","running","completed","failed"],"type":"string"},"usage":{"additionalProperties":true,"description":"Provider-reported units for the job. Keys are sparse; only the units the job reports are present.","nullable":true,"type":"object"}},"required":["id","status"],"type":"object"}}}}}}}},"components":{"schemas":{"PikaRouterJobError":{"description":"Pika's structured failure record, set on a `failed` job. Router answers a failed job with its own error response, so a body Router returns does not carry it; it is declared because it is part of Pika's job document.","nullable":true,"properties":{"code":{"enum":["invalid_input","content_moderation","provider_error","provider_timeout","provider_unavailable","rate_limited","insufficient_balance","membership_required","cycle_limit_exceeded","admission_suspended","usage_limit_exceeded","timed_out","internal"],"type":"string"},"message":{"type":"string"}},"required":["code","message"],"type":"object"},"PikaRouterJobOutput":{"description":"The finished job's media, set when `status` is `completed`. `media_type` says which field carries it: `image` fills `images`, `video` fills `video`, and `videos` fills `videos`.","nullable":true,"properties":{"images":{"description":"Generated images, for an image model.","items":{"$ref":"#/components/schemas/PikaRouterMediaAsset"},"type":"array"},"media_type":{"enum":["image","video","videos"],"type":"string"},"seed":{"description":"The seed the video job actually used, when reported.","nullable":true,"type":"integer"},"video":{"$ref":"#/components/schemas/PikaRouterMediaAsset"},"videos":{"description":"Generated videos, for a multi-video delivery.","items":{"$ref":"#/components/schemas/PikaRouterMediaAsset"},"type":"array"}},"type":"object"},"PikaRouterMediaAsset":{"description":"One generated media file.","properties":{"content_type":{"description":"MIME type of the file.","nullable":true,"type":"string"},"height":{"description":"Height in pixels.","nullable":true,"type":"integer"},"url":{"description":"Download URL of the generated file.","type":"string"},"width":{"description":"Width in pixels.","nullable":true,"type":"integer"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"pika/pika-2.5-image-to-video","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image"],"x-comfy-router-output-modalities":["video"]}
diff --git a/router-schemas/pika/pika-2.5-text-to-video.json b/router-schemas/pika/pika-2.5-text-to-video.json
new file mode 100644
index 0000000000..99f2939860
--- /dev/null
+++ b/router-schemas/pika/pika-2.5-text-to-video.json
@@ -0,0 +1 @@
+{"openapi":"3.0.2","info":{"title":"pika/pika-2.5-text-to-video","description":"The request body Comfy Router accepts for the model \"pika/pika-2.5-text-to-video\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"5a363a027e4f"},"paths":{"/v2/models/pika/pika-2.5-text-to-video":{"post":{"operationId":"runRouterModel","summary":"Run pika/pika-2.5-text-to-video synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"additionalProperties":false,"description":"Request body for Pika 2.5 text-to-video: generate a five-second video from a text prompt. Comfy Router forwards this body to Pika's `pika/pika-2.5/text-to-video` endpoint unchanged and polls the job on the caller's behalf, so the response is the finished job document. A body outside the bounds declared here is refused with `422 invalid_input` before any provider is dialled.","example":{"duration_s":5,"prompt":"A paper boat drifting down a rain-soaked city street at dusk","resolution":"720p"},"properties":{"duration_s":{"default":5,"description":"Video length in seconds. Text-to-video produces five-second clips only.","enum":[5],"type":"integer"},"negative_prompt":{"description":"Text describing what to avoid in the video.","nullable":true,"type":"string"},"prompt":{"description":"Text prompt describing the video to generate.","minLength":1,"type":"string"},"resolution":{"default":"1080p","description":"Output resolution.","enum":["720p","1080p"],"type":"string"},"seed":{"description":"Seed for reproducible generation. Omit it (or send null) for a random seed.","nullable":true,"type":"integer"}},"required":["prompt"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"description":"Pika's job document, the response for every Pika model Comfy Router serves. All of them are SUBMIT-AND-POLL: Router polls Pika's job status route on the caller's behalf, so the body a caller receives is the TERMINAL document rather than the queued handle the submit answers with. A caller therefore reads `status: \"completed\"` with the result under `output` — `output.images[].url` for an image model, `output.video.url` for a video model, or `output.videos[].url` when Pika delivers the video job as a list (`media_type: videos`), which Router treats as the same success. A job Pika fails, or one that completes without its media, does NOT come back as this document: Router answers it with its own error response instead. `queued`, `running` and `failed` are states of Pika's job that Router does not return in this body. Pika's own `billing` record is removed before the document reaches the caller; the credits a call consumed are reported on the response's `X-Comfy-Credits-Used` header.","example":{"id":"6f1c2a9e-3b47-4d8a-9e21-7c5b0f4d8a13","model_id":"pika/pika-2.5/text-to-video","output":{"media_type":"video","seed":42,"video":{"content_type":"video/mp4","height":720,"url":"https://example.invalid/pika/pika-2.5-text-to-video/generated.mp4","width":1280}},"status":"completed"},"properties":{"error":{"$ref":"#/components/schemas/PikaRouterJobError"},"id":{"description":"Pika's job id.","type":"string"},"model_id":{"description":"The Pika catalog entry the job ran.","nullable":true,"type":"string"},"output":{"$ref":"#/components/schemas/PikaRouterJobOutput"},"runtime_s":{"description":"Seconds the generation took, from dispatch to terminal. Best-effort; may be null.","nullable":true,"type":"number"},"status":{"description":"Lifecycle state. A body Router returns always reads `completed`; a `failed` job is answered with Router's error response instead.","enum":["queued","running","completed","failed"],"type":"string"},"usage":{"additionalProperties":true,"description":"Provider-reported units for the job. Keys are sparse; only the units the job reports are present.","nullable":true,"type":"object"}},"required":["id","status"],"type":"object"}}}}}}}},"components":{"schemas":{"PikaRouterJobError":{"description":"Pika's structured failure record, set on a `failed` job. Router answers a failed job with its own error response, so a body Router returns does not carry it; it is declared because it is part of Pika's job document.","nullable":true,"properties":{"code":{"enum":["invalid_input","content_moderation","provider_error","provider_timeout","provider_unavailable","rate_limited","insufficient_balance","membership_required","cycle_limit_exceeded","admission_suspended","usage_limit_exceeded","timed_out","internal"],"type":"string"},"message":{"type":"string"}},"required":["code","message"],"type":"object"},"PikaRouterJobOutput":{"description":"The finished job's media, set when `status` is `completed`. `media_type` says which field carries it: `image` fills `images`, `video` fills `video`, and `videos` fills `videos`.","nullable":true,"properties":{"images":{"description":"Generated images, for an image model.","items":{"$ref":"#/components/schemas/PikaRouterMediaAsset"},"type":"array"},"media_type":{"enum":["image","video","videos"],"type":"string"},"seed":{"description":"The seed the video job actually used, when reported.","nullable":true,"type":"integer"},"video":{"$ref":"#/components/schemas/PikaRouterMediaAsset"},"videos":{"description":"Generated videos, for a multi-video delivery.","items":{"$ref":"#/components/schemas/PikaRouterMediaAsset"},"type":"array"}},"type":"object"},"PikaRouterMediaAsset":{"description":"One generated media file.","properties":{"content_type":{"description":"MIME type of the file.","nullable":true,"type":"string"},"height":{"description":"Height in pixels.","nullable":true,"type":"integer"},"url":{"description":"Download URL of the generated file.","type":"string"},"width":{"description":"Width in pixels.","nullable":true,"type":"integer"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"pika/pika-2.5-text-to-video","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text"],"x-comfy-router-output-modalities":["video"]}
diff --git a/router-schemas/pika/pika-gpt-image-2.json b/router-schemas/pika/pika-gpt-image-2.json
new file mode 100644
index 0000000000..896c76d956
--- /dev/null
+++ b/router-schemas/pika/pika-gpt-image-2.json
@@ -0,0 +1 @@
+{"openapi":"3.0.2","info":{"title":"pika/pika-gpt-image-2","description":"The request body Comfy Router accepts for the model \"pika/pika-gpt-image-2\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"de2e236ae5f2"},"paths":{"/v2/models/pika/pika-gpt-image-2":{"post":{"operationId":"runRouterModel","summary":"Run pika/pika-gpt-image-2 synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"additionalProperties":false,"description":"Request body for GPT Image 2 text-to-image served by Pika. Comfy Router forwards this body to Pika's `openai/gpt-image-2/text-to-image` endpoint unchanged and polls the job on the caller's behalf, so the response is the finished job document. This route is text-to-image only: it takes no input image or mask.\nThis id has a SECOND route, which does not take this body: `openai/gpt-image-2?model_provider=pika` takes `openai/gpt-image-2`'s own OpenAI-shaped body (`n` rather than `num_images`), is validated against that model's schema, and is translated onto this shape before dispatch. Build a Pika body for `pika/pika-gpt-image-2` and an OpenAI body for `openai/gpt-image-2?model_provider=pika`; they are not interchangeable.","example":{"num_images":1,"output_format":"png","prompt":"A watercolor fox curled up in fresh snow","quality":"medium","size":"1024x1024"},"properties":{"aspect_ratio":{"default":"1:1","description":"Output aspect ratio. Ignored when `size` is set.","enum":["1:1","2:3","3:2","3:4","4:3","4:5","5:4","9:16","16:9","21:9"],"type":"string"},"background":{"description":"Background of the output. `transparent` needs a `png` or `webp` output_format.","enum":["auto","opaque","transparent"],"type":"string"},"num_images":{"default":1,"description":"Number of images to generate.","maximum":10,"minimum":1,"type":"integer"},"output_format":{"default":"png","description":"Image file format of the output.","enum":["png","jpeg","webp"],"type":"string"},"prompt":{"description":"Text prompt describing the image to generate.","minLength":1,"type":"string"},"quality":{"default":"medium","description":"Rendering quality; `auto` lets the provider choose.","enum":["auto","low","medium","high"],"type":"string"},"resolution":{"default":"1K","description":"Output resolution. Ignored when `size` is set.","enum":["1K","2K","4K"],"type":"string"},"size":{"description":"Explicit output size as `WIDTHxHEIGHT` (for example `1024x1024`), or `auto` to let the provider choose. Overrides `aspect_ratio` and `resolution`.","nullable":true,"pattern":"^\\d+x\\d+$|^auto$","type":"string"}},"required":["prompt"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"description":"Pika's job document, the response for every Pika model Comfy Router serves. All of them are SUBMIT-AND-POLL: Router polls Pika's job status route on the caller's behalf, so the body a caller receives is the TERMINAL document rather than the queued handle the submit answers with. A caller therefore reads `status: \"completed\"` with the result under `output` — `output.images[].url` for an image model, `output.video.url` for a video model, or `output.videos[].url` when Pika delivers the video job as a list (`media_type: videos`), which Router treats as the same success. A job Pika fails, or one that completes without its media, does NOT come back as this document: Router answers it with its own error response instead. `queued`, `running` and `failed` are states of Pika's job that Router does not return in this body. Pika's own `billing` record is removed before the document reaches the caller; the credits a call consumed are reported on the response's `X-Comfy-Credits-Used` header.","example":{"id":"9a3f7c12-5e84-4b09-a1d6-3c2e8f7b5d90","model_id":"openai/gpt-image-2/text-to-image","output":{"images":[{"content_type":"image/png","height":1024,"url":"https://example.invalid/pika/pika-gpt-image-2/generated.png","width":1024}],"media_type":"image"},"status":"completed"},"properties":{"error":{"$ref":"#/components/schemas/PikaRouterJobError"},"id":{"description":"Pika's job id.","type":"string"},"model_id":{"description":"The Pika catalog entry the job ran.","nullable":true,"type":"string"},"output":{"$ref":"#/components/schemas/PikaRouterJobOutput"},"runtime_s":{"description":"Seconds the generation took, from dispatch to terminal. Best-effort; may be null.","nullable":true,"type":"number"},"status":{"description":"Lifecycle state. A body Router returns always reads `completed`; a `failed` job is answered with Router's error response instead.","enum":["queued","running","completed","failed"],"type":"string"},"usage":{"additionalProperties":true,"description":"Provider-reported units for the job. Keys are sparse; only the units the job reports are present.","nullable":true,"type":"object"}},"required":["id","status"],"type":"object"}}}}}}}},"components":{"schemas":{"PikaRouterJobError":{"description":"Pika's structured failure record, set on a `failed` job. Router answers a failed job with its own error response, so a body Router returns does not carry it; it is declared because it is part of Pika's job document.","nullable":true,"properties":{"code":{"enum":["invalid_input","content_moderation","provider_error","provider_timeout","provider_unavailable","rate_limited","insufficient_balance","membership_required","cycle_limit_exceeded","admission_suspended","usage_limit_exceeded","timed_out","internal"],"type":"string"},"message":{"type":"string"}},"required":["code","message"],"type":"object"},"PikaRouterJobOutput":{"description":"The finished job's media, set when `status` is `completed`. `media_type` says which field carries it: `image` fills `images`, `video` fills `video`, and `videos` fills `videos`.","nullable":true,"properties":{"images":{"description":"Generated images, for an image model.","items":{"$ref":"#/components/schemas/PikaRouterMediaAsset"},"type":"array"},"media_type":{"enum":["image","video","videos"],"type":"string"},"seed":{"description":"The seed the video job actually used, when reported.","nullable":true,"type":"integer"},"video":{"$ref":"#/components/schemas/PikaRouterMediaAsset"},"videos":{"description":"Generated videos, for a multi-video delivery.","items":{"$ref":"#/components/schemas/PikaRouterMediaAsset"},"type":"array"}},"type":"object"},"PikaRouterMediaAsset":{"description":"One generated media file.","properties":{"content_type":{"description":"MIME type of the file.","nullable":true,"type":"string"},"height":{"description":"Height in pixels.","nullable":true,"type":"integer"},"url":{"description":"Download URL of the generated file.","type":"string"},"width":{"description":"Width in pixels.","nullable":true,"type":"integer"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"pika/pika-gpt-image-2","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text"],"x-comfy-router-output-modalities":["image"],"x-comfy-router-alias-of":"openai/gpt-image-2","x-comfy-router-alias-provider":"pika"}
diff --git a/router-schemas/pika/pika-nano-banana-2.json b/router-schemas/pika/pika-nano-banana-2.json
new file mode 100644
index 0000000000..ed10c12dc8
--- /dev/null
+++ b/router-schemas/pika/pika-nano-banana-2.json
@@ -0,0 +1 @@
+{"openapi":"3.0.2","info":{"title":"pika/pika-nano-banana-2","description":"The request body Comfy Router accepts for the model \"pika/pika-nano-banana-2\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"da3fde510441"},"paths":{"/v2/models/pika/pika-nano-banana-2":{"post":{"operationId":"runRouterModel","summary":"Run pika/pika-nano-banana-2 synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"additionalProperties":false,"description":"Request body for Gemini 3.1 Flash Image (Nano Banana 2) text-to-image served by Pika. Comfy Router forwards this body to Pika's `google/gemini-3.1-flash-image/text-to-image` endpoint unchanged and polls the job on the caller's behalf, so the response is the finished job document. This route is text-to-image only: it takes no input image.\nThis id has a SECOND route, which does not take this body: `vertexai/gemini-3.1-flash-image?model_provider=pika` takes the native Gemini `generateContent` body, is validated against that model's schema, and is translated onto this shape before dispatch. The two bodies are not interchangeable.","example":{"aspect_ratio":"1:1","prompt":"A watercolor fox curled up in fresh snow","resolution":"1K","thinking_level":"minimal"},"properties":{"aspect_ratio":{"default":"1:1","description":"Output aspect ratio.","enum":["1:1","2:3","3:2","3:4","4:3","4:5","5:4","9:16","16:9","21:9","auto"],"type":"string"},"num_images":{"default":1,"description":"Number of images to generate. Pika generates exactly one per request for this model.","maximum":1,"minimum":1,"type":"integer"},"output_format":{"default":"png","description":"Image file format of the output.","enum":["png","jpeg","webp"],"type":"string"},"prompt":{"description":"Text prompt describing the image to generate.","minLength":1,"type":"string"},"resolution":{"default":"1K","description":"Output resolution.","enum":["512","1K","2K","4K"],"type":"string"},"thinking_level":{"default":"minimal","description":"How much the model reasons before rendering. `high` helps complex compositions at the cost of latency.","enum":["minimal","high"],"type":"string"}},"required":["prompt"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"description":"Pika's job document, the response for every Pika model Comfy Router serves. All of them are SUBMIT-AND-POLL: Router polls Pika's job status route on the caller's behalf, so the body a caller receives is the TERMINAL document rather than the queued handle the submit answers with. A caller therefore reads `status: \"completed\"` with the result under `output` — `output.images[].url` for an image model, `output.video.url` for a video model, or `output.videos[].url` when Pika delivers the video job as a list (`media_type: videos`), which Router treats as the same success. A job Pika fails, or one that completes without its media, does NOT come back as this document: Router answers it with its own error response instead. `queued`, `running` and `failed` are states of Pika's job that Router does not return in this body. Pika's own `billing` record is removed before the document reaches the caller; the credits a call consumed are reported on the response's `X-Comfy-Credits-Used` header.","example":{"id":"7e2d5a08-1f93-4c6b-9a47-0d8c3e6f2b71","model_id":"google/gemini-3.1-flash-image/text-to-image","output":{"images":[{"content_type":"image/png","height":1024,"url":"https://example.invalid/pika/pika-nano-banana-2/generated.png","width":1024}],"media_type":"image"},"status":"completed"},"properties":{"error":{"$ref":"#/components/schemas/PikaRouterJobError"},"id":{"description":"Pika's job id.","type":"string"},"model_id":{"description":"The Pika catalog entry the job ran.","nullable":true,"type":"string"},"output":{"$ref":"#/components/schemas/PikaRouterJobOutput"},"runtime_s":{"description":"Seconds the generation took, from dispatch to terminal. Best-effort; may be null.","nullable":true,"type":"number"},"status":{"description":"Lifecycle state. A body Router returns always reads `completed`; a `failed` job is answered with Router's error response instead.","enum":["queued","running","completed","failed"],"type":"string"},"usage":{"additionalProperties":true,"description":"Provider-reported units for the job. Keys are sparse; only the units the job reports are present.","nullable":true,"type":"object"}},"required":["id","status"],"type":"object"}}}}}}}},"components":{"schemas":{"PikaRouterJobError":{"description":"Pika's structured failure record, set on a `failed` job. Router answers a failed job with its own error response, so a body Router returns does not carry it; it is declared because it is part of Pika's job document.","nullable":true,"properties":{"code":{"enum":["invalid_input","content_moderation","provider_error","provider_timeout","provider_unavailable","rate_limited","insufficient_balance","membership_required","cycle_limit_exceeded","admission_suspended","usage_limit_exceeded","timed_out","internal"],"type":"string"},"message":{"type":"string"}},"required":["code","message"],"type":"object"},"PikaRouterJobOutput":{"description":"The finished job's media, set when `status` is `completed`. `media_type` says which field carries it: `image` fills `images`, `video` fills `video`, and `videos` fills `videos`.","nullable":true,"properties":{"images":{"description":"Generated images, for an image model.","items":{"$ref":"#/components/schemas/PikaRouterMediaAsset"},"type":"array"},"media_type":{"enum":["image","video","videos"],"type":"string"},"seed":{"description":"The seed the video job actually used, when reported.","nullable":true,"type":"integer"},"video":{"$ref":"#/components/schemas/PikaRouterMediaAsset"},"videos":{"description":"Generated videos, for a multi-video delivery.","items":{"$ref":"#/components/schemas/PikaRouterMediaAsset"},"type":"array"}},"type":"object"},"PikaRouterMediaAsset":{"description":"One generated media file.","properties":{"content_type":{"description":"MIME type of the file.","nullable":true,"type":"string"},"height":{"description":"Height in pixels.","nullable":true,"type":"integer"},"url":{"description":"Download URL of the generated file.","type":"string"},"width":{"description":"Width in pixels.","nullable":true,"type":"integer"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"pika/pika-nano-banana-2","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text"],"x-comfy-router-output-modalities":["image"],"x-comfy-router-alias-of":"vertexai/gemini-3.1-flash-image","x-comfy-router-alias-provider":"pika"}
diff --git a/router-schemas/pika/pika-nano-banana-pro.json b/router-schemas/pika/pika-nano-banana-pro.json
new file mode 100644
index 0000000000..591370433e
--- /dev/null
+++ b/router-schemas/pika/pika-nano-banana-pro.json
@@ -0,0 +1 @@
+{"openapi":"3.0.2","info":{"title":"pika/pika-nano-banana-pro","description":"The request body Comfy Router accepts for the model \"pika/pika-nano-banana-pro\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"87818c010004"},"paths":{"/v2/models/pika/pika-nano-banana-pro":{"post":{"operationId":"runRouterModel","summary":"Run pika/pika-nano-banana-pro synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"additionalProperties":false,"description":"Request body for Gemini 3 Pro Image (Nano Banana Pro) text-to-image served by Pika. Comfy Router forwards this body to Pika's `google/gemini-3-pro-image/text-to-image` endpoint unchanged and polls the job on the caller's behalf, so the response is the finished job document. This route is text-to-image only: it takes no input image.\nThis id has a SECOND route, which does not take this body: `vertexai/gemini-3-pro-image?model_provider=pika` takes the native Gemini `generateContent` body, is validated against that model's schema, and is translated onto this shape before dispatch. The two bodies are not interchangeable.","example":{"aspect_ratio":"16:9","output_format":"png","prompt":"A watercolor fox curled up in fresh snow","resolution":"2K"},"properties":{"aspect_ratio":{"default":"1:1","description":"Output aspect ratio.","enum":["1:1","2:3","3:2","3:4","4:3","4:5","5:4","9:16","16:9","21:9","auto"],"type":"string"},"num_images":{"default":1,"description":"Number of images to generate. Pika generates exactly one per request for this model.","maximum":1,"minimum":1,"type":"integer"},"output_format":{"default":"png","description":"Image file format of the output.","enum":["png","jpeg","webp"],"type":"string"},"prompt":{"description":"Text prompt describing the image to generate.","minLength":1,"type":"string"},"resolution":{"default":"1K","description":"Output resolution.","enum":["1K","2K","4K"],"type":"string"}},"required":["prompt"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"description":"Pika's job document, the response for every Pika model Comfy Router serves. All of them are SUBMIT-AND-POLL: Router polls Pika's job status route on the caller's behalf, so the body a caller receives is the TERMINAL document rather than the queued handle the submit answers with. A caller therefore reads `status: \"completed\"` with the result under `output` — `output.images[].url` for an image model, `output.video.url` for a video model, or `output.videos[].url` when Pika delivers the video job as a list (`media_type: videos`), which Router treats as the same success. A job Pika fails, or one that completes without its media, does NOT come back as this document: Router answers it with its own error response instead. `queued`, `running` and `failed` are states of Pika's job that Router does not return in this body. Pika's own `billing` record is removed before the document reaches the caller; the credits a call consumed are reported on the response's `X-Comfy-Credits-Used` header.","example":{"id":"4c7b1e93-0a26-4d58-8f3e-6b9d2c1a7e05","model_id":"google/gemini-3-pro-image/text-to-image","output":{"images":[{"content_type":"image/png","height":1152,"url":"https://example.invalid/pika/pika-nano-banana-pro/generated.png","width":2048}],"media_type":"image"},"status":"completed"},"properties":{"error":{"$ref":"#/components/schemas/PikaRouterJobError"},"id":{"description":"Pika's job id.","type":"string"},"model_id":{"description":"The Pika catalog entry the job ran.","nullable":true,"type":"string"},"output":{"$ref":"#/components/schemas/PikaRouterJobOutput"},"runtime_s":{"description":"Seconds the generation took, from dispatch to terminal. Best-effort; may be null.","nullable":true,"type":"number"},"status":{"description":"Lifecycle state. A body Router returns always reads `completed`; a `failed` job is answered with Router's error response instead.","enum":["queued","running","completed","failed"],"type":"string"},"usage":{"additionalProperties":true,"description":"Provider-reported units for the job. Keys are sparse; only the units the job reports are present.","nullable":true,"type":"object"}},"required":["id","status"],"type":"object"}}}}}}}},"components":{"schemas":{"PikaRouterJobError":{"description":"Pika's structured failure record, set on a `failed` job. Router answers a failed job with its own error response, so a body Router returns does not carry it; it is declared because it is part of Pika's job document.","nullable":true,"properties":{"code":{"enum":["invalid_input","content_moderation","provider_error","provider_timeout","provider_unavailable","rate_limited","insufficient_balance","membership_required","cycle_limit_exceeded","admission_suspended","usage_limit_exceeded","timed_out","internal"],"type":"string"},"message":{"type":"string"}},"required":["code","message"],"type":"object"},"PikaRouterJobOutput":{"description":"The finished job's media, set when `status` is `completed`. `media_type` says which field carries it: `image` fills `images`, `video` fills `video`, and `videos` fills `videos`.","nullable":true,"properties":{"images":{"description":"Generated images, for an image model.","items":{"$ref":"#/components/schemas/PikaRouterMediaAsset"},"type":"array"},"media_type":{"enum":["image","video","videos"],"type":"string"},"seed":{"description":"The seed the video job actually used, when reported.","nullable":true,"type":"integer"},"video":{"$ref":"#/components/schemas/PikaRouterMediaAsset"},"videos":{"description":"Generated videos, for a multi-video delivery.","items":{"$ref":"#/components/schemas/PikaRouterMediaAsset"},"type":"array"}},"type":"object"},"PikaRouterMediaAsset":{"description":"One generated media file.","properties":{"content_type":{"description":"MIME type of the file.","nullable":true,"type":"string"},"height":{"description":"Height in pixels.","nullable":true,"type":"integer"},"url":{"description":"Download URL of the generated file.","type":"string"},"width":{"description":"Width in pixels.","nullable":true,"type":"integer"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"pika/pika-nano-banana-pro","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text"],"x-comfy-router-output-modalities":["image"],"x-comfy-router-alias-of":"vertexai/gemini-3-pro-image","x-comfy-router-alias-provider":"pika"}
diff --git a/router-schemas/pika/pika-seedance-2.0.json b/router-schemas/pika/pika-seedance-2.0.json
new file mode 100644
index 0000000000..64f6a452f6
--- /dev/null
+++ b/router-schemas/pika/pika-seedance-2.0.json
@@ -0,0 +1 @@
+{"openapi":"3.0.2","info":{"title":"pika/pika-seedance-2.0","description":"The request body Comfy Router accepts for the model \"pika/pika-seedance-2.0\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"3af94c8a38f1"},"paths":{"/v2/models/pika/pika-seedance-2.0":{"post":{"operationId":"runRouterModel","summary":"Run pika/pika-seedance-2.0 synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"additionalProperties":false,"description":"Request body for Seedance 2.0 text-to-video served by Pika. Comfy Router forwards this body to Pika's `bytedance/seedance-2.0/text-to-video` endpoint unchanged and polls the job on the caller's behalf, so the response is the finished job document. This route is text-to-video only: it takes no image, video or audio reference.\nThis id has a SECOND route, which does not take this body: `byteplus/dreamina-seedance-2-0-260128?model_provider=pika` takes the native BytePlus `content[]` body, is validated against that model's schema, and is translated onto this shape before dispatch. The two bodies are not interchangeable.","example":{"duration":5,"generate_audio":true,"prompt":"A paper boat drifting down a rain-soaked city street at dusk","ratio":"16:9","resolution":"720p"},"properties":{"bitrate_mode":{"default":"standard","description":"Output encode bitrate. `high` produces a larger, higher-bitrate file.","enum":["standard","high"],"type":"string"},"duration":{"description":"Video length in seconds (4-15), or `auto` to let the model choose.","oneOf":[{"maximum":15,"minimum":4,"type":"integer"},{"enum":["auto"],"type":"string"}]},"generate_audio":{"default":true,"description":"Whether to generate audio for the video.","type":"boolean"},"prompt":{"description":"Text prompt describing the video to generate.","maxLength":30000,"minLength":1,"type":"string"},"ratio":{"description":"Output aspect ratio. `adaptive` lets the model choose.","enum":["1:1","4:3","3:4","16:9","9:16","21:9","adaptive"],"type":"string"},"resolution":{"description":"Output resolution.","enum":["480p","720p","1080p","4k"],"type":"string"},"watermark":{"default":false,"description":"Whether to add a watermark to the output video.","type":"boolean"}},"required":["prompt","duration","resolution"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"description":"Pika's job document, the response for every Pika model Comfy Router serves. All of them are SUBMIT-AND-POLL: Router polls Pika's job status route on the caller's behalf, so the body a caller receives is the TERMINAL document rather than the queued handle the submit answers with. A caller therefore reads `status: \"completed\"` with the result under `output` — `output.images[].url` for an image model, `output.video.url` for a video model, or `output.videos[].url` when Pika delivers the video job as a list (`media_type: videos`), which Router treats as the same success. A job Pika fails, or one that completes without its media, does NOT come back as this document: Router answers it with its own error response instead. `queued`, `running` and `failed` are states of Pika's job that Router does not return in this body. Pika's own `billing` record is removed before the document reaches the caller; the credits a call consumed are reported on the response's `X-Comfy-Credits-Used` header.","example":{"id":"b5a91d36-7c28-4e0f-8d13-5f6a4b2e9c87","model_id":"bytedance/seedance-2.0/text-to-video","output":{"media_type":"video","seed":42,"video":{"content_type":"video/mp4","height":720,"url":"https://example.invalid/pika/pika-seedance-2.0/generated.mp4","width":1280}},"status":"completed"},"properties":{"error":{"$ref":"#/components/schemas/PikaRouterJobError"},"id":{"description":"Pika's job id.","type":"string"},"model_id":{"description":"The Pika catalog entry the job ran.","nullable":true,"type":"string"},"output":{"$ref":"#/components/schemas/PikaRouterJobOutput"},"runtime_s":{"description":"Seconds the generation took, from dispatch to terminal. Best-effort; may be null.","nullable":true,"type":"number"},"status":{"description":"Lifecycle state. A body Router returns always reads `completed`; a `failed` job is answered with Router's error response instead.","enum":["queued","running","completed","failed"],"type":"string"},"usage":{"additionalProperties":true,"description":"Provider-reported units for the job. Keys are sparse; only the units the job reports are present.","nullable":true,"type":"object"}},"required":["id","status"],"type":"object"}}}}}}}},"components":{"schemas":{"PikaRouterJobError":{"description":"Pika's structured failure record, set on a `failed` job. Router answers a failed job with its own error response, so a body Router returns does not carry it; it is declared because it is part of Pika's job document.","nullable":true,"properties":{"code":{"enum":["invalid_input","content_moderation","provider_error","provider_timeout","provider_unavailable","rate_limited","insufficient_balance","membership_required","cycle_limit_exceeded","admission_suspended","usage_limit_exceeded","timed_out","internal"],"type":"string"},"message":{"type":"string"}},"required":["code","message"],"type":"object"},"PikaRouterJobOutput":{"description":"The finished job's media, set when `status` is `completed`. `media_type` says which field carries it: `image` fills `images`, `video` fills `video`, and `videos` fills `videos`.","nullable":true,"properties":{"images":{"description":"Generated images, for an image model.","items":{"$ref":"#/components/schemas/PikaRouterMediaAsset"},"type":"array"},"media_type":{"enum":["image","video","videos"],"type":"string"},"seed":{"description":"The seed the video job actually used, when reported.","nullable":true,"type":"integer"},"video":{"$ref":"#/components/schemas/PikaRouterMediaAsset"},"videos":{"description":"Generated videos, for a multi-video delivery.","items":{"$ref":"#/components/schemas/PikaRouterMediaAsset"},"type":"array"}},"type":"object"},"PikaRouterMediaAsset":{"description":"One generated media file.","properties":{"content_type":{"description":"MIME type of the file.","nullable":true,"type":"string"},"height":{"description":"Height in pixels.","nullable":true,"type":"integer"},"url":{"description":"Download URL of the generated file.","type":"string"},"width":{"description":"Width in pixels.","nullable":true,"type":"integer"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"pika/pika-seedance-2.0","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text"],"x-comfy-router-output-modalities":["video","audio"],"x-comfy-router-alias-of":"byteplus/dreamina-seedance-2-0-260128","x-comfy-router-alias-provider":"pika"}
diff --git a/router-schemas/veo/veo-3.1-fast-generate-001.json b/router-schemas/veo/veo-3.1-fast-generate-001.json
index a7ce6a9e6d..589e1eb99e 100644
--- a/router-schemas/veo/veo-3.1-fast-generate-001.json
+++ b/router-schemas/veo/veo-3.1-fast-generate-001.json
@@ -1 +1 @@
-{"openapi":"3.0.2","info":{"title":"veo/veo-3.1-fast-generate-001","description":"The request body Comfy Router accepts for the model \"veo/veo-3.1-fast-generate-001\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"461b74caa277"},"paths":{"/v2/models/veo/veo-3.1-fast-generate-001":{"post":{"operationId":"runRouterModel","summary":"Run veo/veo-3.1-fast-generate-001 synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"example":{"instances":[{"prompt":"a single red maple leaf falling onto still water, slow motion"}],"parameters":{"durationSeconds":4,"generateAudio":false,"sampleCount":1}},"properties":{"instances":{"items":{"properties":{"cameraControl":{"description":"Camera motion type. Requires image to be provided.","enum":["fixed","pan_left","pan_right","tilt_up","tilt_down","truck_left","truck_right","pedestal_up","pedestal_down","push_in","pull_out"],"type":"string"},"image":{"description":"Optional first frame image to guide video generation","oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded image data","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the image. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"mimeType":{"description":"MIME type of the image (image/jpeg or image/png)","enum":["image/jpeg","image/png"],"type":"string"}},"type":"object"},"lastFrame":{"description":"Optional last frame image. Used with image to generate video between first and last frames. Supported by Veo 3.0+ models.","oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded image data","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the image. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"mimeType":{"description":"MIME type of the image (image/jpeg or image/png)","enum":["image/jpeg","image/png"],"type":"string"}},"type":"object"},"mask":{"description":"Optional mask for video editing. Applies to input video.","oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded mask bytes","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI to mask file. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"maskMode":{"description":"How the mask is applied","enum":["insert","remove","remove_static","outpaint"],"type":"string"},"mimeType":{"description":"MIME type of the mask (image/png, image/jpeg, image/webp, or video formats)","type":"string"}},"type":"object"},"prompt":{"description":"Text description of the video to generate","type":"string"},"referenceImages":{"description":"Optional reference images to guide video generation. Supports up to 3 asset images or 1 style image. Supported by Veo 3.1 models (preview).","items":{"properties":{"image":{"oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded image data","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the image. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"mimeType":{"description":"MIME type of the image (image/jpeg or image/png)","enum":["image/jpeg","image/png"],"type":"string"}},"type":"object"},"referenceId":{"description":"Optional identifier for the reference image","type":"string"},"referenceType":{"description":"Type of reference image","enum":["asset","style"],"type":"string"}},"required":["image","referenceType"],"type":"object"},"type":"array"},"video":{"description":"Optional input video for video extension or editing. Incompatible with image and referenceImages.","oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded video bytes","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the input video. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"mimeType":{"description":"MIME type of the video","enum":["video/mov","video/mpeg","video/mp4","video/mpg","video/avi","video/wmv","video/mpegps","video/x-flv"],"type":"string"}},"type":"object"}},"required":["prompt"],"type":"object"},"type":"array"},"parameters":{"properties":{"aspectRatio":{"description":"Aspect ratio of the generated video. Default: 16:9","enum":["16:9","9:16"],"example":"16:9","type":"string"},"compressionQuality":{"description":"Video compression quality. Default: optimized","enum":["optimized","lossless"],"type":"string"},"durationSeconds":{"description":"Target duration of the generated video in seconds. Veo 2: 5-8. Veo 3/3.1: 4, 6, or 8. Default: 8","type":"number"},"enhancePrompt":{"description":"Automatically improve prompt for higher quality. Defaults to true.","type":"boolean"},"fps":{"description":"Frame rate of generated videos in frames per second","type":"integer"},"generateAudio":{"description":"Whether to generate audio along with the video. Defaults to true. Supported by Veo 3.0+ models.","type":"boolean"},"negativePrompt":{"description":"Text describing what to avoid in the generated video","type":"string"},"personGeneration":{"description":"Controls people in generated videos. Default: allow_adult","enum":["dont_allow","allow_adult","allowAll"],"type":"string"},"pubsubTopic":{"description":"Cloud Pub/Sub topic for progress updates (projects/{project}/topics/{topic})","type":"string"},"resizeMode":{"description":"Resize approach for input image. Default: pad","enum":["pad","crop"],"type":"string"},"resolution":{"description":"Output video resolution. Supported by Veo 3.0+ models. Default: 720p","enum":["720p","1080p","4k"],"type":"string"},"sampleCount":{"description":"Number of videos to generate. If not specified, 1 video is generated.","maximum":4,"minimum":1,"type":"integer"},"seed":{"description":"Random seed for deterministic output. Different seeds used per video if sampleCount \u003e 1.","format":"uint32","type":"integer"},"storageUri":{"description":"Optional Cloud Storage destination for the generated videos, as a `gs://` URI.\nIT MUST NAME A BUCKET OF YOUR OWN. Outside bring-your-own-key, this route runs under Comfy's own Google Cloud credentials rather than yours, so a destination inside a Comfy-owned bucket would be a write performed by Comfy's service account, into a shared layout, billed to Comfy's project. A request naming one is refused with a 400 and nothing is generated — with one exception on a synchronous Comfy Router call (`POST /v2/models/{provider}/{model}`) where platform storage is enabled: a value naming Router's own Veo or Gemini asset bucket is replaced with a destination Router mints under your own platform prefix for that call (see the Router Veo route), and only that exact Router-minted destination is let through. A queued Router submit (`POST /v2/models/{provider}/{model}/requests`) makes no such replacement, so it refuses that value with a 400 like any other Comfy-owned bucket. Any other destination is forwarded to Vertex unchanged and is yours to grant access to. A bring-your-own-key call runs in your own project and is forwarded as sent.","type":"string"},"task":{"description":"Operation type for the video generation request","enum":["textToVideo","imageToVideo","referenceToVideo","edit","extend","upscale"],"type":"string"}},"type":"object"}},"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/VeoGenVidPollResponse"}],"description":"Comfy Router output schema for the Veo models: the terminal Vertex AI long-running operation document, forwarded unchanged. Veo is submit-and-poll, and Router polls on the caller's behalf, so the body a caller receives is the finished operation (`done: true`) rather than the operation handle the submit returns.\nThe generated videos are in `response.videos`. Each carries either `gcsUri` or `bytesBase64Encoded` (the video inline), and `mimeType` names the container. `response.raiMediaFilteredCount` and `response.raiMediaFilteredReasons` report videos Vertex's responsible-AI filters removed, so a `done: true` operation can legitimately carry fewer videos than were requested — or none at all.\nWHICH OF THE TWO YOU GET depends on the environment, and `gcsUri` is the one field Router does not forward unchanged. TREAT BOTH MEMBERS AS POSSIBLE AND READ WHICHEVER IS PRESENT. Where platform storage is enabled for `veo/*`, Router asks Vertex to write the clips to Cloud Storage rather than inline them and then replaces each `gcsUri` it owns with a SIGNED HTTPS URL you can fetch directly, valid for 24 hours; everything else in the document is Vertex's own. Where it is not enabled, Vertex inlines the clips as `bytesBase64Encoded` and the whole document is forwarded unchanged. Two further cases are always answered with the partner's own value: a request that sets `parameters.storageUri` to a bucket of its own keeps that bucket and its `gs://` URIs, untouched, and a bring-your-own-key call runs entirely in your project and is likewise left alone. A `storageUri` naming a COMFY-OWNED bucket is never honoured. Where platform storage is enabled, a value naming Router's own Veo or Gemini asset bucket is replaced on a synchronous call with a destination Router mints under your own platform prefix for that call, and only that exact Router-minted destination passes the guard; a queued submit (`/requests`) makes no such replacement and refuses it with a 400. A value naming any other Comfy-owned bucket — or any Comfy-owned bucket where platform storage is not enabled — is refused with a 400 and nothing is generated. See `VeoGenVidRequest.parameters.storageUri`.\nThe example's `gcsUri` is a placeholder, with the signature query the real answer carries deliberately omitted: a signed URL published in a spec is a leaked credential and a dead link at once.\nEVERY MEMBER IS CLASSIFIED AND RUNS TODAY. The three veo ids that were not — `veo/veo-2.0-generate-001`, `veo/veo-3.0-generate-001` and `veo/veo-3.0-fast-generate-001` — have been retired and have left both this list and the Router catalog; a call naming one is refused 404 model_not_found before dispatch, with a `detail` naming its successor.","example":{"done":true,"name":"projects/example-project/locations/us-central1/publishers/google/models/veo-3.1-fast-generate-001/operations/1a2b3c4d","response":{"@type":"type.googleapis.com/cloud.ai.large_models.vision.GenerateVideoResponse","raiMediaFilteredCount":0,"videos":[{"gcsUri":"https://storage.googleapis.com/EXAMPLE_BUCKET/veo/USER_ID/REQUEST_ID/sample_0.mp4","mimeType":"video/mp4"}]}}}}}}}}}},"components":{"schemas":{"VeoGenVidPollResponse":{"description":"Response from polling a Veo video generation operation","properties":{"done":{"description":"Whether the operation has completed","type":"boolean"},"error":{"description":"Error details, present if the operation failed","properties":{"code":{"description":"gRPC error code","type":"integer"},"message":{"description":"Error message","type":"string"}},"type":"object"},"name":{"description":"Operation resource name","type":"string"},"response":{"description":"The prediction response, present when done is true","properties":{"@type":{"example":"type.googleapis.com/cloud.ai.large_models.vision.GenerateVideoResponse","type":"string"},"raiMediaFilteredCount":{"description":"Number of videos filtered by responsible AI policies","type":"integer"},"raiMediaFilteredReasons":{"description":"Reasons why videos were filtered by responsible AI policies","items":{"type":"string"},"type":"array"},"videos":{"items":{"properties":{"bytesBase64Encoded":{"description":"Base64-encoded video content","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the generated video","type":"string"},"mimeType":{"description":"Video MIME type (video/mp4)","type":"string"}},"type":"object"},"type":"array"}},"type":"object"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"veo/veo-3.1-fast-generate-001","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image","video"],"x-comfy-router-output-modalities":["video","audio"]}
+{"openapi":"3.0.2","info":{"title":"veo/veo-3.1-fast-generate-001","description":"The request body Comfy Router accepts for the model \"veo/veo-3.1-fast-generate-001\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"56727421d95b"},"paths":{"/v2/models/veo/veo-3.1-fast-generate-001":{"post":{"operationId":"runRouterModel","summary":"Run veo/veo-3.1-fast-generate-001 synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"example":{"instances":[{"prompt":"a single red maple leaf falling onto still water, slow motion"}],"parameters":{"durationSeconds":4,"generateAudio":false,"sampleCount":1}},"properties":{"instances":{"items":{"properties":{"cameraControl":{"description":"Camera motion type. Requires image to be provided.","enum":["fixed","pan_left","pan_right","tilt_up","tilt_down","truck_left","truck_right","pedestal_up","pedestal_down","push_in","pull_out"],"type":"string"},"image":{"description":"Optional first frame image to guide video generation","oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded image data","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the image. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"mimeType":{"description":"MIME type of the image (image/jpeg or image/png)","enum":["image/jpeg","image/png"],"type":"string"}},"type":"object"},"lastFrame":{"description":"Optional last frame image. Used with image to generate video between first and last frames. Supported by Veo 3.0+ models.","oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded image data","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the image. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"mimeType":{"description":"MIME type of the image (image/jpeg or image/png)","enum":["image/jpeg","image/png"],"type":"string"}},"type":"object"},"mask":{"description":"Optional mask for video editing. Applies to input video.","oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded mask bytes","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI to mask file. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"maskMode":{"description":"How the mask is applied","enum":["insert","remove","remove_static","outpaint"],"type":"string"},"mimeType":{"description":"MIME type of the mask (image/png, image/jpeg, image/webp, or video formats)","type":"string"}},"type":"object"},"prompt":{"description":"Text description of the video to generate","type":"string"},"referenceImages":{"description":"Optional reference images to guide video generation. Supports up to 3 asset images or 1 style image. Supported by Veo 3.1 models (preview).","items":{"properties":{"image":{"oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded image data","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the image. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"mimeType":{"description":"MIME type of the image (image/jpeg or image/png)","enum":["image/jpeg","image/png"],"type":"string"}},"type":"object"},"referenceId":{"description":"Optional identifier for the reference image","type":"string"},"referenceType":{"description":"Type of reference image","enum":["asset","style"],"type":"string"}},"required":["image","referenceType"],"type":"object"},"type":"array"},"video":{"description":"Optional input video for video extension or editing. Incompatible with image and referenceImages.","oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded video bytes","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the input video. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"mimeType":{"description":"MIME type of the video","enum":["video/mov","video/mpeg","video/mp4","video/mpg","video/avi","video/wmv","video/mpegps","video/x-flv"],"type":"string"}},"type":"object"}},"required":["prompt"],"type":"object"},"type":"array"},"parameters":{"properties":{"aspectRatio":{"description":"Aspect ratio of the generated video. Default: 16:9","enum":["16:9","9:16"],"example":"16:9","type":"string"},"compressionQuality":{"description":"Video compression quality. Default: optimized","enum":["optimized","lossless"],"type":"string"},"durationSeconds":{"description":"Target duration of the generated video in seconds. Veo 2: 5-8. Veo 3/3.1: 4, 6, or 8. Default: 8","type":"number"},"enhancePrompt":{"description":"Automatically improve prompt for higher quality. Defaults to true.","type":"boolean"},"fps":{"description":"Frame rate of generated videos in frames per second","type":"integer"},"generateAudio":{"description":"Whether to generate audio along with the video. Defaults to true. Supported by Veo 3.0+ models.","type":"boolean"},"negativePrompt":{"description":"Text describing what to avoid in the generated video","type":"string"},"personGeneration":{"description":"Controls people in generated videos. Default: allow_adult","enum":["dont_allow","allow_adult","allowAll"],"type":"string"},"pubsubTopic":{"description":"Cloud Pub/Sub topic for progress updates (projects/{project}/topics/{topic}). Not accepted on Comfy-credentialed (non-BYOK) requests: the generate route answers 400 if it is present, because the operation runs in Comfy's project and Vertex would publish as Comfy's service agent. Accepted only on bring-your-own-key requests, where Vertex evaluates the topic grant in your own project.","type":"string"},"resizeMode":{"description":"Resize approach for input image. Default: pad","enum":["pad","crop"],"type":"string"},"resolution":{"description":"Output video resolution. Supported by Veo 3.0+ models. Default: 720p","enum":["720p","1080p","4k"],"type":"string"},"sampleCount":{"description":"Number of videos to generate. If not specified, 1 video is generated.","maximum":4,"minimum":1,"type":"integer"},"seed":{"description":"Random seed for deterministic output. Different seeds used per video if sampleCount \u003e 1.","format":"uint32","type":"integer"},"storageUri":{"description":"Optional Cloud Storage destination for the generated videos, as a `gs://` URI.\nIT MUST NAME A BUCKET OF YOUR OWN. Outside bring-your-own-key, this route runs under Comfy's own Google Cloud credentials rather than yours, so a destination inside a Comfy-owned bucket would be a write performed by Comfy's service account, into a shared layout, billed to Comfy's project. A request naming one is refused with a 400 and nothing is generated — with one exception on a synchronous Comfy Router call (`POST /v2/models/{provider}/{model}`) where platform storage is enabled: a value naming Router's own Veo or Gemini asset bucket is replaced with a destination Router mints under your own platform prefix for that call (see the Router Veo route), and only that exact Router-minted destination is let through. A queued Router submit (`POST /v2/models/{provider}/{model}/requests`) makes no such replacement, so it refuses that value with a 400 like any other Comfy-owned bucket. Any other destination is forwarded to Vertex unchanged and is yours to grant access to. A bring-your-own-key call runs in your own project and is forwarded as sent.","type":"string"},"task":{"description":"Operation type for the video generation request","enum":["textToVideo","imageToVideo","referenceToVideo","edit","extend","upscale"],"type":"string"}},"type":"object"}},"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/VeoGenVidPollResponse"}],"description":"Comfy Router output schema for the Veo models: the terminal Vertex AI long-running operation document, forwarded unchanged. Veo is submit-and-poll, and Router polls on the caller's behalf, so the body a caller receives is the finished operation (`done: true`) rather than the operation handle the submit returns.\nThe generated videos are in `response.videos`. Each carries either `gcsUri` or `bytesBase64Encoded` (the video inline), and `mimeType` names the container. `response.raiMediaFilteredCount` and `response.raiMediaFilteredReasons` report videos Vertex's responsible-AI filters removed, so a `done: true` operation can legitimately carry fewer videos than were requested — or none at all.\nWHICH OF THE TWO YOU GET depends on the environment, and `gcsUri` is the one field Router does not forward unchanged. TREAT BOTH MEMBERS AS POSSIBLE AND READ WHICHEVER IS PRESENT. Where platform storage is enabled for `veo/*`, Router asks Vertex to write the clips to Cloud Storage rather than inline them and then replaces each `gcsUri` it owns with a SIGNED HTTPS URL you can fetch directly, valid for 24 hours; everything else in the document is Vertex's own. Where it is not enabled, Vertex inlines the clips as `bytesBase64Encoded` and the whole document is forwarded unchanged. Two further cases are always answered with the partner's own value: a request that sets `parameters.storageUri` to a bucket of its own keeps that bucket and its `gs://` URIs, untouched, and a bring-your-own-key call runs entirely in your project and is likewise left alone. A `storageUri` naming a COMFY-OWNED bucket is never honoured. Where platform storage is enabled, a value naming Router's own Veo or Gemini asset bucket is replaced on a synchronous call with a destination Router mints under your own platform prefix for that call, and only that exact Router-minted destination passes the guard; a queued submit (`/requests`) makes no such replacement and refuses it with a 400. A value naming any other Comfy-owned bucket — or any Comfy-owned bucket where platform storage is not enabled — is refused with a 400 and nothing is generated. See `VeoGenVidRequest.parameters.storageUri`.\nThe example's `gcsUri` is a placeholder, with the signature query the real answer carries deliberately omitted: a signed URL published in a spec is a leaked credential and a dead link at once.\nEVERY MEMBER IS CLASSIFIED AND RUNS TODAY. The three veo ids that were not — `veo/veo-2.0-generate-001`, `veo/veo-3.0-generate-001` and `veo/veo-3.0-fast-generate-001` — have been retired and have left both this list and the Router catalog; a call naming one is refused 404 model_not_found before dispatch, with a `detail` naming its successor.","example":{"done":true,"name":"projects/example-project/locations/us-central1/publishers/google/models/veo-3.1-fast-generate-001/operations/1a2b3c4d","response":{"@type":"type.googleapis.com/cloud.ai.large_models.vision.GenerateVideoResponse","raiMediaFilteredCount":0,"videos":[{"gcsUri":"https://storage.googleapis.com/EXAMPLE_BUCKET/veo/USER_ID/REQUEST_ID/sample_0.mp4","mimeType":"video/mp4"}]}}}}}}}}}},"components":{"schemas":{"VeoGenVidPollResponse":{"description":"Response from polling a Veo video generation operation","properties":{"done":{"description":"Whether the operation has completed","type":"boolean"},"error":{"description":"Error details, present if the operation failed","properties":{"code":{"description":"gRPC error code","type":"integer"},"message":{"description":"Error message","type":"string"}},"type":"object"},"name":{"description":"Operation resource name","type":"string"},"response":{"description":"The prediction response, present when done is true","properties":{"@type":{"example":"type.googleapis.com/cloud.ai.large_models.vision.GenerateVideoResponse","type":"string"},"raiMediaFilteredCount":{"description":"Number of videos filtered by responsible AI policies","type":"integer"},"raiMediaFilteredReasons":{"description":"Reasons why videos were filtered by responsible AI policies","items":{"type":"string"},"type":"array"},"videos":{"items":{"properties":{"bytesBase64Encoded":{"description":"Base64-encoded video content","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the generated video","type":"string"},"mimeType":{"description":"Video MIME type (video/mp4)","type":"string"}},"type":"object"},"type":"array"}},"type":"object"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"veo/veo-3.1-fast-generate-001","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image","video"],"x-comfy-router-output-modalities":["video","audio"]}
diff --git a/router-schemas/veo/veo-3.1-generate-001.json b/router-schemas/veo/veo-3.1-generate-001.json
index c545b31dc8..48f6fd57c6 100644
--- a/router-schemas/veo/veo-3.1-generate-001.json
+++ b/router-schemas/veo/veo-3.1-generate-001.json
@@ -1 +1 @@
-{"openapi":"3.0.2","info":{"title":"veo/veo-3.1-generate-001","description":"The request body Comfy Router accepts for the model \"veo/veo-3.1-generate-001\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"461b74caa277"},"paths":{"/v2/models/veo/veo-3.1-generate-001":{"post":{"operationId":"runRouterModel","summary":"Run veo/veo-3.1-generate-001 synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"example":{"instances":[{"prompt":"a single red maple leaf falling onto still water, slow motion"}],"parameters":{"durationSeconds":4,"generateAudio":false,"sampleCount":1}},"properties":{"instances":{"items":{"properties":{"cameraControl":{"description":"Camera motion type. Requires image to be provided.","enum":["fixed","pan_left","pan_right","tilt_up","tilt_down","truck_left","truck_right","pedestal_up","pedestal_down","push_in","pull_out"],"type":"string"},"image":{"description":"Optional first frame image to guide video generation","oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded image data","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the image. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"mimeType":{"description":"MIME type of the image (image/jpeg or image/png)","enum":["image/jpeg","image/png"],"type":"string"}},"type":"object"},"lastFrame":{"description":"Optional last frame image. Used with image to generate video between first and last frames. Supported by Veo 3.0+ models.","oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded image data","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the image. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"mimeType":{"description":"MIME type of the image (image/jpeg or image/png)","enum":["image/jpeg","image/png"],"type":"string"}},"type":"object"},"mask":{"description":"Optional mask for video editing. Applies to input video.","oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded mask bytes","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI to mask file. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"maskMode":{"description":"How the mask is applied","enum":["insert","remove","remove_static","outpaint"],"type":"string"},"mimeType":{"description":"MIME type of the mask (image/png, image/jpeg, image/webp, or video formats)","type":"string"}},"type":"object"},"prompt":{"description":"Text description of the video to generate","type":"string"},"referenceImages":{"description":"Optional reference images to guide video generation. Supports up to 3 asset images or 1 style image. Supported by Veo 3.1 models (preview).","items":{"properties":{"image":{"oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded image data","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the image. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"mimeType":{"description":"MIME type of the image (image/jpeg or image/png)","enum":["image/jpeg","image/png"],"type":"string"}},"type":"object"},"referenceId":{"description":"Optional identifier for the reference image","type":"string"},"referenceType":{"description":"Type of reference image","enum":["asset","style"],"type":"string"}},"required":["image","referenceType"],"type":"object"},"type":"array"},"video":{"description":"Optional input video for video extension or editing. Incompatible with image and referenceImages.","oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded video bytes","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the input video. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"mimeType":{"description":"MIME type of the video","enum":["video/mov","video/mpeg","video/mp4","video/mpg","video/avi","video/wmv","video/mpegps","video/x-flv"],"type":"string"}},"type":"object"}},"required":["prompt"],"type":"object"},"type":"array"},"parameters":{"properties":{"aspectRatio":{"description":"Aspect ratio of the generated video. Default: 16:9","enum":["16:9","9:16"],"example":"16:9","type":"string"},"compressionQuality":{"description":"Video compression quality. Default: optimized","enum":["optimized","lossless"],"type":"string"},"durationSeconds":{"description":"Target duration of the generated video in seconds. Veo 2: 5-8. Veo 3/3.1: 4, 6, or 8. Default: 8","type":"number"},"enhancePrompt":{"description":"Automatically improve prompt for higher quality. Defaults to true.","type":"boolean"},"fps":{"description":"Frame rate of generated videos in frames per second","type":"integer"},"generateAudio":{"description":"Whether to generate audio along with the video. Defaults to true. Supported by Veo 3.0+ models.","type":"boolean"},"negativePrompt":{"description":"Text describing what to avoid in the generated video","type":"string"},"personGeneration":{"description":"Controls people in generated videos. Default: allow_adult","enum":["dont_allow","allow_adult","allowAll"],"type":"string"},"pubsubTopic":{"description":"Cloud Pub/Sub topic for progress updates (projects/{project}/topics/{topic})","type":"string"},"resizeMode":{"description":"Resize approach for input image. Default: pad","enum":["pad","crop"],"type":"string"},"resolution":{"description":"Output video resolution. Supported by Veo 3.0+ models. Default: 720p","enum":["720p","1080p","4k"],"type":"string"},"sampleCount":{"description":"Number of videos to generate. If not specified, 1 video is generated.","maximum":4,"minimum":1,"type":"integer"},"seed":{"description":"Random seed for deterministic output. Different seeds used per video if sampleCount \u003e 1.","format":"uint32","type":"integer"},"storageUri":{"description":"Optional Cloud Storage destination for the generated videos, as a `gs://` URI.\nIT MUST NAME A BUCKET OF YOUR OWN. Outside bring-your-own-key, this route runs under Comfy's own Google Cloud credentials rather than yours, so a destination inside a Comfy-owned bucket would be a write performed by Comfy's service account, into a shared layout, billed to Comfy's project. A request naming one is refused with a 400 and nothing is generated — with one exception on a synchronous Comfy Router call (`POST /v2/models/{provider}/{model}`) where platform storage is enabled: a value naming Router's own Veo or Gemini asset bucket is replaced with a destination Router mints under your own platform prefix for that call (see the Router Veo route), and only that exact Router-minted destination is let through. A queued Router submit (`POST /v2/models/{provider}/{model}/requests`) makes no such replacement, so it refuses that value with a 400 like any other Comfy-owned bucket. Any other destination is forwarded to Vertex unchanged and is yours to grant access to. A bring-your-own-key call runs in your own project and is forwarded as sent.","type":"string"},"task":{"description":"Operation type for the video generation request","enum":["textToVideo","imageToVideo","referenceToVideo","edit","extend","upscale"],"type":"string"}},"type":"object"}},"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/VeoGenVidPollResponse"}],"description":"Comfy Router output schema for the Veo models: the terminal Vertex AI long-running operation document, forwarded unchanged. Veo is submit-and-poll, and Router polls on the caller's behalf, so the body a caller receives is the finished operation (`done: true`) rather than the operation handle the submit returns.\nThe generated videos are in `response.videos`. Each carries either `gcsUri` or `bytesBase64Encoded` (the video inline), and `mimeType` names the container. `response.raiMediaFilteredCount` and `response.raiMediaFilteredReasons` report videos Vertex's responsible-AI filters removed, so a `done: true` operation can legitimately carry fewer videos than were requested — or none at all.\nWHICH OF THE TWO YOU GET depends on the environment, and `gcsUri` is the one field Router does not forward unchanged. TREAT BOTH MEMBERS AS POSSIBLE AND READ WHICHEVER IS PRESENT. Where platform storage is enabled for `veo/*`, Router asks Vertex to write the clips to Cloud Storage rather than inline them and then replaces each `gcsUri` it owns with a SIGNED HTTPS URL you can fetch directly, valid for 24 hours; everything else in the document is Vertex's own. Where it is not enabled, Vertex inlines the clips as `bytesBase64Encoded` and the whole document is forwarded unchanged. Two further cases are always answered with the partner's own value: a request that sets `parameters.storageUri` to a bucket of its own keeps that bucket and its `gs://` URIs, untouched, and a bring-your-own-key call runs entirely in your project and is likewise left alone. A `storageUri` naming a COMFY-OWNED bucket is never honoured. Where platform storage is enabled, a value naming Router's own Veo or Gemini asset bucket is replaced on a synchronous call with a destination Router mints under your own platform prefix for that call, and only that exact Router-minted destination passes the guard; a queued submit (`/requests`) makes no such replacement and refuses it with a 400. A value naming any other Comfy-owned bucket — or any Comfy-owned bucket where platform storage is not enabled — is refused with a 400 and nothing is generated. See `VeoGenVidRequest.parameters.storageUri`.\nThe example's `gcsUri` is a placeholder, with the signature query the real answer carries deliberately omitted: a signed URL published in a spec is a leaked credential and a dead link at once.\nEVERY MEMBER IS CLASSIFIED AND RUNS TODAY. The three veo ids that were not — `veo/veo-2.0-generate-001`, `veo/veo-3.0-generate-001` and `veo/veo-3.0-fast-generate-001` — have been retired and have left both this list and the Router catalog; a call naming one is refused 404 model_not_found before dispatch, with a `detail` naming its successor.","example":{"done":true,"name":"projects/example-project/locations/us-central1/publishers/google/models/veo-3.1-fast-generate-001/operations/1a2b3c4d","response":{"@type":"type.googleapis.com/cloud.ai.large_models.vision.GenerateVideoResponse","raiMediaFilteredCount":0,"videos":[{"gcsUri":"https://storage.googleapis.com/EXAMPLE_BUCKET/veo/USER_ID/REQUEST_ID/sample_0.mp4","mimeType":"video/mp4"}]}}}}}}}}}},"components":{"schemas":{"VeoGenVidPollResponse":{"description":"Response from polling a Veo video generation operation","properties":{"done":{"description":"Whether the operation has completed","type":"boolean"},"error":{"description":"Error details, present if the operation failed","properties":{"code":{"description":"gRPC error code","type":"integer"},"message":{"description":"Error message","type":"string"}},"type":"object"},"name":{"description":"Operation resource name","type":"string"},"response":{"description":"The prediction response, present when done is true","properties":{"@type":{"example":"type.googleapis.com/cloud.ai.large_models.vision.GenerateVideoResponse","type":"string"},"raiMediaFilteredCount":{"description":"Number of videos filtered by responsible AI policies","type":"integer"},"raiMediaFilteredReasons":{"description":"Reasons why videos were filtered by responsible AI policies","items":{"type":"string"},"type":"array"},"videos":{"items":{"properties":{"bytesBase64Encoded":{"description":"Base64-encoded video content","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the generated video","type":"string"},"mimeType":{"description":"Video MIME type (video/mp4)","type":"string"}},"type":"object"},"type":"array"}},"type":"object"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"veo/veo-3.1-generate-001","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image","video"],"x-comfy-router-output-modalities":["video","audio"]}
+{"openapi":"3.0.2","info":{"title":"veo/veo-3.1-generate-001","description":"The request body Comfy Router accepts for the model \"veo/veo-3.1-generate-001\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"56727421d95b"},"paths":{"/v2/models/veo/veo-3.1-generate-001":{"post":{"operationId":"runRouterModel","summary":"Run veo/veo-3.1-generate-001 synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"example":{"instances":[{"prompt":"a single red maple leaf falling onto still water, slow motion"}],"parameters":{"durationSeconds":4,"generateAudio":false,"sampleCount":1}},"properties":{"instances":{"items":{"properties":{"cameraControl":{"description":"Camera motion type. Requires image to be provided.","enum":["fixed","pan_left","pan_right","tilt_up","tilt_down","truck_left","truck_right","pedestal_up","pedestal_down","push_in","pull_out"],"type":"string"},"image":{"description":"Optional first frame image to guide video generation","oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded image data","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the image. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"mimeType":{"description":"MIME type of the image (image/jpeg or image/png)","enum":["image/jpeg","image/png"],"type":"string"}},"type":"object"},"lastFrame":{"description":"Optional last frame image. Used with image to generate video between first and last frames. Supported by Veo 3.0+ models.","oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded image data","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the image. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"mimeType":{"description":"MIME type of the image (image/jpeg or image/png)","enum":["image/jpeg","image/png"],"type":"string"}},"type":"object"},"mask":{"description":"Optional mask for video editing. Applies to input video.","oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded mask bytes","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI to mask file. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"maskMode":{"description":"How the mask is applied","enum":["insert","remove","remove_static","outpaint"],"type":"string"},"mimeType":{"description":"MIME type of the mask (image/png, image/jpeg, image/webp, or video formats)","type":"string"}},"type":"object"},"prompt":{"description":"Text description of the video to generate","type":"string"},"referenceImages":{"description":"Optional reference images to guide video generation. Supports up to 3 asset images or 1 style image. Supported by Veo 3.1 models (preview).","items":{"properties":{"image":{"oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded image data","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the image. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"mimeType":{"description":"MIME type of the image (image/jpeg or image/png)","enum":["image/jpeg","image/png"],"type":"string"}},"type":"object"},"referenceId":{"description":"Optional identifier for the reference image","type":"string"},"referenceType":{"description":"Type of reference image","enum":["asset","style"],"type":"string"}},"required":["image","referenceType"],"type":"object"},"type":"array"},"video":{"description":"Optional input video for video extension or editing. Incompatible with image and referenceImages.","oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded video bytes","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the input video. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"mimeType":{"description":"MIME type of the video","enum":["video/mov","video/mpeg","video/mp4","video/mpg","video/avi","video/wmv","video/mpegps","video/x-flv"],"type":"string"}},"type":"object"}},"required":["prompt"],"type":"object"},"type":"array"},"parameters":{"properties":{"aspectRatio":{"description":"Aspect ratio of the generated video. Default: 16:9","enum":["16:9","9:16"],"example":"16:9","type":"string"},"compressionQuality":{"description":"Video compression quality. Default: optimized","enum":["optimized","lossless"],"type":"string"},"durationSeconds":{"description":"Target duration of the generated video in seconds. Veo 2: 5-8. Veo 3/3.1: 4, 6, or 8. Default: 8","type":"number"},"enhancePrompt":{"description":"Automatically improve prompt for higher quality. Defaults to true.","type":"boolean"},"fps":{"description":"Frame rate of generated videos in frames per second","type":"integer"},"generateAudio":{"description":"Whether to generate audio along with the video. Defaults to true. Supported by Veo 3.0+ models.","type":"boolean"},"negativePrompt":{"description":"Text describing what to avoid in the generated video","type":"string"},"personGeneration":{"description":"Controls people in generated videos. Default: allow_adult","enum":["dont_allow","allow_adult","allowAll"],"type":"string"},"pubsubTopic":{"description":"Cloud Pub/Sub topic for progress updates (projects/{project}/topics/{topic}). Not accepted on Comfy-credentialed (non-BYOK) requests: the generate route answers 400 if it is present, because the operation runs in Comfy's project and Vertex would publish as Comfy's service agent. Accepted only on bring-your-own-key requests, where Vertex evaluates the topic grant in your own project.","type":"string"},"resizeMode":{"description":"Resize approach for input image. Default: pad","enum":["pad","crop"],"type":"string"},"resolution":{"description":"Output video resolution. Supported by Veo 3.0+ models. Default: 720p","enum":["720p","1080p","4k"],"type":"string"},"sampleCount":{"description":"Number of videos to generate. If not specified, 1 video is generated.","maximum":4,"minimum":1,"type":"integer"},"seed":{"description":"Random seed for deterministic output. Different seeds used per video if sampleCount \u003e 1.","format":"uint32","type":"integer"},"storageUri":{"description":"Optional Cloud Storage destination for the generated videos, as a `gs://` URI.\nIT MUST NAME A BUCKET OF YOUR OWN. Outside bring-your-own-key, this route runs under Comfy's own Google Cloud credentials rather than yours, so a destination inside a Comfy-owned bucket would be a write performed by Comfy's service account, into a shared layout, billed to Comfy's project. A request naming one is refused with a 400 and nothing is generated — with one exception on a synchronous Comfy Router call (`POST /v2/models/{provider}/{model}`) where platform storage is enabled: a value naming Router's own Veo or Gemini asset bucket is replaced with a destination Router mints under your own platform prefix for that call (see the Router Veo route), and only that exact Router-minted destination is let through. A queued Router submit (`POST /v2/models/{provider}/{model}/requests`) makes no such replacement, so it refuses that value with a 400 like any other Comfy-owned bucket. Any other destination is forwarded to Vertex unchanged and is yours to grant access to. A bring-your-own-key call runs in your own project and is forwarded as sent.","type":"string"},"task":{"description":"Operation type for the video generation request","enum":["textToVideo","imageToVideo","referenceToVideo","edit","extend","upscale"],"type":"string"}},"type":"object"}},"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/VeoGenVidPollResponse"}],"description":"Comfy Router output schema for the Veo models: the terminal Vertex AI long-running operation document, forwarded unchanged. Veo is submit-and-poll, and Router polls on the caller's behalf, so the body a caller receives is the finished operation (`done: true`) rather than the operation handle the submit returns.\nThe generated videos are in `response.videos`. Each carries either `gcsUri` or `bytesBase64Encoded` (the video inline), and `mimeType` names the container. `response.raiMediaFilteredCount` and `response.raiMediaFilteredReasons` report videos Vertex's responsible-AI filters removed, so a `done: true` operation can legitimately carry fewer videos than were requested — or none at all.\nWHICH OF THE TWO YOU GET depends on the environment, and `gcsUri` is the one field Router does not forward unchanged. TREAT BOTH MEMBERS AS POSSIBLE AND READ WHICHEVER IS PRESENT. Where platform storage is enabled for `veo/*`, Router asks Vertex to write the clips to Cloud Storage rather than inline them and then replaces each `gcsUri` it owns with a SIGNED HTTPS URL you can fetch directly, valid for 24 hours; everything else in the document is Vertex's own. Where it is not enabled, Vertex inlines the clips as `bytesBase64Encoded` and the whole document is forwarded unchanged. Two further cases are always answered with the partner's own value: a request that sets `parameters.storageUri` to a bucket of its own keeps that bucket and its `gs://` URIs, untouched, and a bring-your-own-key call runs entirely in your project and is likewise left alone. A `storageUri` naming a COMFY-OWNED bucket is never honoured. Where platform storage is enabled, a value naming Router's own Veo or Gemini asset bucket is replaced on a synchronous call with a destination Router mints under your own platform prefix for that call, and only that exact Router-minted destination passes the guard; a queued submit (`/requests`) makes no such replacement and refuses it with a 400. A value naming any other Comfy-owned bucket — or any Comfy-owned bucket where platform storage is not enabled — is refused with a 400 and nothing is generated. See `VeoGenVidRequest.parameters.storageUri`.\nThe example's `gcsUri` is a placeholder, with the signature query the real answer carries deliberately omitted: a signed URL published in a spec is a leaked credential and a dead link at once.\nEVERY MEMBER IS CLASSIFIED AND RUNS TODAY. The three veo ids that were not — `veo/veo-2.0-generate-001`, `veo/veo-3.0-generate-001` and `veo/veo-3.0-fast-generate-001` — have been retired and have left both this list and the Router catalog; a call naming one is refused 404 model_not_found before dispatch, with a `detail` naming its successor.","example":{"done":true,"name":"projects/example-project/locations/us-central1/publishers/google/models/veo-3.1-fast-generate-001/operations/1a2b3c4d","response":{"@type":"type.googleapis.com/cloud.ai.large_models.vision.GenerateVideoResponse","raiMediaFilteredCount":0,"videos":[{"gcsUri":"https://storage.googleapis.com/EXAMPLE_BUCKET/veo/USER_ID/REQUEST_ID/sample_0.mp4","mimeType":"video/mp4"}]}}}}}}}}}},"components":{"schemas":{"VeoGenVidPollResponse":{"description":"Response from polling a Veo video generation operation","properties":{"done":{"description":"Whether the operation has completed","type":"boolean"},"error":{"description":"Error details, present if the operation failed","properties":{"code":{"description":"gRPC error code","type":"integer"},"message":{"description":"Error message","type":"string"}},"type":"object"},"name":{"description":"Operation resource name","type":"string"},"response":{"description":"The prediction response, present when done is true","properties":{"@type":{"example":"type.googleapis.com/cloud.ai.large_models.vision.GenerateVideoResponse","type":"string"},"raiMediaFilteredCount":{"description":"Number of videos filtered by responsible AI policies","type":"integer"},"raiMediaFilteredReasons":{"description":"Reasons why videos were filtered by responsible AI policies","items":{"type":"string"},"type":"array"},"videos":{"items":{"properties":{"bytesBase64Encoded":{"description":"Base64-encoded video content","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the generated video","type":"string"},"mimeType":{"description":"Video MIME type (video/mp4)","type":"string"}},"type":"object"},"type":"array"}},"type":"object"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"veo/veo-3.1-generate-001","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image","video"],"x-comfy-router-output-modalities":["video","audio"]}
diff --git a/router-schemas/veo/veo-3.1-lite-generate-001.json b/router-schemas/veo/veo-3.1-lite-generate-001.json
index 21a9ee19e8..1db1192a39 100644
--- a/router-schemas/veo/veo-3.1-lite-generate-001.json
+++ b/router-schemas/veo/veo-3.1-lite-generate-001.json
@@ -1 +1 @@
-{"openapi":"3.0.2","info":{"title":"veo/veo-3.1-lite-generate-001","description":"The request body Comfy Router accepts for the model \"veo/veo-3.1-lite-generate-001\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"461b74caa277"},"paths":{"/v2/models/veo/veo-3.1-lite-generate-001":{"post":{"operationId":"runRouterModel","summary":"Run veo/veo-3.1-lite-generate-001 synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"example":{"instances":[{"prompt":"a single red maple leaf falling onto still water, slow motion"}],"parameters":{"durationSeconds":4,"generateAudio":false,"sampleCount":1}},"properties":{"instances":{"items":{"properties":{"cameraControl":{"description":"Camera motion type. Requires image to be provided.","enum":["fixed","pan_left","pan_right","tilt_up","tilt_down","truck_left","truck_right","pedestal_up","pedestal_down","push_in","pull_out"],"type":"string"},"image":{"description":"Optional first frame image to guide video generation","oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded image data","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the image. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"mimeType":{"description":"MIME type of the image (image/jpeg or image/png)","enum":["image/jpeg","image/png"],"type":"string"}},"type":"object"},"lastFrame":{"description":"Optional last frame image. Used with image to generate video between first and last frames. Supported by Veo 3.0+ models.","oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded image data","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the image. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"mimeType":{"description":"MIME type of the image (image/jpeg or image/png)","enum":["image/jpeg","image/png"],"type":"string"}},"type":"object"},"mask":{"description":"Optional mask for video editing. Applies to input video.","oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded mask bytes","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI to mask file. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"maskMode":{"description":"How the mask is applied","enum":["insert","remove","remove_static","outpaint"],"type":"string"},"mimeType":{"description":"MIME type of the mask (image/png, image/jpeg, image/webp, or video formats)","type":"string"}},"type":"object"},"prompt":{"description":"Text description of the video to generate","type":"string"},"referenceImages":{"description":"Optional reference images to guide video generation. Supports up to 3 asset images or 1 style image. Supported by Veo 3.1 models (preview).","items":{"properties":{"image":{"oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded image data","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the image. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"mimeType":{"description":"MIME type of the image (image/jpeg or image/png)","enum":["image/jpeg","image/png"],"type":"string"}},"type":"object"},"referenceId":{"description":"Optional identifier for the reference image","type":"string"},"referenceType":{"description":"Type of reference image","enum":["asset","style"],"type":"string"}},"required":["image","referenceType"],"type":"object"},"type":"array"},"video":{"description":"Optional input video for video extension or editing. Incompatible with image and referenceImages.","oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded video bytes","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the input video. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"mimeType":{"description":"MIME type of the video","enum":["video/mov","video/mpeg","video/mp4","video/mpg","video/avi","video/wmv","video/mpegps","video/x-flv"],"type":"string"}},"type":"object"}},"required":["prompt"],"type":"object"},"type":"array"},"parameters":{"properties":{"aspectRatio":{"description":"Aspect ratio of the generated video. Default: 16:9","enum":["16:9","9:16"],"example":"16:9","type":"string"},"compressionQuality":{"description":"Video compression quality. Default: optimized","enum":["optimized","lossless"],"type":"string"},"durationSeconds":{"description":"Target duration of the generated video in seconds. Veo 2: 5-8. Veo 3/3.1: 4, 6, or 8. Default: 8","type":"number"},"enhancePrompt":{"description":"Automatically improve prompt for higher quality. Defaults to true.","type":"boolean"},"fps":{"description":"Frame rate of generated videos in frames per second","type":"integer"},"generateAudio":{"description":"Whether to generate audio along with the video. Defaults to true. Supported by Veo 3.0+ models.","type":"boolean"},"negativePrompt":{"description":"Text describing what to avoid in the generated video","type":"string"},"personGeneration":{"description":"Controls people in generated videos. Default: allow_adult","enum":["dont_allow","allow_adult","allowAll"],"type":"string"},"pubsubTopic":{"description":"Cloud Pub/Sub topic for progress updates (projects/{project}/topics/{topic})","type":"string"},"resizeMode":{"description":"Resize approach for input image. Default: pad","enum":["pad","crop"],"type":"string"},"resolution":{"description":"Output video resolution. Supported by Veo 3.0+ models. Default: 720p","enum":["720p","1080p","4k"],"type":"string"},"sampleCount":{"description":"Number of videos to generate. If not specified, 1 video is generated.","maximum":4,"minimum":1,"type":"integer"},"seed":{"description":"Random seed for deterministic output. Different seeds used per video if sampleCount \u003e 1.","format":"uint32","type":"integer"},"storageUri":{"description":"Optional Cloud Storage destination for the generated videos, as a `gs://` URI.\nIT MUST NAME A BUCKET OF YOUR OWN. Outside bring-your-own-key, this route runs under Comfy's own Google Cloud credentials rather than yours, so a destination inside a Comfy-owned bucket would be a write performed by Comfy's service account, into a shared layout, billed to Comfy's project. A request naming one is refused with a 400 and nothing is generated — with one exception on a synchronous Comfy Router call (`POST /v2/models/{provider}/{model}`) where platform storage is enabled: a value naming Router's own Veo or Gemini asset bucket is replaced with a destination Router mints under your own platform prefix for that call (see the Router Veo route), and only that exact Router-minted destination is let through. A queued Router submit (`POST /v2/models/{provider}/{model}/requests`) makes no such replacement, so it refuses that value with a 400 like any other Comfy-owned bucket. Any other destination is forwarded to Vertex unchanged and is yours to grant access to. A bring-your-own-key call runs in your own project and is forwarded as sent.","type":"string"},"task":{"description":"Operation type for the video generation request","enum":["textToVideo","imageToVideo","referenceToVideo","edit","extend","upscale"],"type":"string"}},"type":"object"}},"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/VeoGenVidPollResponse"}],"description":"Comfy Router output schema for the Veo models: the terminal Vertex AI long-running operation document, forwarded unchanged. Veo is submit-and-poll, and Router polls on the caller's behalf, so the body a caller receives is the finished operation (`done: true`) rather than the operation handle the submit returns.\nThe generated videos are in `response.videos`. Each carries either `gcsUri` or `bytesBase64Encoded` (the video inline), and `mimeType` names the container. `response.raiMediaFilteredCount` and `response.raiMediaFilteredReasons` report videos Vertex's responsible-AI filters removed, so a `done: true` operation can legitimately carry fewer videos than were requested — or none at all.\nWHICH OF THE TWO YOU GET depends on the environment, and `gcsUri` is the one field Router does not forward unchanged. TREAT BOTH MEMBERS AS POSSIBLE AND READ WHICHEVER IS PRESENT. Where platform storage is enabled for `veo/*`, Router asks Vertex to write the clips to Cloud Storage rather than inline them and then replaces each `gcsUri` it owns with a SIGNED HTTPS URL you can fetch directly, valid for 24 hours; everything else in the document is Vertex's own. Where it is not enabled, Vertex inlines the clips as `bytesBase64Encoded` and the whole document is forwarded unchanged. Two further cases are always answered with the partner's own value: a request that sets `parameters.storageUri` to a bucket of its own keeps that bucket and its `gs://` URIs, untouched, and a bring-your-own-key call runs entirely in your project and is likewise left alone. A `storageUri` naming a COMFY-OWNED bucket is never honoured. Where platform storage is enabled, a value naming Router's own Veo or Gemini asset bucket is replaced on a synchronous call with a destination Router mints under your own platform prefix for that call, and only that exact Router-minted destination passes the guard; a queued submit (`/requests`) makes no such replacement and refuses it with a 400. A value naming any other Comfy-owned bucket — or any Comfy-owned bucket where platform storage is not enabled — is refused with a 400 and nothing is generated. See `VeoGenVidRequest.parameters.storageUri`.\nThe example's `gcsUri` is a placeholder, with the signature query the real answer carries deliberately omitted: a signed URL published in a spec is a leaked credential and a dead link at once.\nEVERY MEMBER IS CLASSIFIED AND RUNS TODAY. The three veo ids that were not — `veo/veo-2.0-generate-001`, `veo/veo-3.0-generate-001` and `veo/veo-3.0-fast-generate-001` — have been retired and have left both this list and the Router catalog; a call naming one is refused 404 model_not_found before dispatch, with a `detail` naming its successor.","example":{"done":true,"name":"projects/example-project/locations/us-central1/publishers/google/models/veo-3.1-fast-generate-001/operations/1a2b3c4d","response":{"@type":"type.googleapis.com/cloud.ai.large_models.vision.GenerateVideoResponse","raiMediaFilteredCount":0,"videos":[{"gcsUri":"https://storage.googleapis.com/EXAMPLE_BUCKET/veo/USER_ID/REQUEST_ID/sample_0.mp4","mimeType":"video/mp4"}]}}}}}}}}}},"components":{"schemas":{"VeoGenVidPollResponse":{"description":"Response from polling a Veo video generation operation","properties":{"done":{"description":"Whether the operation has completed","type":"boolean"},"error":{"description":"Error details, present if the operation failed","properties":{"code":{"description":"gRPC error code","type":"integer"},"message":{"description":"Error message","type":"string"}},"type":"object"},"name":{"description":"Operation resource name","type":"string"},"response":{"description":"The prediction response, present when done is true","properties":{"@type":{"example":"type.googleapis.com/cloud.ai.large_models.vision.GenerateVideoResponse","type":"string"},"raiMediaFilteredCount":{"description":"Number of videos filtered by responsible AI policies","type":"integer"},"raiMediaFilteredReasons":{"description":"Reasons why videos were filtered by responsible AI policies","items":{"type":"string"},"type":"array"},"videos":{"items":{"properties":{"bytesBase64Encoded":{"description":"Base64-encoded video content","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the generated video","type":"string"},"mimeType":{"description":"Video MIME type (video/mp4)","type":"string"}},"type":"object"},"type":"array"}},"type":"object"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"veo/veo-3.1-lite-generate-001","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image","video"],"x-comfy-router-output-modalities":["video","audio"]}
+{"openapi":"3.0.2","info":{"title":"veo/veo-3.1-lite-generate-001","description":"The request body Comfy Router accepts for the model \"veo/veo-3.1-lite-generate-001\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"56727421d95b"},"paths":{"/v2/models/veo/veo-3.1-lite-generate-001":{"post":{"operationId":"runRouterModel","summary":"Run veo/veo-3.1-lite-generate-001 synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"example":{"instances":[{"prompt":"a single red maple leaf falling onto still water, slow motion"}],"parameters":{"durationSeconds":4,"generateAudio":false,"sampleCount":1}},"properties":{"instances":{"items":{"properties":{"cameraControl":{"description":"Camera motion type. Requires image to be provided.","enum":["fixed","pan_left","pan_right","tilt_up","tilt_down","truck_left","truck_right","pedestal_up","pedestal_down","push_in","pull_out"],"type":"string"},"image":{"description":"Optional first frame image to guide video generation","oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded image data","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the image. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"mimeType":{"description":"MIME type of the image (image/jpeg or image/png)","enum":["image/jpeg","image/png"],"type":"string"}},"type":"object"},"lastFrame":{"description":"Optional last frame image. Used with image to generate video between first and last frames. Supported by Veo 3.0+ models.","oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded image data","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the image. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"mimeType":{"description":"MIME type of the image (image/jpeg or image/png)","enum":["image/jpeg","image/png"],"type":"string"}},"type":"object"},"mask":{"description":"Optional mask for video editing. Applies to input video.","oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded mask bytes","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI to mask file. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"maskMode":{"description":"How the mask is applied","enum":["insert","remove","remove_static","outpaint"],"type":"string"},"mimeType":{"description":"MIME type of the mask (image/png, image/jpeg, image/webp, or video formats)","type":"string"}},"type":"object"},"prompt":{"description":"Text description of the video to generate","type":"string"},"referenceImages":{"description":"Optional reference images to guide video generation. Supports up to 3 asset images or 1 style image. Supported by Veo 3.1 models (preview).","items":{"properties":{"image":{"oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded image data","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the image. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"mimeType":{"description":"MIME type of the image (image/jpeg or image/png)","enum":["image/jpeg","image/png"],"type":"string"}},"type":"object"},"referenceId":{"description":"Optional identifier for the reference image","type":"string"},"referenceType":{"description":"Type of reference image","enum":["asset","style"],"type":"string"}},"required":["image","referenceType"],"type":"object"},"type":"array"},"video":{"description":"Optional input video for video extension or editing. Incompatible with image and referenceImages.","oneOf":[{"required":["bytesBase64Encoded"]},{"required":["gcsUri"]}],"properties":{"bytesBase64Encoded":{"description":"Base64-encoded video bytes","format":"byte","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the input video. On a non-BYOK request a `gs://` URI (or its `https://storage.googleapis.com` form) naming a Comfy-owned bucket is refused with 400 unless the object is under your own `POST /customers/storage` prefix or is a video a previous Veo generate wrote for you.","type":"string"},"mimeType":{"description":"MIME type of the video","enum":["video/mov","video/mpeg","video/mp4","video/mpg","video/avi","video/wmv","video/mpegps","video/x-flv"],"type":"string"}},"type":"object"}},"required":["prompt"],"type":"object"},"type":"array"},"parameters":{"properties":{"aspectRatio":{"description":"Aspect ratio of the generated video. Default: 16:9","enum":["16:9","9:16"],"example":"16:9","type":"string"},"compressionQuality":{"description":"Video compression quality. Default: optimized","enum":["optimized","lossless"],"type":"string"},"durationSeconds":{"description":"Target duration of the generated video in seconds. Veo 2: 5-8. Veo 3/3.1: 4, 6, or 8. Default: 8","type":"number"},"enhancePrompt":{"description":"Automatically improve prompt for higher quality. Defaults to true.","type":"boolean"},"fps":{"description":"Frame rate of generated videos in frames per second","type":"integer"},"generateAudio":{"description":"Whether to generate audio along with the video. Defaults to true. Supported by Veo 3.0+ models.","type":"boolean"},"negativePrompt":{"description":"Text describing what to avoid in the generated video","type":"string"},"personGeneration":{"description":"Controls people in generated videos. Default: allow_adult","enum":["dont_allow","allow_adult","allowAll"],"type":"string"},"pubsubTopic":{"description":"Cloud Pub/Sub topic for progress updates (projects/{project}/topics/{topic}). Not accepted on Comfy-credentialed (non-BYOK) requests: the generate route answers 400 if it is present, because the operation runs in Comfy's project and Vertex would publish as Comfy's service agent. Accepted only on bring-your-own-key requests, where Vertex evaluates the topic grant in your own project.","type":"string"},"resizeMode":{"description":"Resize approach for input image. Default: pad","enum":["pad","crop"],"type":"string"},"resolution":{"description":"Output video resolution. Supported by Veo 3.0+ models. Default: 720p","enum":["720p","1080p","4k"],"type":"string"},"sampleCount":{"description":"Number of videos to generate. If not specified, 1 video is generated.","maximum":4,"minimum":1,"type":"integer"},"seed":{"description":"Random seed for deterministic output. Different seeds used per video if sampleCount \u003e 1.","format":"uint32","type":"integer"},"storageUri":{"description":"Optional Cloud Storage destination for the generated videos, as a `gs://` URI.\nIT MUST NAME A BUCKET OF YOUR OWN. Outside bring-your-own-key, this route runs under Comfy's own Google Cloud credentials rather than yours, so a destination inside a Comfy-owned bucket would be a write performed by Comfy's service account, into a shared layout, billed to Comfy's project. A request naming one is refused with a 400 and nothing is generated — with one exception on a synchronous Comfy Router call (`POST /v2/models/{provider}/{model}`) where platform storage is enabled: a value naming Router's own Veo or Gemini asset bucket is replaced with a destination Router mints under your own platform prefix for that call (see the Router Veo route), and only that exact Router-minted destination is let through. A queued Router submit (`POST /v2/models/{provider}/{model}/requests`) makes no such replacement, so it refuses that value with a 400 like any other Comfy-owned bucket. Any other destination is forwarded to Vertex unchanged and is yours to grant access to. A bring-your-own-key call runs in your own project and is forwarded as sent.","type":"string"},"task":{"description":"Operation type for the video generation request","enum":["textToVideo","imageToVideo","referenceToVideo","edit","extend","upscale"],"type":"string"}},"type":"object"}},"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/VeoGenVidPollResponse"}],"description":"Comfy Router output schema for the Veo models: the terminal Vertex AI long-running operation document, forwarded unchanged. Veo is submit-and-poll, and Router polls on the caller's behalf, so the body a caller receives is the finished operation (`done: true`) rather than the operation handle the submit returns.\nThe generated videos are in `response.videos`. Each carries either `gcsUri` or `bytesBase64Encoded` (the video inline), and `mimeType` names the container. `response.raiMediaFilteredCount` and `response.raiMediaFilteredReasons` report videos Vertex's responsible-AI filters removed, so a `done: true` operation can legitimately carry fewer videos than were requested — or none at all.\nWHICH OF THE TWO YOU GET depends on the environment, and `gcsUri` is the one field Router does not forward unchanged. TREAT BOTH MEMBERS AS POSSIBLE AND READ WHICHEVER IS PRESENT. Where platform storage is enabled for `veo/*`, Router asks Vertex to write the clips to Cloud Storage rather than inline them and then replaces each `gcsUri` it owns with a SIGNED HTTPS URL you can fetch directly, valid for 24 hours; everything else in the document is Vertex's own. Where it is not enabled, Vertex inlines the clips as `bytesBase64Encoded` and the whole document is forwarded unchanged. Two further cases are always answered with the partner's own value: a request that sets `parameters.storageUri` to a bucket of its own keeps that bucket and its `gs://` URIs, untouched, and a bring-your-own-key call runs entirely in your project and is likewise left alone. A `storageUri` naming a COMFY-OWNED bucket is never honoured. Where platform storage is enabled, a value naming Router's own Veo or Gemini asset bucket is replaced on a synchronous call with a destination Router mints under your own platform prefix for that call, and only that exact Router-minted destination passes the guard; a queued submit (`/requests`) makes no such replacement and refuses it with a 400. A value naming any other Comfy-owned bucket — or any Comfy-owned bucket where platform storage is not enabled — is refused with a 400 and nothing is generated. See `VeoGenVidRequest.parameters.storageUri`.\nThe example's `gcsUri` is a placeholder, with the signature query the real answer carries deliberately omitted: a signed URL published in a spec is a leaked credential and a dead link at once.\nEVERY MEMBER IS CLASSIFIED AND RUNS TODAY. The three veo ids that were not — `veo/veo-2.0-generate-001`, `veo/veo-3.0-generate-001` and `veo/veo-3.0-fast-generate-001` — have been retired and have left both this list and the Router catalog; a call naming one is refused 404 model_not_found before dispatch, with a `detail` naming its successor.","example":{"done":true,"name":"projects/example-project/locations/us-central1/publishers/google/models/veo-3.1-fast-generate-001/operations/1a2b3c4d","response":{"@type":"type.googleapis.com/cloud.ai.large_models.vision.GenerateVideoResponse","raiMediaFilteredCount":0,"videos":[{"gcsUri":"https://storage.googleapis.com/EXAMPLE_BUCKET/veo/USER_ID/REQUEST_ID/sample_0.mp4","mimeType":"video/mp4"}]}}}}}}}}}},"components":{"schemas":{"VeoGenVidPollResponse":{"description":"Response from polling a Veo video generation operation","properties":{"done":{"description":"Whether the operation has completed","type":"boolean"},"error":{"description":"Error details, present if the operation failed","properties":{"code":{"description":"gRPC error code","type":"integer"},"message":{"description":"Error message","type":"string"}},"type":"object"},"name":{"description":"Operation resource name","type":"string"},"response":{"description":"The prediction response, present when done is true","properties":{"@type":{"example":"type.googleapis.com/cloud.ai.large_models.vision.GenerateVideoResponse","type":"string"},"raiMediaFilteredCount":{"description":"Number of videos filtered by responsible AI policies","type":"integer"},"raiMediaFilteredReasons":{"description":"Reasons why videos were filtered by responsible AI policies","items":{"type":"string"},"type":"array"},"videos":{"items":{"properties":{"bytesBase64Encoded":{"description":"Base64-encoded video content","type":"string"},"gcsUri":{"description":"Cloud Storage URI of the generated video","type":"string"},"mimeType":{"description":"Video MIME type (video/mp4)","type":"string"}},"type":"object"},"type":"array"}},"type":"object"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"veo/veo-3.1-lite-generate-001","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image","video"],"x-comfy-router-output-modalities":["video","audio"]}
diff --git a/router-schemas/vertexai/gemini-2.5-flash-image.json b/router-schemas/vertexai/gemini-2.5-flash-image.json
index 8b27786537..3b4ac835b5 100644
--- a/router-schemas/vertexai/gemini-2.5-flash-image.json
+++ b/router-schemas/vertexai/gemini-2.5-flash-image.json
@@ -1 +1 @@
-{"openapi":"3.0.2","info":{"title":"vertexai/gemini-2.5-flash-image","description":"The request body Comfy Router accepts for the model \"vertexai/gemini-2.5-flash-image\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"4fddddb86113"},"paths":{"/v2/models/vertexai/gemini-2.5-flash-image":{"post":{"operationId":"runRouterModel","summary":"Run vertexai/gemini-2.5-flash-image synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"example":{"contents":[{"parts":[{"text":"Describe a robot learning to paint, in two sentences."}],"role":"user"}]},"properties":{"contents":{"items":{"$ref":"#/components/schemas/GeminiContent"},"type":"array"},"generationConfig":{"$ref":"#/components/schemas/GeminiGenerationConfig"},"safetySettings":{"items":{"$ref":"#/components/schemas/GeminiSafetySetting"},"type":"array"},"systemInstruction":{"$ref":"#/components/schemas/GeminiSystemInstructionContent"},"tools":{"items":{"$ref":"#/components/schemas/GeminiTool"},"type":"array"},"uploadImagesToStorage":{"description":"If true, generated images will be uploaded to cloud storage and returned as signed URLs instead of inline base64 data. The URLs expire after 24 hours.","type":"boolean"},"videoMetadata":{"$ref":"#/components/schemas/GeminiVideoMetadata"}},"required":["contents"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/GeminiGenerateContentResponse"}],"description":"Comfy Router output schema for the image-returning Gemini models: the same Vertex AI `generateContent` response the text models return, forwarded unchanged, but the generated image arrives as base64 bytes at `candidates[0].content.parts[0].inlineData.data`, with its media type at `inlineData.mimeType`. A response may carry a text part alongside the image part, so select the part by the field you need rather than by index.\n`uploadImagesToStorage` CHANGES THIS SHAPE. When the request sets it, the proxy uploads each generated image to Comfy storage and replaces the part's `inlineData` with `fileData`, carrying a Comfy-signed URL at `fileData.fileUri` and the media type at `fileData.mimeType`; `inlineData` is then absent for that part. The URL expires 24 hours after it is minted, so download the image rather than storing the link. An image whose upload fails is left as `inlineData`, so ONE response can mix both shapes - branch on which key is present rather than assuming either.\nRouter does not re-host a partner link for this family: Gemini returns the image as bytes and no URL of its own, so `uploadImagesToStorage` is an upload of those bytes that the caller asks for, not a rewrite of somebody else's link. A request that leaves the field unset is answered as base64 from end to end WHENEVER VERTEX AI ITSELF SERVES IT - which is always the case for `vertexai/gemini-2.5-flash-image` and `vertexai/gemini-3.1-flash-lite-image`, the two ids on this component that have no other producer, and is the usual but not the only case for the two below.\n`vertexai/gemini-3-pro-image` (`fal`, `runware`, `wavespeed`) and `vertexai/gemini-3.1-flash-image` (`fal`, `runware`, `wavespeed`) have ALTERNATE PRODUCERS: an eligible request may be served by a partner instead of by Vertex AI. Two PUBLISHED routes reach one, both the alt-provider dispatch in `server/implementation/router_run.go`: `model_provider` names a partner explicitly, and `fallback_provider` (default ON) retries once against a registered alternate when the Vertex AI attempt fails for a reason attributable to Router or to Vertex AI rather than to the request. That retry is suppressed - whatever `fallback_provider` says - on a BYOK call, on a same-key idempotent replay, on a multipart edit operation, and when the model has no registered alternate. SEPARATELY, the `vertexai` partner proxy runs a fallback cascade of its own on these two ids when the platform-credential attempt fails: it is NOT governed by `fallback_provider`, it is disabled only for BYOK requests, and it can therefore fire on a failure the request itself caused and on a call that asked for `fallback_provider: false`. Treat a non-Vertex producer as possible on these two ids whatever the request asked for.\nA response the `fal`, `runware` or `wavespeed` leg served, translated back into this contract, carries every generated image as `fileData.fileUri` holding THE PARTNER'S OWN hosted URL, and never `inlineData.data` - all three partners return a hosted URL and never base64 bytes. The URL's lifetime and access policy are the partner's own rather than the 24-hour Comfy-signed URL described above, and Comfy publishes neither, so download the image promptly rather than storing the link. The proxy-level cascade above is NOT bound to that shape: it substitutes another provider's Gemini-compatible body, which may carry inline bytes instead. So the rule is the one the `uploadImagesToStorage` paragraph already states - branch on which key a part actually carries, never on a producer you inferred.\n`uploadImagesToStorage` does not change any of this. The flag is an upload of Vertex AI's own bytes, and off Vertex AI there are no such bytes to upload, so sending it neither produces `inlineData` nor rewrites the partner's link: it is simply inert on an alt-provider leg. A request that carries the key - whatever its value, `false` included - is told so: the translated alt-provider response lists `uploadImagesToStorage` in `X-Comfy-Router-Dropped-Params`, among the request fields the partner's schema has no place for. Only a translated response carries that disclosure: the proxy-level cascade and a `strict_mode: true` call run no translation and send no such header.\nTHREE RESPONSES SKIP THAT TRANSLATION and return the PARTNER'S OWN envelope rather than this component's: `strict_mode: true` asks for the alternate provider's raw schema by definition, a partner response that arrives with a non-empty `Content-Encoding` is forwarded unchanged because Router does not decompress a body to translate it, and a translation that fails or yields nothing forwards the partner's body unchanged - that body may carry no `candidates` key at all. An alt-provider response that IS translated back carries `candidates` and nothing else: `usageMetadata`, `promptFeedback`, `modelVersion`, `responseId` and `finishReason` are Vertex AI's own fields, and the translation has nothing to map them from.\nWHICH PRODUCER ANSWERED is disclosed more weakly than the shape itself. `X-Comfy-Router-Fallback-Provider` names the provider only on a response Router's OWN fallback retry served: the proxy-level cascade sets no header. A same-key idempotent replay repeats both that header and `X-Comfy-Router-Dropped-Params` exactly as the original call sent them, so a replay discloses what the original call did and no more. The request's own `model_provider` is reliable only in the direction it states - it says a partner was ASKED for, not that none answered when it was absent. The published per-model document of a model with a PUBLISHED leg carries an `x-comfy-router-alt-providers` marker naming it; absence of that marker is NOT a guarantee that the model has no alternate producer, since a registered leg that is not cleared for public disclosure still dispatches without appearing there. Which partner served a given request is a Comfy routing decision that can change without notice, and is not part of this contract.\nAs for the text models, `promptFeedback` and `usageMetadata` are present on a safety-blocked response that generated nothing; `candidates` is the field to branch on. Both are Vertex AI's own fields: on a response an alternate producer served, see the paragraph above for what is absent.\nThe example's `inlineData.data` is the PLACEHOLDER `PGJhc2U2ND4=`, which decodes to the literal text `\u003cbase64\u003e` - a real image would be megabytes, and a real provider response does not belong in a published spec. It is spelled as valid base64 rather than as a bare `\u003cbase64\u003e` marker because `GeminiInlineData.data` declares `format: byte`, which this document's own example validation enforces.\nThe example is authored ONCE for all four ids above and rendered into each per-model document unchanged, so its `modelVersion` is ILLUSTRATIVE RATHER THAN ENDPOINT-SPECIFIC. Vertex AI echoes the id that actually served the call, so a `vertexai/gemini-3-pro-image` response reads `gemini-3-pro-image` there - not the `gemini-2.5-flash-image` the example happens to show - and on an alt-provider-served response the field is absent entirely, per the paragraph above. Where `modelVersion` is present, read it as echoing the served id, never as the literal value printed in the document you are reading.","example":{"candidates":[{"content":{"parts":[{"inlineData":{"data":"PGJhc2U2ND4=","mimeType":"image/png"}}],"role":"model"},"finishReason":"STOP"}],"modelVersion":"gemini-2.5-flash-image","responseId":"7c6b5a49-3827-1605-f4e3-d2c1b0a99887","usageMetadata":{"candidatesTokenCount":1290,"promptTokenCount":11,"totalTokenCount":1301}}}}}}}}}},"components":{"schemas":{"GeminiCandidate":{"properties":{"citationMetadata":{"$ref":"#/components/schemas/GeminiCitationMetadata"},"content":{"$ref":"#/components/schemas/GeminiContent"},"finishReason":{"type":"string"},"safetyRatings":{"items":{"$ref":"#/components/schemas/GeminiSafetyRating"},"type":"array"}},"type":"object"},"GeminiCitation":{"properties":{"authors":{"items":{"type":"string"},"type":"array"},"endIndex":{"type":"integer"},"license":{"type":"string"},"publicationDate":{"format":"date","type":"string"},"startIndex":{"type":"integer"},"title":{"type":"string"},"uri":{"type":"string"}},"type":"object"},"GeminiCitationMetadata":{"properties":{"citations":{"items":{"$ref":"#/components/schemas/GeminiCitation"},"type":"array"}},"type":"object"},"GeminiContent":{"description":"The content of the current conversation with the model. For single-turn queries, this is a single instance. For multi-turn queries, this is a repeated field that contains conversation history and the latest request.\n","properties":{"parts":{"items":{"$ref":"#/components/schemas/GeminiPart"},"type":"array"},"role":{"enum":["user","model"],"example":"user","type":"string"}},"required":["parts"],"type":"object"},"GeminiFileData":{"description":"URI based data.","properties":{"fileUri":{"description":"URI","type":"string"},"mimeType":{"$ref":"#/components/schemas/GeminiMimeType"}},"type":"object"},"GeminiFunctionDeclaration":{"properties":{"description":{"type":"string"},"name":{"type":"string"},"parameters":{"description":"JSON schema for the function parameters","type":"object"}},"required":["name"],"type":"object"},"GeminiGenerateContentResponse":{"properties":{"candidates":{"items":{"$ref":"#/components/schemas/GeminiCandidate"},"type":"array"},"createTime":{"description":"Timestamp when the response was created.","type":"string"},"modelVersion":{"description":"The model version used to generate the response.","type":"string"},"promptFeedback":{"$ref":"#/components/schemas/GeminiPromptFeedback"},"responseId":{"description":"Unique identifier for the response.","type":"string"},"usageMetadata":{"$ref":"#/components/schemas/GeminiUsageMetadata"}},"type":"object"},"GeminiGenerationConfig":{"description":"Sampling, length and output settings for the generation. Every field is optional: the fields below that declare a `default` apply it when omitted, and the rest fall back to the model's own behaviour.\n","properties":{"imageConfig":{"description":"Configuration for image generation","properties":{"aspectRatio":{"description":"Aspect ratio for generated images","type":"string"},"imageOutputOptions":{"description":"Optional. The image output format for generated images.","properties":{"compressionQuality":{"description":"Optional. The compression quality of the output image.","type":"integer"},"mimeType":{"description":"Optional. The image format that the output should be saved as, on the Vertex AI paths: requests served by Comfy's own credentials, and BYOK requests authenticated with a GCP service account. Accepted values there are `image/png` and `image/jpeg`, matched case-insensitively and normalised to lower case before the request is forwarded; any other value is rejected with a 400 naming this field. Defaults to `image/png` when omitted. A BYOK request authenticated with a Google AI Studio API key is the exception: that upstream has no such property and rejects the whole call if it is present, so the field is dropped from the request rather than honoured or rejected, and the output format is whatever AI Studio chooses. On every path, read the media type back from the response part you are given (`inlineData.mimeType`, or `fileData.mimeType` when `uploadImagesToStorage` is set) rather than assuming the value you sent.","type":"string"}},"type":"object"},"imageSize":{"description":"Optional. Specifies the size of generated images. Supported values are 1K, 2K, 4K. If not specified, the model will use default value 1K.","type":"string"}},"type":"object"},"maxOutputTokens":{"description":"Maximum number of tokens that can be generated in the response. A token is approximately 4 characters. 100 tokens correspond to roughly 60-80 words.\n","example":2048,"maximum":65536,"minimum":16,"type":"integer"},"responseModalities":{"items":{"enum":["TEXT","IMAGE"],"type":"string"},"type":"array"},"seed":{"description":"When seed is fixed to a specific value, the model makes a best effort to provide the same response for repeated requests. Deterministic output isn't guaranteed. Also, changing the model or parameter settings, such as the temperature, can cause variations in the response even when you use the same seed value. By default, a random seed value is used. Available for the following models:, gemini-2.5-flash, gemini-2.5-pro, gemini-2.5-flash-preview-04-1, gemini-2.5-pro-preview-05-0, gemini-2.0-flash-lite-00, gemini-2.0-flash-001\n","example":343940597,"type":"integer"},"stopSequences":{"items":{"type":"string"},"type":"array"},"temperature":{"default":1,"description":"The temperature is used for sampling during response generation, which occurs when topP and topK are applied. Temperature controls the degree of randomness in token selection. Lower temperatures are good for prompts that require a less open-ended or creative response, while higher temperatures can lead to more diverse or creative results. A temperature of 0 means that the highest probability tokens are always selected. In this case, responses for a given prompt are mostly deterministic, but a small amount of variation is still possible. If the model returns a response that's too generic, too short, or the model gives a fallback response, try increasing the temperature\n","format":"float","maximum":2,"minimum":0,"type":"number"},"thinkingConfig":{"description":"Optional. Configuration for thinking features. Thinking is a process where the model breaks down a complex task into smaller steps to generate a higher-quality response.","properties":{"includeThoughts":{"description":"Optional. If true, the model will include its thoughts in the response.","type":"boolean"},"thinkingBudget":{"description":"Optional. The token budget for the model's thinking process. The model will make a best effort to stay within this budget.","type":"integer"},"thinkingLevel":{"description":"Optional. The thinking level for the model.","enum":["THINKING_LEVEL_UNSPECIFIED","LOW","MEDIUM","HIGH","MINIMAL"],"type":"string"}},"type":"object"},"topK":{"default":40,"description":"Top-K changes how the model selects tokens for output. A top-K of 1 means the next selected token is the most probable among all tokens in the model's vocabulary. A top-K of 3 means that the next token is selected from among the 3 most probable tokens by using temperature.\n","example":40,"minimum":1,"type":"integer"},"topP":{"default":0.95,"description":"If specified, nucleus sampling is used.\nTop-P changes how the model selects tokens for output. Tokens are selected from the most (see top-K) to least probable until the sum of their probabilities equals the top-P value. For example, if tokens A, B, and C have a probability of 0.3, 0.2, and 0.1 and the top-P value is 0.5, then the model will select either A or B as the next token by using temperature and excludes C as a candidate.\nSpecify a lower value for less random responses and a higher value for more random responses.\n","format":"float","maximum":1,"minimum":0,"type":"number"}},"type":"object"},"GeminiInlineData":{"description":"Inline data in raw bytes. For gemini-2.0-flash-lite and gemini-2.0-flash, you can specify up to 3000 images by using inlineData.\n","properties":{"data":{"description":"The base64 encoding of the image, PDF, or video to include inline in the prompt. When including media inline, you must also specify the media type (mimeType) of the data. Size limit: 20MB\n","format":"byte","type":"string"},"mimeType":{"$ref":"#/components/schemas/GeminiMimeType"}},"type":"object"},"GeminiMimeType":{"description":"The media type of the file specified in the data or fileUri fields. Acceptable values include the following. For gemini-2.0-flash-lite and gemini-2.0-flash, the maximum length of an audio file is 8.4 hours and the maximum length of a video file (without audio) is one hour. For more information, see Gemini audio and video requirements. Text files must be UTF-8 encoded. The contents of the text file count toward the token limit. There is no limit on image resolution.","enum":["application/pdf","audio/mpeg","audio/mp3","audio/wav","image/png","image/jpeg","image/webp","text/plain","video/mov","video/mpeg","video/mp4","video/mpg","video/avi","video/wmv","video/mpegps","video/flv","image/heic","image/heif","audio/flac","video/webm"],"type":"string"},"GeminiOffset":{"description":"Represents a duration offset for video timeline positions.\n","properties":{"nanos":{"description":"Signed fractions of a second at nanosecond resolution. Negative second values with fractions must still have non-negative nanos values.\n","example":0,"maximum":999999999,"minimum":0,"type":"integer"},"seconds":{"description":"Signed seconds of the span of time. Must be from -315,576,000,000 to +315,576,000,000 inclusive.\n","example":60,"maximum":315576000000,"minimum":-315576000000,"type":"integer"}},"type":"object"},"GeminiPart":{"properties":{"fileData":{"$ref":"#/components/schemas/GeminiFileData"},"inlineData":{"$ref":"#/components/schemas/GeminiInlineData"},"mediaProcessing":{"description":"How the model reads this part's video. Set \"AGENTIC\" to let the model decide which segments to inspect, instead of fixed-rate frame sampling. Omit for the default fixed-rate sampling. Supported on gemini-3.7-flash and newer Flash models.\n","type":"string"},"text":{"description":"A text prompt or code snippet.","example":"Write a story about a robot learning to paint","type":"string"},"thought":{"description":"Indicates this part is a thinking/reasoning step from the model.","type":"boolean"}},"type":"object"},"GeminiPromptFeedback":{"properties":{"blockReason":{"type":"string"},"blockReasonMessage":{"type":"string"},"safetyRatings":{"items":{"$ref":"#/components/schemas/GeminiSafetyRating"},"type":"array"}},"type":"object"},"GeminiSafetyCategory":{"enum":["HARM_CATEGORY_SEXUALLY_EXPLICIT","HARM_CATEGORY_HATE_SPEECH","HARM_CATEGORY_HARASSMENT","HARM_CATEGORY_DANGEROUS_CONTENT"],"type":"string"},"GeminiSafetyRating":{"properties":{"category":{"$ref":"#/components/schemas/GeminiSafetyCategory"},"probability":{"description":"The probability that the content violates the specified safety category","enum":["NEGLIGIBLE","LOW","MEDIUM","HIGH","UNKNOWN"],"type":"string"}},"type":"object"},"GeminiSafetySetting":{"description":"Per request settings for blocking unsafe content. Enforced on GenerateContentResponse.candidates.\n","properties":{"category":{"$ref":"#/components/schemas/GeminiSafetyCategory"},"threshold":{"$ref":"#/components/schemas/GeminiSafetyThreshold"}},"required":["category","threshold"],"type":"object"},"GeminiSafetyThreshold":{"enum":["OFF","BLOCK_NONE","BLOCK_LOW_AND_ABOVE","BLOCK_MEDIUM_AND_ABOVE","BLOCK_ONLY_HIGH"],"type":"string"},"GeminiSystemInstructionContent":{"description":"Instructions for the model to steer it toward better performance. For example, \"Answer as concisely as possible\" or \"Don't use technical terms in your response\". The text strings count toward the token limit. The role field of systemInstruction is ignored and doesn't affect the performance of the model. Note: Only text should be used in parts and content in each part should be in a separate paragraph.\n","properties":{"parts":{"description":"A list of ordered parts that make up a single message. Different parts may have different IANA MIME types. For limits on the inputs, such as the maximum number of tokens or the number of images, see the model specifications on the Google models page.\n","items":{"$ref":"#/components/schemas/GeminiTextPart"},"type":"array"},"role":{"description":"The identity of the entity that creates the message. The following values are supported: user: This indicates that the message is sent by a real person, typically a user-generated message. model: This indicates that the message is generated by the model. The model value is used to insert messages from the model into the conversation during multi-turn conversations. For non-multi-turn conversations, this field can be left blank or unset.\n","enum":["user","model"],"example":"user","type":"string"}},"required":["parts"],"type":"object"},"GeminiTextPart":{"properties":{"text":{"description":"A text prompt or code snippet.","example":"Answer as concisely as possible","type":"string"}},"type":"object"},"GeminiTool":{"description":"A piece of code that enables the system to interact with external systems to perform an action, or set of actions, outside of knowledge and scope of the model. See Function calling.\n","properties":{"functionDeclarations":{"items":{"$ref":"#/components/schemas/GeminiFunctionDeclaration"},"type":"array"}},"type":"object"},"GeminiUsageMetadata":{"properties":{"cachedContentTokenCount":{"description":"Output only. Number of tokens in the cached part in the input (the cached content).","type":"integer"},"candidatesTokenCount":{"description":"Number of tokens in the response(s).","type":"integer"},"candidatesTokensDetails":{"description":"Breakdown of candidate tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"promptTokenCount":{"description":"Number of tokens in the request. When cachedContent is set, this is still the total effective prompt size meaning this includes the number of tokens in the cached content.","type":"integer"},"promptTokensDetails":{"description":"Breakdown of prompt tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"thoughtsTokenCount":{"description":"Number of tokens present in thoughts output.","type":"integer"},"toolUsePromptTokenCount":{"description":"Number of tokens present in tool-use prompt(s).","type":"integer"},"toolUsePromptTokensDetails":{"description":"Breakdown of tool-use prompt tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"totalTokenCount":{"description":"Total number of tokens (prompt + candidates).","type":"integer"},"trafficType":{"description":"Traffic type used for the request (e.g., PROVISIONED_THROUGHPUT).","type":"string"}},"type":"object"},"GeminiVideoMetadata":{"description":"For video input, the start and end offset of the video in Duration format. For example, to specify a 10 second clip starting at 1:00, set \"startOffset\": { \"seconds\": 60 } and \"endOffset\": { \"seconds\": 70 }. The metadata should only be specified while the video data is presented in inlineData or fileData.\n","properties":{"endOffset":{"$ref":"#/components/schemas/GeminiOffset"},"startOffset":{"$ref":"#/components/schemas/GeminiOffset"}},"type":"object"},"Modality":{"description":"Type of input or output content modality.","enum":["MODALITY_UNSPECIFIED","TEXT","IMAGE","VIDEO","AUDIO","DOCUMENT"],"type":"string"},"ModalityTokenCount":{"properties":{"modality":{"$ref":"#/components/schemas/Modality"},"tokenCount":{"description":"Number of tokens for the given modality.","type":"integer"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"vertexai/gemini-2.5-flash-image","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image"],"x-comfy-router-output-modalities":["image","text"]}
+{"openapi":"3.0.2","info":{"title":"vertexai/gemini-2.5-flash-image","description":"The request body Comfy Router accepts for the model \"vertexai/gemini-2.5-flash-image\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"a86e35b5a353"},"paths":{"/v2/models/vertexai/gemini-2.5-flash-image":{"post":{"operationId":"runRouterModel","summary":"Run vertexai/gemini-2.5-flash-image synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"example":{"contents":[{"parts":[{"text":"Describe a robot learning to paint, in two sentences."}],"role":"user"}]},"properties":{"contents":{"items":{"$ref":"#/components/schemas/GeminiContent"},"type":"array"},"generationConfig":{"$ref":"#/components/schemas/GeminiGenerationConfig"},"safetySettings":{"items":{"$ref":"#/components/schemas/GeminiSafetySetting"},"type":"array"},"systemInstruction":{"$ref":"#/components/schemas/GeminiSystemInstructionContent"},"tools":{"items":{"$ref":"#/components/schemas/GeminiTool"},"type":"array"},"uploadImagesToStorage":{"description":"If true, generated images will be uploaded to cloud storage and returned as signed URLs instead of inline base64 data. The URLs expire after 24 hours.","type":"boolean"},"videoMetadata":{"$ref":"#/components/schemas/GeminiVideoMetadata"}},"required":["contents"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/GeminiGenerateContentResponse"}],"description":"Comfy Router output schema for the image-returning Gemini models: the same Vertex AI `generateContent` response the text models return, forwarded unchanged, but the generated image arrives as base64 bytes at `candidates[0].content.parts[0].inlineData.data`, with its media type at `inlineData.mimeType`. A response may carry a text part alongside the image part, so select the part by the field you need rather than by index.\n`uploadImagesToStorage` CHANGES THIS SHAPE. When the request sets it, the proxy uploads each generated image to Comfy storage and replaces the part's `inlineData` with `fileData`, carrying a Comfy-signed URL at `fileData.fileUri` and the media type at `fileData.mimeType`; `inlineData` is then absent for that part. The URL expires 24 hours after it is minted, so download the image rather than storing the link. An image whose upload fails is left as `inlineData`, so ONE response can mix both shapes - branch on which key is present rather than assuming either.\nRouter does not re-host a partner link for this family: Gemini returns the image as bytes and no URL of its own, so `uploadImagesToStorage` is an upload of those bytes that the caller asks for, not a rewrite of somebody else's link. A request that leaves the field unset is answered as base64 from end to end WHENEVER VERTEX AI ITSELF SERVES IT - which is always the case for `vertexai/gemini-2.5-flash-image` and `vertexai/gemini-3.1-flash-lite-image`, the two ids on this component that have no other producer, and is the usual but not the only case for the two below.\n`vertexai/gemini-3-pro-image` (`fal`, `pika`, `runware`, `wavespeed`) and `vertexai/gemini-3.1-flash-image` (`fal`, `pika`, `runware`, `wavespeed`) have ALTERNATE PRODUCERS: an eligible request may be served by a partner instead of by Vertex AI. Two PUBLISHED routes reach one, both the alt-provider dispatch in `server/implementation/router_run.go`: `model_provider` names a partner explicitly, and `fallback_provider` (default ON) retries once against a registered alternate when the Vertex AI attempt fails for a reason attributable to Router or to Vertex AI rather than to the request. That retry is suppressed - whatever `fallback_provider` says - on a BYOK call, on a same-key idempotent replay, on a multipart edit operation, and when the model has no registered alternate. SEPARATELY, the `vertexai` partner proxy runs a fallback cascade of its own on these two ids when the platform-credential attempt fails: it is NOT governed by `fallback_provider`, it is disabled only for BYOK requests, and it can therefore fire on a failure the request itself caused and on a call that asked for `fallback_provider: false`. Treat a non-Vertex producer as possible on these two ids whatever the request asked for.\nA response the `fal`, `pika`, `runware` or `wavespeed` leg served, translated back into this contract, carries every generated image as `fileData.fileUri` holding THE PARTNER'S OWN hosted URL, and never `inlineData.data` - all four partners return a hosted URL and never base64 bytes. The URL's lifetime and access policy are the partner's own rather than the 24-hour Comfy-signed URL described above, and Comfy publishes neither, so download the image promptly rather than storing the link. The proxy-level cascade above is NOT bound to that shape: it substitutes another provider's Gemini-compatible body, which may carry inline bytes instead. So the rule is the one the `uploadImagesToStorage` paragraph already states - branch on which key a part actually carries, never on a producer you inferred.\n`uploadImagesToStorage` does not change any of this. The flag is an upload of Vertex AI's own bytes, and off Vertex AI there are no such bytes to upload, so sending it neither produces `inlineData` nor rewrites the partner's link: it is simply inert on an alt-provider leg. A request that carries the key - whatever its value, `false` included - is told so: the translated alt-provider response lists `uploadImagesToStorage` in `X-Comfy-Router-Dropped-Params`, among the request fields the partner's schema has no place for. Only a translated response carries that disclosure: the proxy-level cascade and a `strict_mode: true` call run no translation and send no such header.\nTHREE RESPONSES SKIP THAT TRANSLATION and return the PARTNER'S OWN envelope rather than this component's: `strict_mode: true` asks for the alternate provider's raw schema by definition, a partner response that arrives with a non-empty `Content-Encoding` is forwarded unchanged because Router does not decompress a body to translate it, and a translation that fails or yields nothing forwards the partner's body unchanged - that body may carry no `candidates` key at all. An alt-provider response that IS translated back carries `candidates` and nothing else: `usageMetadata`, `promptFeedback`, `modelVersion`, `responseId` and `finishReason` are Vertex AI's own fields, and the translation has nothing to map them from.\nWHICH PRODUCER ANSWERED is disclosed more weakly than the shape itself. `X-Comfy-Router-Fallback-Provider` names the provider only on a response Router's OWN fallback retry served: the proxy-level cascade sets no header. A same-key idempotent replay repeats both that header and `X-Comfy-Router-Dropped-Params` exactly as the original call sent them, so a replay discloses what the original call did and no more. The request's own `model_provider` is reliable only in the direction it states - it says a partner was ASKED for, not that none answered when it was absent. The published per-model document of a model with a PUBLISHED leg carries an `x-comfy-router-alt-providers` marker naming it; absence of that marker is NOT a guarantee that the model has no alternate producer, since a registered leg that is not cleared for public disclosure still dispatches without appearing there. Which partner served a given request is a Comfy routing decision that can change without notice, and is not part of this contract.\nAs for the text models, `promptFeedback` and `usageMetadata` are present on a safety-blocked response that generated nothing; `candidates` is the field to branch on. Both are Vertex AI's own fields: on a response an alternate producer served, see the paragraph above for what is absent.\nThe example's `inlineData.data` is the PLACEHOLDER `PGJhc2U2ND4=`, which decodes to the literal text `\u003cbase64\u003e` - a real image would be megabytes, and a real provider response does not belong in a published spec. It is spelled as valid base64 rather than as a bare `\u003cbase64\u003e` marker because `GeminiInlineData.data` declares `format: byte`, which this document's own example validation enforces.\nThe example is authored ONCE for all four ids above and rendered into each per-model document unchanged, so its `modelVersion` is ILLUSTRATIVE RATHER THAN ENDPOINT-SPECIFIC. Vertex AI echoes the id that actually served the call, so a `vertexai/gemini-3-pro-image` response reads `gemini-3-pro-image` there - not the `gemini-2.5-flash-image` the example happens to show - and on an alt-provider-served response the field is absent entirely, per the paragraph above. Where `modelVersion` is present, read it as echoing the served id, never as the literal value printed in the document you are reading.","example":{"candidates":[{"content":{"parts":[{"inlineData":{"data":"PGJhc2U2ND4=","mimeType":"image/png"}}],"role":"model"},"finishReason":"STOP"}],"modelVersion":"gemini-2.5-flash-image","responseId":"7c6b5a49-3827-1605-f4e3-d2c1b0a99887","usageMetadata":{"candidatesTokenCount":1290,"promptTokenCount":11,"totalTokenCount":1301}}}}}}}}}},"components":{"schemas":{"GeminiCandidate":{"properties":{"citationMetadata":{"$ref":"#/components/schemas/GeminiCitationMetadata"},"content":{"$ref":"#/components/schemas/GeminiContent"},"finishReason":{"type":"string"},"safetyRatings":{"items":{"$ref":"#/components/schemas/GeminiSafetyRating"},"type":"array"}},"type":"object"},"GeminiCitation":{"properties":{"authors":{"items":{"type":"string"},"type":"array"},"endIndex":{"type":"integer"},"license":{"type":"string"},"publicationDate":{"format":"date","type":"string"},"startIndex":{"type":"integer"},"title":{"type":"string"},"uri":{"type":"string"}},"type":"object"},"GeminiCitationMetadata":{"properties":{"citations":{"items":{"$ref":"#/components/schemas/GeminiCitation"},"type":"array"}},"type":"object"},"GeminiContent":{"description":"The content of the current conversation with the model. For single-turn queries, this is a single instance. For multi-turn queries, this is a repeated field that contains conversation history and the latest request.\n","properties":{"parts":{"items":{"$ref":"#/components/schemas/GeminiPart"},"type":"array"},"role":{"enum":["user","model"],"example":"user","type":"string"}},"required":["parts"],"type":"object"},"GeminiFileData":{"description":"URI based data.","properties":{"fileUri":{"description":"URI","type":"string"},"mimeType":{"$ref":"#/components/schemas/GeminiMimeType"}},"type":"object"},"GeminiFunctionDeclaration":{"properties":{"description":{"type":"string"},"name":{"type":"string"},"parameters":{"description":"JSON schema for the function parameters","type":"object"}},"required":["name"],"type":"object"},"GeminiGenerateContentResponse":{"properties":{"candidates":{"items":{"$ref":"#/components/schemas/GeminiCandidate"},"type":"array"},"createTime":{"description":"Timestamp when the response was created.","type":"string"},"modelVersion":{"description":"The model version used to generate the response.","type":"string"},"promptFeedback":{"$ref":"#/components/schemas/GeminiPromptFeedback"},"responseId":{"description":"Unique identifier for the response.","type":"string"},"usageMetadata":{"$ref":"#/components/schemas/GeminiUsageMetadata"}},"type":"object"},"GeminiGenerationConfig":{"description":"Sampling, length and output settings for the generation. Every field is optional: the fields below that declare a `default` apply it when omitted, and the rest fall back to the model's own behaviour.\n","properties":{"imageConfig":{"description":"Configuration for image generation","properties":{"aspectRatio":{"description":"Aspect ratio for generated images","type":"string"},"imageOutputOptions":{"description":"Optional. The image output format for generated images.","properties":{"compressionQuality":{"description":"Optional. The compression quality of the output image.","type":"integer"},"mimeType":{"description":"Optional. The image format that the output should be saved as, on the Vertex AI paths: requests served by Comfy's own credentials, and BYOK requests authenticated with a GCP service account. Accepted values there are `image/png` and `image/jpeg`, matched case-insensitively and normalised to lower case before the request is forwarded; any other value is rejected with a 400 naming this field. Defaults to `image/png` when omitted. A BYOK request authenticated with a Google AI Studio API key is the exception: that upstream has no such property and rejects the whole call if it is present, so the field is dropped from the request rather than honoured or rejected, and the output format is whatever AI Studio chooses. On every path, read the media type back from the response part you are given (`inlineData.mimeType`, or `fileData.mimeType` when `uploadImagesToStorage` is set) rather than assuming the value you sent.","type":"string"}},"type":"object"},"imageSize":{"description":"Optional. Specifies the size of generated images. Supported values are 1K, 2K, 4K. If not specified, the model will use default value 1K.","type":"string"}},"type":"object"},"maxOutputTokens":{"description":"Maximum number of tokens that can be generated in the response. A token is approximately 4 characters. 100 tokens correspond to roughly 60-80 words.\n","example":2048,"maximum":65536,"minimum":16,"type":"integer"},"responseModalities":{"items":{"enum":["TEXT","IMAGE"],"type":"string"},"type":"array"},"seed":{"description":"When seed is fixed to a specific value, the model makes a best effort to provide the same response for repeated requests. Deterministic output isn't guaranteed. Also, changing the model or parameter settings, such as the temperature, can cause variations in the response even when you use the same seed value. By default, a random seed value is used. Available for the following models:, gemini-2.5-flash, gemini-2.5-pro, gemini-2.5-flash-preview-04-1, gemini-2.5-pro-preview-05-0, gemini-2.0-flash-lite-00, gemini-2.0-flash-001\n","example":343940597,"type":"integer"},"stopSequences":{"items":{"type":"string"},"type":"array"},"temperature":{"default":1,"description":"The temperature is used for sampling during response generation, which occurs when topP and topK are applied. Temperature controls the degree of randomness in token selection. Lower temperatures are good for prompts that require a less open-ended or creative response, while higher temperatures can lead to more diverse or creative results. A temperature of 0 means that the highest probability tokens are always selected. In this case, responses for a given prompt are mostly deterministic, but a small amount of variation is still possible. If the model returns a response that's too generic, too short, or the model gives a fallback response, try increasing the temperature\n","format":"float","maximum":2,"minimum":0,"type":"number"},"thinkingConfig":{"description":"Optional. Configuration for thinking features. Thinking is a process where the model breaks down a complex task into smaller steps to generate a higher-quality response.","properties":{"includeThoughts":{"description":"Optional. If true, the model will include its thoughts in the response.","type":"boolean"},"thinkingBudget":{"description":"Optional. The token budget for the model's thinking process. The model will make a best effort to stay within this budget.","type":"integer"},"thinkingLevel":{"description":"Optional. The thinking level for the model.","enum":["THINKING_LEVEL_UNSPECIFIED","LOW","MEDIUM","HIGH","MINIMAL"],"type":"string"}},"type":"object"},"topK":{"default":40,"description":"Top-K changes how the model selects tokens for output. A top-K of 1 means the next selected token is the most probable among all tokens in the model's vocabulary. A top-K of 3 means that the next token is selected from among the 3 most probable tokens by using temperature.\n","example":40,"minimum":1,"type":"integer"},"topP":{"default":0.95,"description":"If specified, nucleus sampling is used.\nTop-P changes how the model selects tokens for output. Tokens are selected from the most (see top-K) to least probable until the sum of their probabilities equals the top-P value. For example, if tokens A, B, and C have a probability of 0.3, 0.2, and 0.1 and the top-P value is 0.5, then the model will select either A or B as the next token by using temperature and excludes C as a candidate.\nSpecify a lower value for less random responses and a higher value for more random responses.\n","format":"float","maximum":1,"minimum":0,"type":"number"}},"type":"object"},"GeminiInlineData":{"description":"Inline data in raw bytes. For gemini-2.0-flash-lite and gemini-2.0-flash, you can specify up to 3000 images by using inlineData.\n","properties":{"data":{"description":"The base64 encoding of the image, PDF, or video to include inline in the prompt. When including media inline, you must also specify the media type (mimeType) of the data. Size limit: 20MB\n","format":"byte","type":"string"},"mimeType":{"$ref":"#/components/schemas/GeminiMimeType"}},"type":"object"},"GeminiMimeType":{"description":"The media type of the file specified in the data or fileUri fields. Acceptable values include the following. For gemini-2.0-flash-lite and gemini-2.0-flash, the maximum length of an audio file is 8.4 hours and the maximum length of a video file (without audio) is one hour. For more information, see Gemini audio and video requirements. Text files must be UTF-8 encoded. The contents of the text file count toward the token limit. There is no limit on image resolution.","enum":["application/pdf","audio/mpeg","audio/mp3","audio/wav","image/png","image/jpeg","image/webp","text/plain","video/mov","video/mpeg","video/mp4","video/mpg","video/avi","video/wmv","video/mpegps","video/flv","image/heic","image/heif","audio/flac","video/webm"],"type":"string"},"GeminiOffset":{"description":"Represents a duration offset for video timeline positions.\n","properties":{"nanos":{"description":"Signed fractions of a second at nanosecond resolution. Negative second values with fractions must still have non-negative nanos values.\n","example":0,"maximum":999999999,"minimum":0,"type":"integer"},"seconds":{"description":"Signed seconds of the span of time. Must be from -315,576,000,000 to +315,576,000,000 inclusive.\n","example":60,"maximum":315576000000,"minimum":-315576000000,"type":"integer"}},"type":"object"},"GeminiPart":{"properties":{"fileData":{"$ref":"#/components/schemas/GeminiFileData"},"inlineData":{"$ref":"#/components/schemas/GeminiInlineData"},"mediaProcessing":{"description":"How the model reads this part's video. Set \"AGENTIC\" to let the model decide which segments to inspect, instead of fixed-rate frame sampling. Omit for the default fixed-rate sampling. Supported on gemini-3.7-flash and newer Flash models.\n","type":"string"},"text":{"description":"A text prompt or code snippet.","example":"Write a story about a robot learning to paint","type":"string"},"thought":{"description":"Indicates this part is a thinking/reasoning step from the model.","type":"boolean"}},"type":"object"},"GeminiPromptFeedback":{"properties":{"blockReason":{"type":"string"},"blockReasonMessage":{"type":"string"},"safetyRatings":{"items":{"$ref":"#/components/schemas/GeminiSafetyRating"},"type":"array"}},"type":"object"},"GeminiSafetyCategory":{"enum":["HARM_CATEGORY_SEXUALLY_EXPLICIT","HARM_CATEGORY_HATE_SPEECH","HARM_CATEGORY_HARASSMENT","HARM_CATEGORY_DANGEROUS_CONTENT"],"type":"string"},"GeminiSafetyRating":{"properties":{"category":{"$ref":"#/components/schemas/GeminiSafetyCategory"},"probability":{"description":"The probability that the content violates the specified safety category","enum":["NEGLIGIBLE","LOW","MEDIUM","HIGH","UNKNOWN"],"type":"string"}},"type":"object"},"GeminiSafetySetting":{"description":"Per request settings for blocking unsafe content. Enforced on GenerateContentResponse.candidates.\n","properties":{"category":{"$ref":"#/components/schemas/GeminiSafetyCategory"},"threshold":{"$ref":"#/components/schemas/GeminiSafetyThreshold"}},"required":["category","threshold"],"type":"object"},"GeminiSafetyThreshold":{"enum":["OFF","BLOCK_NONE","BLOCK_LOW_AND_ABOVE","BLOCK_MEDIUM_AND_ABOVE","BLOCK_ONLY_HIGH"],"type":"string"},"GeminiSystemInstructionContent":{"description":"Instructions for the model to steer it toward better performance. For example, \"Answer as concisely as possible\" or \"Don't use technical terms in your response\". The text strings count toward the token limit. The role field of systemInstruction is ignored and doesn't affect the performance of the model. Note: Only text should be used in parts and content in each part should be in a separate paragraph.\n","properties":{"parts":{"description":"A list of ordered parts that make up a single message. Different parts may have different IANA MIME types. For limits on the inputs, such as the maximum number of tokens or the number of images, see the model specifications on the Google models page.\n","items":{"$ref":"#/components/schemas/GeminiTextPart"},"type":"array"},"role":{"description":"The identity of the entity that creates the message. The following values are supported: user: This indicates that the message is sent by a real person, typically a user-generated message. model: This indicates that the message is generated by the model. The model value is used to insert messages from the model into the conversation during multi-turn conversations. For non-multi-turn conversations, this field can be left blank or unset.\n","enum":["user","model"],"example":"user","type":"string"}},"required":["parts"],"type":"object"},"GeminiTextPart":{"properties":{"text":{"description":"A text prompt or code snippet.","example":"Answer as concisely as possible","type":"string"}},"type":"object"},"GeminiTool":{"description":"A piece of code that enables the system to interact with external systems to perform an action, or set of actions, outside of knowledge and scope of the model. See Function calling.\n","properties":{"functionDeclarations":{"items":{"$ref":"#/components/schemas/GeminiFunctionDeclaration"},"type":"array"}},"type":"object"},"GeminiUsageMetadata":{"properties":{"cachedContentTokenCount":{"description":"Output only. Number of tokens in the cached part in the input (the cached content).","type":"integer"},"candidatesTokenCount":{"description":"Number of tokens in the response(s).","type":"integer"},"candidatesTokensDetails":{"description":"Breakdown of candidate tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"promptTokenCount":{"description":"Number of tokens in the request. When cachedContent is set, this is still the total effective prompt size meaning this includes the number of tokens in the cached content.","type":"integer"},"promptTokensDetails":{"description":"Breakdown of prompt tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"thoughtsTokenCount":{"description":"Number of tokens present in thoughts output.","type":"integer"},"toolUsePromptTokenCount":{"description":"Number of tokens present in tool-use prompt(s).","type":"integer"},"toolUsePromptTokensDetails":{"description":"Breakdown of tool-use prompt tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"totalTokenCount":{"description":"Total number of tokens (prompt + candidates).","type":"integer"},"trafficType":{"description":"Traffic type used for the request (e.g., PROVISIONED_THROUGHPUT).","type":"string"}},"type":"object"},"GeminiVideoMetadata":{"description":"For video input, the start and end offset of the video in Duration format. For example, to specify a 10 second clip starting at 1:00, set \"startOffset\": { \"seconds\": 60 } and \"endOffset\": { \"seconds\": 70 }. The metadata should only be specified while the video data is presented in inlineData or fileData.\n","properties":{"endOffset":{"$ref":"#/components/schemas/GeminiOffset"},"startOffset":{"$ref":"#/components/schemas/GeminiOffset"}},"type":"object"},"Modality":{"description":"Type of input or output content modality.","enum":["MODALITY_UNSPECIFIED","TEXT","IMAGE","VIDEO","AUDIO","DOCUMENT"],"type":"string"},"ModalityTokenCount":{"properties":{"modality":{"$ref":"#/components/schemas/Modality"},"tokenCount":{"description":"Number of tokens for the given modality.","type":"integer"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"vertexai/gemini-2.5-flash-image","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image"],"x-comfy-router-output-modalities":["image","text"]}
diff --git a/router-schemas/vertexai/gemini-3-pro-image.json b/router-schemas/vertexai/gemini-3-pro-image.json
index 5db006768c..c008a9fc23 100644
--- a/router-schemas/vertexai/gemini-3-pro-image.json
+++ b/router-schemas/vertexai/gemini-3-pro-image.json
@@ -1 +1 @@
-{"openapi":"3.0.2","info":{"title":"vertexai/gemini-3-pro-image","description":"The request body Comfy Router accepts for the model \"vertexai/gemini-3-pro-image\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"4fddddb86113"},"paths":{"/v2/models/vertexai/gemini-3-pro-image":{"post":{"operationId":"runRouterModel","summary":"Run vertexai/gemini-3-pro-image synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"example":{"contents":[{"parts":[{"text":"Describe a robot learning to paint, in two sentences."}],"role":"user"}]},"properties":{"contents":{"items":{"$ref":"#/components/schemas/GeminiContent"},"type":"array"},"generationConfig":{"$ref":"#/components/schemas/GeminiGenerationConfig"},"safetySettings":{"items":{"$ref":"#/components/schemas/GeminiSafetySetting"},"type":"array"},"systemInstruction":{"$ref":"#/components/schemas/GeminiSystemInstructionContent"},"tools":{"items":{"$ref":"#/components/schemas/GeminiTool"},"type":"array"},"uploadImagesToStorage":{"description":"If true, generated images will be uploaded to cloud storage and returned as signed URLs instead of inline base64 data. The URLs expire after 24 hours.","type":"boolean"},"videoMetadata":{"$ref":"#/components/schemas/GeminiVideoMetadata"}},"required":["contents"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/GeminiGenerateContentResponse"}],"description":"Comfy Router output schema for the image-returning Gemini models: the same Vertex AI `generateContent` response the text models return, forwarded unchanged, but the generated image arrives as base64 bytes at `candidates[0].content.parts[0].inlineData.data`, with its media type at `inlineData.mimeType`. A response may carry a text part alongside the image part, so select the part by the field you need rather than by index.\n`uploadImagesToStorage` CHANGES THIS SHAPE. When the request sets it, the proxy uploads each generated image to Comfy storage and replaces the part's `inlineData` with `fileData`, carrying a Comfy-signed URL at `fileData.fileUri` and the media type at `fileData.mimeType`; `inlineData` is then absent for that part. The URL expires 24 hours after it is minted, so download the image rather than storing the link. An image whose upload fails is left as `inlineData`, so ONE response can mix both shapes - branch on which key is present rather than assuming either.\nRouter does not re-host a partner link for this family: Gemini returns the image as bytes and no URL of its own, so `uploadImagesToStorage` is an upload of those bytes that the caller asks for, not a rewrite of somebody else's link. A request that leaves the field unset is answered as base64 from end to end WHENEVER VERTEX AI ITSELF SERVES IT - which is always the case for `vertexai/gemini-2.5-flash-image` and `vertexai/gemini-3.1-flash-lite-image`, the two ids on this component that have no other producer, and is the usual but not the only case for the two below.\n`vertexai/gemini-3-pro-image` (`fal`, `runware`, `wavespeed`) and `vertexai/gemini-3.1-flash-image` (`fal`, `runware`, `wavespeed`) have ALTERNATE PRODUCERS: an eligible request may be served by a partner instead of by Vertex AI. Two PUBLISHED routes reach one, both the alt-provider dispatch in `server/implementation/router_run.go`: `model_provider` names a partner explicitly, and `fallback_provider` (default ON) retries once against a registered alternate when the Vertex AI attempt fails for a reason attributable to Router or to Vertex AI rather than to the request. That retry is suppressed - whatever `fallback_provider` says - on a BYOK call, on a same-key idempotent replay, on a multipart edit operation, and when the model has no registered alternate. SEPARATELY, the `vertexai` partner proxy runs a fallback cascade of its own on these two ids when the platform-credential attempt fails: it is NOT governed by `fallback_provider`, it is disabled only for BYOK requests, and it can therefore fire on a failure the request itself caused and on a call that asked for `fallback_provider: false`. Treat a non-Vertex producer as possible on these two ids whatever the request asked for.\nA response the `fal`, `runware` or `wavespeed` leg served, translated back into this contract, carries every generated image as `fileData.fileUri` holding THE PARTNER'S OWN hosted URL, and never `inlineData.data` - all three partners return a hosted URL and never base64 bytes. The URL's lifetime and access policy are the partner's own rather than the 24-hour Comfy-signed URL described above, and Comfy publishes neither, so download the image promptly rather than storing the link. The proxy-level cascade above is NOT bound to that shape: it substitutes another provider's Gemini-compatible body, which may carry inline bytes instead. So the rule is the one the `uploadImagesToStorage` paragraph already states - branch on which key a part actually carries, never on a producer you inferred.\n`uploadImagesToStorage` does not change any of this. The flag is an upload of Vertex AI's own bytes, and off Vertex AI there are no such bytes to upload, so sending it neither produces `inlineData` nor rewrites the partner's link: it is simply inert on an alt-provider leg. A request that carries the key - whatever its value, `false` included - is told so: the translated alt-provider response lists `uploadImagesToStorage` in `X-Comfy-Router-Dropped-Params`, among the request fields the partner's schema has no place for. Only a translated response carries that disclosure: the proxy-level cascade and a `strict_mode: true` call run no translation and send no such header.\nTHREE RESPONSES SKIP THAT TRANSLATION and return the PARTNER'S OWN envelope rather than this component's: `strict_mode: true` asks for the alternate provider's raw schema by definition, a partner response that arrives with a non-empty `Content-Encoding` is forwarded unchanged because Router does not decompress a body to translate it, and a translation that fails or yields nothing forwards the partner's body unchanged - that body may carry no `candidates` key at all. An alt-provider response that IS translated back carries `candidates` and nothing else: `usageMetadata`, `promptFeedback`, `modelVersion`, `responseId` and `finishReason` are Vertex AI's own fields, and the translation has nothing to map them from.\nWHICH PRODUCER ANSWERED is disclosed more weakly than the shape itself. `X-Comfy-Router-Fallback-Provider` names the provider only on a response Router's OWN fallback retry served: the proxy-level cascade sets no header. A same-key idempotent replay repeats both that header and `X-Comfy-Router-Dropped-Params` exactly as the original call sent them, so a replay discloses what the original call did and no more. The request's own `model_provider` is reliable only in the direction it states - it says a partner was ASKED for, not that none answered when it was absent. The published per-model document of a model with a PUBLISHED leg carries an `x-comfy-router-alt-providers` marker naming it; absence of that marker is NOT a guarantee that the model has no alternate producer, since a registered leg that is not cleared for public disclosure still dispatches without appearing there. Which partner served a given request is a Comfy routing decision that can change without notice, and is not part of this contract.\nAs for the text models, `promptFeedback` and `usageMetadata` are present on a safety-blocked response that generated nothing; `candidates` is the field to branch on. Both are Vertex AI's own fields: on a response an alternate producer served, see the paragraph above for what is absent.\nThe example's `inlineData.data` is the PLACEHOLDER `PGJhc2U2ND4=`, which decodes to the literal text `\u003cbase64\u003e` - a real image would be megabytes, and a real provider response does not belong in a published spec. It is spelled as valid base64 rather than as a bare `\u003cbase64\u003e` marker because `GeminiInlineData.data` declares `format: byte`, which this document's own example validation enforces.\nThe example is authored ONCE for all four ids above and rendered into each per-model document unchanged, so its `modelVersion` is ILLUSTRATIVE RATHER THAN ENDPOINT-SPECIFIC. Vertex AI echoes the id that actually served the call, so a `vertexai/gemini-3-pro-image` response reads `gemini-3-pro-image` there - not the `gemini-2.5-flash-image` the example happens to show - and on an alt-provider-served response the field is absent entirely, per the paragraph above. Where `modelVersion` is present, read it as echoing the served id, never as the literal value printed in the document you are reading.","example":{"candidates":[{"content":{"parts":[{"inlineData":{"data":"PGJhc2U2ND4=","mimeType":"image/png"}}],"role":"model"},"finishReason":"STOP"}],"modelVersion":"gemini-2.5-flash-image","responseId":"7c6b5a49-3827-1605-f4e3-d2c1b0a99887","usageMetadata":{"candidatesTokenCount":1290,"promptTokenCount":11,"totalTokenCount":1301}}}}}}}}}},"components":{"schemas":{"GeminiCandidate":{"properties":{"citationMetadata":{"$ref":"#/components/schemas/GeminiCitationMetadata"},"content":{"$ref":"#/components/schemas/GeminiContent"},"finishReason":{"type":"string"},"safetyRatings":{"items":{"$ref":"#/components/schemas/GeminiSafetyRating"},"type":"array"}},"type":"object"},"GeminiCitation":{"properties":{"authors":{"items":{"type":"string"},"type":"array"},"endIndex":{"type":"integer"},"license":{"type":"string"},"publicationDate":{"format":"date","type":"string"},"startIndex":{"type":"integer"},"title":{"type":"string"},"uri":{"type":"string"}},"type":"object"},"GeminiCitationMetadata":{"properties":{"citations":{"items":{"$ref":"#/components/schemas/GeminiCitation"},"type":"array"}},"type":"object"},"GeminiContent":{"description":"The content of the current conversation with the model. For single-turn queries, this is a single instance. For multi-turn queries, this is a repeated field that contains conversation history and the latest request.\n","properties":{"parts":{"items":{"$ref":"#/components/schemas/GeminiPart"},"type":"array"},"role":{"enum":["user","model"],"example":"user","type":"string"}},"required":["parts"],"type":"object"},"GeminiFileData":{"description":"URI based data.","properties":{"fileUri":{"description":"URI","type":"string"},"mimeType":{"$ref":"#/components/schemas/GeminiMimeType"}},"type":"object"},"GeminiFunctionDeclaration":{"properties":{"description":{"type":"string"},"name":{"type":"string"},"parameters":{"description":"JSON schema for the function parameters","type":"object"}},"required":["name"],"type":"object"},"GeminiGenerateContentResponse":{"properties":{"candidates":{"items":{"$ref":"#/components/schemas/GeminiCandidate"},"type":"array"},"createTime":{"description":"Timestamp when the response was created.","type":"string"},"modelVersion":{"description":"The model version used to generate the response.","type":"string"},"promptFeedback":{"$ref":"#/components/schemas/GeminiPromptFeedback"},"responseId":{"description":"Unique identifier for the response.","type":"string"},"usageMetadata":{"$ref":"#/components/schemas/GeminiUsageMetadata"}},"type":"object"},"GeminiGenerationConfig":{"description":"Sampling, length and output settings for the generation. Every field is optional: the fields below that declare a `default` apply it when omitted, and the rest fall back to the model's own behaviour.\n","properties":{"imageConfig":{"description":"Configuration for image generation","properties":{"aspectRatio":{"description":"Aspect ratio for generated images","type":"string"},"imageOutputOptions":{"description":"Optional. The image output format for generated images.","properties":{"compressionQuality":{"description":"Optional. The compression quality of the output image.","type":"integer"},"mimeType":{"description":"Optional. The image format that the output should be saved as, on the Vertex AI paths: requests served by Comfy's own credentials, and BYOK requests authenticated with a GCP service account. Accepted values there are `image/png` and `image/jpeg`, matched case-insensitively and normalised to lower case before the request is forwarded; any other value is rejected with a 400 naming this field. Defaults to `image/png` when omitted. A BYOK request authenticated with a Google AI Studio API key is the exception: that upstream has no such property and rejects the whole call if it is present, so the field is dropped from the request rather than honoured or rejected, and the output format is whatever AI Studio chooses. On every path, read the media type back from the response part you are given (`inlineData.mimeType`, or `fileData.mimeType` when `uploadImagesToStorage` is set) rather than assuming the value you sent.","type":"string"}},"type":"object"},"imageSize":{"description":"Optional. Specifies the size of generated images. Supported values are 1K, 2K, 4K. If not specified, the model will use default value 1K.","type":"string"}},"type":"object"},"maxOutputTokens":{"description":"Maximum number of tokens that can be generated in the response. A token is approximately 4 characters. 100 tokens correspond to roughly 60-80 words.\n","example":2048,"maximum":65536,"minimum":16,"type":"integer"},"responseModalities":{"items":{"enum":["TEXT","IMAGE"],"type":"string"},"type":"array"},"seed":{"description":"When seed is fixed to a specific value, the model makes a best effort to provide the same response for repeated requests. Deterministic output isn't guaranteed. Also, changing the model or parameter settings, such as the temperature, can cause variations in the response even when you use the same seed value. By default, a random seed value is used. Available for the following models:, gemini-2.5-flash, gemini-2.5-pro, gemini-2.5-flash-preview-04-1, gemini-2.5-pro-preview-05-0, gemini-2.0-flash-lite-00, gemini-2.0-flash-001\n","example":343940597,"type":"integer"},"stopSequences":{"items":{"type":"string"},"type":"array"},"temperature":{"default":1,"description":"The temperature is used for sampling during response generation, which occurs when topP and topK are applied. Temperature controls the degree of randomness in token selection. Lower temperatures are good for prompts that require a less open-ended or creative response, while higher temperatures can lead to more diverse or creative results. A temperature of 0 means that the highest probability tokens are always selected. In this case, responses for a given prompt are mostly deterministic, but a small amount of variation is still possible. If the model returns a response that's too generic, too short, or the model gives a fallback response, try increasing the temperature\n","format":"float","maximum":2,"minimum":0,"type":"number"},"thinkingConfig":{"description":"Optional. Configuration for thinking features. Thinking is a process where the model breaks down a complex task into smaller steps to generate a higher-quality response.","properties":{"includeThoughts":{"description":"Optional. If true, the model will include its thoughts in the response.","type":"boolean"},"thinkingBudget":{"description":"Optional. The token budget for the model's thinking process. The model will make a best effort to stay within this budget.","type":"integer"},"thinkingLevel":{"description":"Optional. The thinking level for the model.","enum":["THINKING_LEVEL_UNSPECIFIED","LOW","MEDIUM","HIGH","MINIMAL"],"type":"string"}},"type":"object"},"topK":{"default":40,"description":"Top-K changes how the model selects tokens for output. A top-K of 1 means the next selected token is the most probable among all tokens in the model's vocabulary. A top-K of 3 means that the next token is selected from among the 3 most probable tokens by using temperature.\n","example":40,"minimum":1,"type":"integer"},"topP":{"default":0.95,"description":"If specified, nucleus sampling is used.\nTop-P changes how the model selects tokens for output. Tokens are selected from the most (see top-K) to least probable until the sum of their probabilities equals the top-P value. For example, if tokens A, B, and C have a probability of 0.3, 0.2, and 0.1 and the top-P value is 0.5, then the model will select either A or B as the next token by using temperature and excludes C as a candidate.\nSpecify a lower value for less random responses and a higher value for more random responses.\n","format":"float","maximum":1,"minimum":0,"type":"number"}},"type":"object"},"GeminiInlineData":{"description":"Inline data in raw bytes. For gemini-2.0-flash-lite and gemini-2.0-flash, you can specify up to 3000 images by using inlineData.\n","properties":{"data":{"description":"The base64 encoding of the image, PDF, or video to include inline in the prompt. When including media inline, you must also specify the media type (mimeType) of the data. Size limit: 20MB\n","format":"byte","type":"string"},"mimeType":{"$ref":"#/components/schemas/GeminiMimeType"}},"type":"object"},"GeminiMimeType":{"description":"The media type of the file specified in the data or fileUri fields. Acceptable values include the following. For gemini-2.0-flash-lite and gemini-2.0-flash, the maximum length of an audio file is 8.4 hours and the maximum length of a video file (without audio) is one hour. For more information, see Gemini audio and video requirements. Text files must be UTF-8 encoded. The contents of the text file count toward the token limit. There is no limit on image resolution.","enum":["application/pdf","audio/mpeg","audio/mp3","audio/wav","image/png","image/jpeg","image/webp","text/plain","video/mov","video/mpeg","video/mp4","video/mpg","video/avi","video/wmv","video/mpegps","video/flv","image/heic","image/heif","audio/flac","video/webm"],"type":"string"},"GeminiOffset":{"description":"Represents a duration offset for video timeline positions.\n","properties":{"nanos":{"description":"Signed fractions of a second at nanosecond resolution. Negative second values with fractions must still have non-negative nanos values.\n","example":0,"maximum":999999999,"minimum":0,"type":"integer"},"seconds":{"description":"Signed seconds of the span of time. Must be from -315,576,000,000 to +315,576,000,000 inclusive.\n","example":60,"maximum":315576000000,"minimum":-315576000000,"type":"integer"}},"type":"object"},"GeminiPart":{"properties":{"fileData":{"$ref":"#/components/schemas/GeminiFileData"},"inlineData":{"$ref":"#/components/schemas/GeminiInlineData"},"mediaProcessing":{"description":"How the model reads this part's video. Set \"AGENTIC\" to let the model decide which segments to inspect, instead of fixed-rate frame sampling. Omit for the default fixed-rate sampling. Supported on gemini-3.7-flash and newer Flash models.\n","type":"string"},"text":{"description":"A text prompt or code snippet.","example":"Write a story about a robot learning to paint","type":"string"},"thought":{"description":"Indicates this part is a thinking/reasoning step from the model.","type":"boolean"}},"type":"object"},"GeminiPromptFeedback":{"properties":{"blockReason":{"type":"string"},"blockReasonMessage":{"type":"string"},"safetyRatings":{"items":{"$ref":"#/components/schemas/GeminiSafetyRating"},"type":"array"}},"type":"object"},"GeminiSafetyCategory":{"enum":["HARM_CATEGORY_SEXUALLY_EXPLICIT","HARM_CATEGORY_HATE_SPEECH","HARM_CATEGORY_HARASSMENT","HARM_CATEGORY_DANGEROUS_CONTENT"],"type":"string"},"GeminiSafetyRating":{"properties":{"category":{"$ref":"#/components/schemas/GeminiSafetyCategory"},"probability":{"description":"The probability that the content violates the specified safety category","enum":["NEGLIGIBLE","LOW","MEDIUM","HIGH","UNKNOWN"],"type":"string"}},"type":"object"},"GeminiSafetySetting":{"description":"Per request settings for blocking unsafe content. Enforced on GenerateContentResponse.candidates.\n","properties":{"category":{"$ref":"#/components/schemas/GeminiSafetyCategory"},"threshold":{"$ref":"#/components/schemas/GeminiSafetyThreshold"}},"required":["category","threshold"],"type":"object"},"GeminiSafetyThreshold":{"enum":["OFF","BLOCK_NONE","BLOCK_LOW_AND_ABOVE","BLOCK_MEDIUM_AND_ABOVE","BLOCK_ONLY_HIGH"],"type":"string"},"GeminiSystemInstructionContent":{"description":"Instructions for the model to steer it toward better performance. For example, \"Answer as concisely as possible\" or \"Don't use technical terms in your response\". The text strings count toward the token limit. The role field of systemInstruction is ignored and doesn't affect the performance of the model. Note: Only text should be used in parts and content in each part should be in a separate paragraph.\n","properties":{"parts":{"description":"A list of ordered parts that make up a single message. Different parts may have different IANA MIME types. For limits on the inputs, such as the maximum number of tokens or the number of images, see the model specifications on the Google models page.\n","items":{"$ref":"#/components/schemas/GeminiTextPart"},"type":"array"},"role":{"description":"The identity of the entity that creates the message. The following values are supported: user: This indicates that the message is sent by a real person, typically a user-generated message. model: This indicates that the message is generated by the model. The model value is used to insert messages from the model into the conversation during multi-turn conversations. For non-multi-turn conversations, this field can be left blank or unset.\n","enum":["user","model"],"example":"user","type":"string"}},"required":["parts"],"type":"object"},"GeminiTextPart":{"properties":{"text":{"description":"A text prompt or code snippet.","example":"Answer as concisely as possible","type":"string"}},"type":"object"},"GeminiTool":{"description":"A piece of code that enables the system to interact with external systems to perform an action, or set of actions, outside of knowledge and scope of the model. See Function calling.\n","properties":{"functionDeclarations":{"items":{"$ref":"#/components/schemas/GeminiFunctionDeclaration"},"type":"array"}},"type":"object"},"GeminiUsageMetadata":{"properties":{"cachedContentTokenCount":{"description":"Output only. Number of tokens in the cached part in the input (the cached content).","type":"integer"},"candidatesTokenCount":{"description":"Number of tokens in the response(s).","type":"integer"},"candidatesTokensDetails":{"description":"Breakdown of candidate tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"promptTokenCount":{"description":"Number of tokens in the request. When cachedContent is set, this is still the total effective prompt size meaning this includes the number of tokens in the cached content.","type":"integer"},"promptTokensDetails":{"description":"Breakdown of prompt tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"thoughtsTokenCount":{"description":"Number of tokens present in thoughts output.","type":"integer"},"toolUsePromptTokenCount":{"description":"Number of tokens present in tool-use prompt(s).","type":"integer"},"toolUsePromptTokensDetails":{"description":"Breakdown of tool-use prompt tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"totalTokenCount":{"description":"Total number of tokens (prompt + candidates).","type":"integer"},"trafficType":{"description":"Traffic type used for the request (e.g., PROVISIONED_THROUGHPUT).","type":"string"}},"type":"object"},"GeminiVideoMetadata":{"description":"For video input, the start and end offset of the video in Duration format. For example, to specify a 10 second clip starting at 1:00, set \"startOffset\": { \"seconds\": 60 } and \"endOffset\": { \"seconds\": 70 }. The metadata should only be specified while the video data is presented in inlineData or fileData.\n","properties":{"endOffset":{"$ref":"#/components/schemas/GeminiOffset"},"startOffset":{"$ref":"#/components/schemas/GeminiOffset"}},"type":"object"},"Modality":{"description":"Type of input or output content modality.","enum":["MODALITY_UNSPECIFIED","TEXT","IMAGE","VIDEO","AUDIO","DOCUMENT"],"type":"string"},"ModalityTokenCount":{"properties":{"modality":{"$ref":"#/components/schemas/Modality"},"tokenCount":{"description":"Number of tokens for the given modality.","type":"integer"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"vertexai/gemini-3-pro-image","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image"],"x-comfy-router-output-modalities":["image","text"],"x-comfy-router-alt-providers":[{"provider":"fal","model_id":"fal/fal-nano-banana-pro"},{"provider":"runware","model_id":"runware/runware-nano-banana-pro"},{"provider":"wavespeed","model_id":"wavespeed/wavespeed-nano-banana-pro"}]}
+{"openapi":"3.0.2","info":{"title":"vertexai/gemini-3-pro-image","description":"The request body Comfy Router accepts for the model \"vertexai/gemini-3-pro-image\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"a86e35b5a353"},"paths":{"/v2/models/vertexai/gemini-3-pro-image":{"post":{"operationId":"runRouterModel","summary":"Run vertexai/gemini-3-pro-image synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"example":{"contents":[{"parts":[{"text":"Describe a robot learning to paint, in two sentences."}],"role":"user"}]},"properties":{"contents":{"items":{"$ref":"#/components/schemas/GeminiContent"},"type":"array"},"generationConfig":{"$ref":"#/components/schemas/GeminiGenerationConfig"},"safetySettings":{"items":{"$ref":"#/components/schemas/GeminiSafetySetting"},"type":"array"},"systemInstruction":{"$ref":"#/components/schemas/GeminiSystemInstructionContent"},"tools":{"items":{"$ref":"#/components/schemas/GeminiTool"},"type":"array"},"uploadImagesToStorage":{"description":"If true, generated images will be uploaded to cloud storage and returned as signed URLs instead of inline base64 data. The URLs expire after 24 hours.","type":"boolean"},"videoMetadata":{"$ref":"#/components/schemas/GeminiVideoMetadata"}},"required":["contents"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/GeminiGenerateContentResponse"}],"description":"Comfy Router output schema for the image-returning Gemini models: the same Vertex AI `generateContent` response the text models return, forwarded unchanged, but the generated image arrives as base64 bytes at `candidates[0].content.parts[0].inlineData.data`, with its media type at `inlineData.mimeType`. A response may carry a text part alongside the image part, so select the part by the field you need rather than by index.\n`uploadImagesToStorage` CHANGES THIS SHAPE. When the request sets it, the proxy uploads each generated image to Comfy storage and replaces the part's `inlineData` with `fileData`, carrying a Comfy-signed URL at `fileData.fileUri` and the media type at `fileData.mimeType`; `inlineData` is then absent for that part. The URL expires 24 hours after it is minted, so download the image rather than storing the link. An image whose upload fails is left as `inlineData`, so ONE response can mix both shapes - branch on which key is present rather than assuming either.\nRouter does not re-host a partner link for this family: Gemini returns the image as bytes and no URL of its own, so `uploadImagesToStorage` is an upload of those bytes that the caller asks for, not a rewrite of somebody else's link. A request that leaves the field unset is answered as base64 from end to end WHENEVER VERTEX AI ITSELF SERVES IT - which is always the case for `vertexai/gemini-2.5-flash-image` and `vertexai/gemini-3.1-flash-lite-image`, the two ids on this component that have no other producer, and is the usual but not the only case for the two below.\n`vertexai/gemini-3-pro-image` (`fal`, `pika`, `runware`, `wavespeed`) and `vertexai/gemini-3.1-flash-image` (`fal`, `pika`, `runware`, `wavespeed`) have ALTERNATE PRODUCERS: an eligible request may be served by a partner instead of by Vertex AI. Two PUBLISHED routes reach one, both the alt-provider dispatch in `server/implementation/router_run.go`: `model_provider` names a partner explicitly, and `fallback_provider` (default ON) retries once against a registered alternate when the Vertex AI attempt fails for a reason attributable to Router or to Vertex AI rather than to the request. That retry is suppressed - whatever `fallback_provider` says - on a BYOK call, on a same-key idempotent replay, on a multipart edit operation, and when the model has no registered alternate. SEPARATELY, the `vertexai` partner proxy runs a fallback cascade of its own on these two ids when the platform-credential attempt fails: it is NOT governed by `fallback_provider`, it is disabled only for BYOK requests, and it can therefore fire on a failure the request itself caused and on a call that asked for `fallback_provider: false`. Treat a non-Vertex producer as possible on these two ids whatever the request asked for.\nA response the `fal`, `pika`, `runware` or `wavespeed` leg served, translated back into this contract, carries every generated image as `fileData.fileUri` holding THE PARTNER'S OWN hosted URL, and never `inlineData.data` - all four partners return a hosted URL and never base64 bytes. The URL's lifetime and access policy are the partner's own rather than the 24-hour Comfy-signed URL described above, and Comfy publishes neither, so download the image promptly rather than storing the link. The proxy-level cascade above is NOT bound to that shape: it substitutes another provider's Gemini-compatible body, which may carry inline bytes instead. So the rule is the one the `uploadImagesToStorage` paragraph already states - branch on which key a part actually carries, never on a producer you inferred.\n`uploadImagesToStorage` does not change any of this. The flag is an upload of Vertex AI's own bytes, and off Vertex AI there are no such bytes to upload, so sending it neither produces `inlineData` nor rewrites the partner's link: it is simply inert on an alt-provider leg. A request that carries the key - whatever its value, `false` included - is told so: the translated alt-provider response lists `uploadImagesToStorage` in `X-Comfy-Router-Dropped-Params`, among the request fields the partner's schema has no place for. Only a translated response carries that disclosure: the proxy-level cascade and a `strict_mode: true` call run no translation and send no such header.\nTHREE RESPONSES SKIP THAT TRANSLATION and return the PARTNER'S OWN envelope rather than this component's: `strict_mode: true` asks for the alternate provider's raw schema by definition, a partner response that arrives with a non-empty `Content-Encoding` is forwarded unchanged because Router does not decompress a body to translate it, and a translation that fails or yields nothing forwards the partner's body unchanged - that body may carry no `candidates` key at all. An alt-provider response that IS translated back carries `candidates` and nothing else: `usageMetadata`, `promptFeedback`, `modelVersion`, `responseId` and `finishReason` are Vertex AI's own fields, and the translation has nothing to map them from.\nWHICH PRODUCER ANSWERED is disclosed more weakly than the shape itself. `X-Comfy-Router-Fallback-Provider` names the provider only on a response Router's OWN fallback retry served: the proxy-level cascade sets no header. A same-key idempotent replay repeats both that header and `X-Comfy-Router-Dropped-Params` exactly as the original call sent them, so a replay discloses what the original call did and no more. The request's own `model_provider` is reliable only in the direction it states - it says a partner was ASKED for, not that none answered when it was absent. The published per-model document of a model with a PUBLISHED leg carries an `x-comfy-router-alt-providers` marker naming it; absence of that marker is NOT a guarantee that the model has no alternate producer, since a registered leg that is not cleared for public disclosure still dispatches without appearing there. Which partner served a given request is a Comfy routing decision that can change without notice, and is not part of this contract.\nAs for the text models, `promptFeedback` and `usageMetadata` are present on a safety-blocked response that generated nothing; `candidates` is the field to branch on. Both are Vertex AI's own fields: on a response an alternate producer served, see the paragraph above for what is absent.\nThe example's `inlineData.data` is the PLACEHOLDER `PGJhc2U2ND4=`, which decodes to the literal text `\u003cbase64\u003e` - a real image would be megabytes, and a real provider response does not belong in a published spec. It is spelled as valid base64 rather than as a bare `\u003cbase64\u003e` marker because `GeminiInlineData.data` declares `format: byte`, which this document's own example validation enforces.\nThe example is authored ONCE for all four ids above and rendered into each per-model document unchanged, so its `modelVersion` is ILLUSTRATIVE RATHER THAN ENDPOINT-SPECIFIC. Vertex AI echoes the id that actually served the call, so a `vertexai/gemini-3-pro-image` response reads `gemini-3-pro-image` there - not the `gemini-2.5-flash-image` the example happens to show - and on an alt-provider-served response the field is absent entirely, per the paragraph above. Where `modelVersion` is present, read it as echoing the served id, never as the literal value printed in the document you are reading.","example":{"candidates":[{"content":{"parts":[{"inlineData":{"data":"PGJhc2U2ND4=","mimeType":"image/png"}}],"role":"model"},"finishReason":"STOP"}],"modelVersion":"gemini-2.5-flash-image","responseId":"7c6b5a49-3827-1605-f4e3-d2c1b0a99887","usageMetadata":{"candidatesTokenCount":1290,"promptTokenCount":11,"totalTokenCount":1301}}}}}}}}}},"components":{"schemas":{"GeminiCandidate":{"properties":{"citationMetadata":{"$ref":"#/components/schemas/GeminiCitationMetadata"},"content":{"$ref":"#/components/schemas/GeminiContent"},"finishReason":{"type":"string"},"safetyRatings":{"items":{"$ref":"#/components/schemas/GeminiSafetyRating"},"type":"array"}},"type":"object"},"GeminiCitation":{"properties":{"authors":{"items":{"type":"string"},"type":"array"},"endIndex":{"type":"integer"},"license":{"type":"string"},"publicationDate":{"format":"date","type":"string"},"startIndex":{"type":"integer"},"title":{"type":"string"},"uri":{"type":"string"}},"type":"object"},"GeminiCitationMetadata":{"properties":{"citations":{"items":{"$ref":"#/components/schemas/GeminiCitation"},"type":"array"}},"type":"object"},"GeminiContent":{"description":"The content of the current conversation with the model. For single-turn queries, this is a single instance. For multi-turn queries, this is a repeated field that contains conversation history and the latest request.\n","properties":{"parts":{"items":{"$ref":"#/components/schemas/GeminiPart"},"type":"array"},"role":{"enum":["user","model"],"example":"user","type":"string"}},"required":["parts"],"type":"object"},"GeminiFileData":{"description":"URI based data.","properties":{"fileUri":{"description":"URI","type":"string"},"mimeType":{"$ref":"#/components/schemas/GeminiMimeType"}},"type":"object"},"GeminiFunctionDeclaration":{"properties":{"description":{"type":"string"},"name":{"type":"string"},"parameters":{"description":"JSON schema for the function parameters","type":"object"}},"required":["name"],"type":"object"},"GeminiGenerateContentResponse":{"properties":{"candidates":{"items":{"$ref":"#/components/schemas/GeminiCandidate"},"type":"array"},"createTime":{"description":"Timestamp when the response was created.","type":"string"},"modelVersion":{"description":"The model version used to generate the response.","type":"string"},"promptFeedback":{"$ref":"#/components/schemas/GeminiPromptFeedback"},"responseId":{"description":"Unique identifier for the response.","type":"string"},"usageMetadata":{"$ref":"#/components/schemas/GeminiUsageMetadata"}},"type":"object"},"GeminiGenerationConfig":{"description":"Sampling, length and output settings for the generation. Every field is optional: the fields below that declare a `default` apply it when omitted, and the rest fall back to the model's own behaviour.\n","properties":{"imageConfig":{"description":"Configuration for image generation","properties":{"aspectRatio":{"description":"Aspect ratio for generated images","type":"string"},"imageOutputOptions":{"description":"Optional. The image output format for generated images.","properties":{"compressionQuality":{"description":"Optional. The compression quality of the output image.","type":"integer"},"mimeType":{"description":"Optional. The image format that the output should be saved as, on the Vertex AI paths: requests served by Comfy's own credentials, and BYOK requests authenticated with a GCP service account. Accepted values there are `image/png` and `image/jpeg`, matched case-insensitively and normalised to lower case before the request is forwarded; any other value is rejected with a 400 naming this field. Defaults to `image/png` when omitted. A BYOK request authenticated with a Google AI Studio API key is the exception: that upstream has no such property and rejects the whole call if it is present, so the field is dropped from the request rather than honoured or rejected, and the output format is whatever AI Studio chooses. On every path, read the media type back from the response part you are given (`inlineData.mimeType`, or `fileData.mimeType` when `uploadImagesToStorage` is set) rather than assuming the value you sent.","type":"string"}},"type":"object"},"imageSize":{"description":"Optional. Specifies the size of generated images. Supported values are 1K, 2K, 4K. If not specified, the model will use default value 1K.","type":"string"}},"type":"object"},"maxOutputTokens":{"description":"Maximum number of tokens that can be generated in the response. A token is approximately 4 characters. 100 tokens correspond to roughly 60-80 words.\n","example":2048,"maximum":65536,"minimum":16,"type":"integer"},"responseModalities":{"items":{"enum":["TEXT","IMAGE"],"type":"string"},"type":"array"},"seed":{"description":"When seed is fixed to a specific value, the model makes a best effort to provide the same response for repeated requests. Deterministic output isn't guaranteed. Also, changing the model or parameter settings, such as the temperature, can cause variations in the response even when you use the same seed value. By default, a random seed value is used. Available for the following models:, gemini-2.5-flash, gemini-2.5-pro, gemini-2.5-flash-preview-04-1, gemini-2.5-pro-preview-05-0, gemini-2.0-flash-lite-00, gemini-2.0-flash-001\n","example":343940597,"type":"integer"},"stopSequences":{"items":{"type":"string"},"type":"array"},"temperature":{"default":1,"description":"The temperature is used for sampling during response generation, which occurs when topP and topK are applied. Temperature controls the degree of randomness in token selection. Lower temperatures are good for prompts that require a less open-ended or creative response, while higher temperatures can lead to more diverse or creative results. A temperature of 0 means that the highest probability tokens are always selected. In this case, responses for a given prompt are mostly deterministic, but a small amount of variation is still possible. If the model returns a response that's too generic, too short, or the model gives a fallback response, try increasing the temperature\n","format":"float","maximum":2,"minimum":0,"type":"number"},"thinkingConfig":{"description":"Optional. Configuration for thinking features. Thinking is a process where the model breaks down a complex task into smaller steps to generate a higher-quality response.","properties":{"includeThoughts":{"description":"Optional. If true, the model will include its thoughts in the response.","type":"boolean"},"thinkingBudget":{"description":"Optional. The token budget for the model's thinking process. The model will make a best effort to stay within this budget.","type":"integer"},"thinkingLevel":{"description":"Optional. The thinking level for the model.","enum":["THINKING_LEVEL_UNSPECIFIED","LOW","MEDIUM","HIGH","MINIMAL"],"type":"string"}},"type":"object"},"topK":{"default":40,"description":"Top-K changes how the model selects tokens for output. A top-K of 1 means the next selected token is the most probable among all tokens in the model's vocabulary. A top-K of 3 means that the next token is selected from among the 3 most probable tokens by using temperature.\n","example":40,"minimum":1,"type":"integer"},"topP":{"default":0.95,"description":"If specified, nucleus sampling is used.\nTop-P changes how the model selects tokens for output. Tokens are selected from the most (see top-K) to least probable until the sum of their probabilities equals the top-P value. For example, if tokens A, B, and C have a probability of 0.3, 0.2, and 0.1 and the top-P value is 0.5, then the model will select either A or B as the next token by using temperature and excludes C as a candidate.\nSpecify a lower value for less random responses and a higher value for more random responses.\n","format":"float","maximum":1,"minimum":0,"type":"number"}},"type":"object"},"GeminiInlineData":{"description":"Inline data in raw bytes. For gemini-2.0-flash-lite and gemini-2.0-flash, you can specify up to 3000 images by using inlineData.\n","properties":{"data":{"description":"The base64 encoding of the image, PDF, or video to include inline in the prompt. When including media inline, you must also specify the media type (mimeType) of the data. Size limit: 20MB\n","format":"byte","type":"string"},"mimeType":{"$ref":"#/components/schemas/GeminiMimeType"}},"type":"object"},"GeminiMimeType":{"description":"The media type of the file specified in the data or fileUri fields. Acceptable values include the following. For gemini-2.0-flash-lite and gemini-2.0-flash, the maximum length of an audio file is 8.4 hours and the maximum length of a video file (without audio) is one hour. For more information, see Gemini audio and video requirements. Text files must be UTF-8 encoded. The contents of the text file count toward the token limit. There is no limit on image resolution.","enum":["application/pdf","audio/mpeg","audio/mp3","audio/wav","image/png","image/jpeg","image/webp","text/plain","video/mov","video/mpeg","video/mp4","video/mpg","video/avi","video/wmv","video/mpegps","video/flv","image/heic","image/heif","audio/flac","video/webm"],"type":"string"},"GeminiOffset":{"description":"Represents a duration offset for video timeline positions.\n","properties":{"nanos":{"description":"Signed fractions of a second at nanosecond resolution. Negative second values with fractions must still have non-negative nanos values.\n","example":0,"maximum":999999999,"minimum":0,"type":"integer"},"seconds":{"description":"Signed seconds of the span of time. Must be from -315,576,000,000 to +315,576,000,000 inclusive.\n","example":60,"maximum":315576000000,"minimum":-315576000000,"type":"integer"}},"type":"object"},"GeminiPart":{"properties":{"fileData":{"$ref":"#/components/schemas/GeminiFileData"},"inlineData":{"$ref":"#/components/schemas/GeminiInlineData"},"mediaProcessing":{"description":"How the model reads this part's video. Set \"AGENTIC\" to let the model decide which segments to inspect, instead of fixed-rate frame sampling. Omit for the default fixed-rate sampling. Supported on gemini-3.7-flash and newer Flash models.\n","type":"string"},"text":{"description":"A text prompt or code snippet.","example":"Write a story about a robot learning to paint","type":"string"},"thought":{"description":"Indicates this part is a thinking/reasoning step from the model.","type":"boolean"}},"type":"object"},"GeminiPromptFeedback":{"properties":{"blockReason":{"type":"string"},"blockReasonMessage":{"type":"string"},"safetyRatings":{"items":{"$ref":"#/components/schemas/GeminiSafetyRating"},"type":"array"}},"type":"object"},"GeminiSafetyCategory":{"enum":["HARM_CATEGORY_SEXUALLY_EXPLICIT","HARM_CATEGORY_HATE_SPEECH","HARM_CATEGORY_HARASSMENT","HARM_CATEGORY_DANGEROUS_CONTENT"],"type":"string"},"GeminiSafetyRating":{"properties":{"category":{"$ref":"#/components/schemas/GeminiSafetyCategory"},"probability":{"description":"The probability that the content violates the specified safety category","enum":["NEGLIGIBLE","LOW","MEDIUM","HIGH","UNKNOWN"],"type":"string"}},"type":"object"},"GeminiSafetySetting":{"description":"Per request settings for blocking unsafe content. Enforced on GenerateContentResponse.candidates.\n","properties":{"category":{"$ref":"#/components/schemas/GeminiSafetyCategory"},"threshold":{"$ref":"#/components/schemas/GeminiSafetyThreshold"}},"required":["category","threshold"],"type":"object"},"GeminiSafetyThreshold":{"enum":["OFF","BLOCK_NONE","BLOCK_LOW_AND_ABOVE","BLOCK_MEDIUM_AND_ABOVE","BLOCK_ONLY_HIGH"],"type":"string"},"GeminiSystemInstructionContent":{"description":"Instructions for the model to steer it toward better performance. For example, \"Answer as concisely as possible\" or \"Don't use technical terms in your response\". The text strings count toward the token limit. The role field of systemInstruction is ignored and doesn't affect the performance of the model. Note: Only text should be used in parts and content in each part should be in a separate paragraph.\n","properties":{"parts":{"description":"A list of ordered parts that make up a single message. Different parts may have different IANA MIME types. For limits on the inputs, such as the maximum number of tokens or the number of images, see the model specifications on the Google models page.\n","items":{"$ref":"#/components/schemas/GeminiTextPart"},"type":"array"},"role":{"description":"The identity of the entity that creates the message. The following values are supported: user: This indicates that the message is sent by a real person, typically a user-generated message. model: This indicates that the message is generated by the model. The model value is used to insert messages from the model into the conversation during multi-turn conversations. For non-multi-turn conversations, this field can be left blank or unset.\n","enum":["user","model"],"example":"user","type":"string"}},"required":["parts"],"type":"object"},"GeminiTextPart":{"properties":{"text":{"description":"A text prompt or code snippet.","example":"Answer as concisely as possible","type":"string"}},"type":"object"},"GeminiTool":{"description":"A piece of code that enables the system to interact with external systems to perform an action, or set of actions, outside of knowledge and scope of the model. See Function calling.\n","properties":{"functionDeclarations":{"items":{"$ref":"#/components/schemas/GeminiFunctionDeclaration"},"type":"array"}},"type":"object"},"GeminiUsageMetadata":{"properties":{"cachedContentTokenCount":{"description":"Output only. Number of tokens in the cached part in the input (the cached content).","type":"integer"},"candidatesTokenCount":{"description":"Number of tokens in the response(s).","type":"integer"},"candidatesTokensDetails":{"description":"Breakdown of candidate tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"promptTokenCount":{"description":"Number of tokens in the request. When cachedContent is set, this is still the total effective prompt size meaning this includes the number of tokens in the cached content.","type":"integer"},"promptTokensDetails":{"description":"Breakdown of prompt tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"thoughtsTokenCount":{"description":"Number of tokens present in thoughts output.","type":"integer"},"toolUsePromptTokenCount":{"description":"Number of tokens present in tool-use prompt(s).","type":"integer"},"toolUsePromptTokensDetails":{"description":"Breakdown of tool-use prompt tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"totalTokenCount":{"description":"Total number of tokens (prompt + candidates).","type":"integer"},"trafficType":{"description":"Traffic type used for the request (e.g., PROVISIONED_THROUGHPUT).","type":"string"}},"type":"object"},"GeminiVideoMetadata":{"description":"For video input, the start and end offset of the video in Duration format. For example, to specify a 10 second clip starting at 1:00, set \"startOffset\": { \"seconds\": 60 } and \"endOffset\": { \"seconds\": 70 }. The metadata should only be specified while the video data is presented in inlineData or fileData.\n","properties":{"endOffset":{"$ref":"#/components/schemas/GeminiOffset"},"startOffset":{"$ref":"#/components/schemas/GeminiOffset"}},"type":"object"},"Modality":{"description":"Type of input or output content modality.","enum":["MODALITY_UNSPECIFIED","TEXT","IMAGE","VIDEO","AUDIO","DOCUMENT"],"type":"string"},"ModalityTokenCount":{"properties":{"modality":{"$ref":"#/components/schemas/Modality"},"tokenCount":{"description":"Number of tokens for the given modality.","type":"integer"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"vertexai/gemini-3-pro-image","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image"],"x-comfy-router-output-modalities":["image","text"],"x-comfy-router-alt-providers":[{"provider":"fal","model_id":"fal/fal-nano-banana-pro"},{"provider":"pika","model_id":"pika/pika-nano-banana-pro"},{"provider":"runware","model_id":"runware/runware-nano-banana-pro"},{"provider":"wavespeed","model_id":"wavespeed/wavespeed-nano-banana-pro"}]}
diff --git a/router-schemas/vertexai/gemini-3.1-flash-image.json b/router-schemas/vertexai/gemini-3.1-flash-image.json
index d6afe571df..4f5d3f4367 100644
--- a/router-schemas/vertexai/gemini-3.1-flash-image.json
+++ b/router-schemas/vertexai/gemini-3.1-flash-image.json
@@ -1 +1 @@
-{"openapi":"3.0.2","info":{"title":"vertexai/gemini-3.1-flash-image","description":"The request body Comfy Router accepts for the model \"vertexai/gemini-3.1-flash-image\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"4fddddb86113"},"paths":{"/v2/models/vertexai/gemini-3.1-flash-image":{"post":{"operationId":"runRouterModel","summary":"Run vertexai/gemini-3.1-flash-image synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"example":{"contents":[{"parts":[{"text":"Describe a robot learning to paint, in two sentences."}],"role":"user"}]},"properties":{"contents":{"items":{"$ref":"#/components/schemas/GeminiContent"},"type":"array"},"generationConfig":{"$ref":"#/components/schemas/GeminiGenerationConfig"},"safetySettings":{"items":{"$ref":"#/components/schemas/GeminiSafetySetting"},"type":"array"},"systemInstruction":{"$ref":"#/components/schemas/GeminiSystemInstructionContent"},"tools":{"items":{"$ref":"#/components/schemas/GeminiTool"},"type":"array"},"uploadImagesToStorage":{"description":"If true, generated images will be uploaded to cloud storage and returned as signed URLs instead of inline base64 data. The URLs expire after 24 hours.","type":"boolean"},"videoMetadata":{"$ref":"#/components/schemas/GeminiVideoMetadata"}},"required":["contents"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/GeminiGenerateContentResponse"}],"description":"Comfy Router output schema for the image-returning Gemini models: the same Vertex AI `generateContent` response the text models return, forwarded unchanged, but the generated image arrives as base64 bytes at `candidates[0].content.parts[0].inlineData.data`, with its media type at `inlineData.mimeType`. A response may carry a text part alongside the image part, so select the part by the field you need rather than by index.\n`uploadImagesToStorage` CHANGES THIS SHAPE. When the request sets it, the proxy uploads each generated image to Comfy storage and replaces the part's `inlineData` with `fileData`, carrying a Comfy-signed URL at `fileData.fileUri` and the media type at `fileData.mimeType`; `inlineData` is then absent for that part. The URL expires 24 hours after it is minted, so download the image rather than storing the link. An image whose upload fails is left as `inlineData`, so ONE response can mix both shapes - branch on which key is present rather than assuming either.\nRouter does not re-host a partner link for this family: Gemini returns the image as bytes and no URL of its own, so `uploadImagesToStorage` is an upload of those bytes that the caller asks for, not a rewrite of somebody else's link. A request that leaves the field unset is answered as base64 from end to end WHENEVER VERTEX AI ITSELF SERVES IT - which is always the case for `vertexai/gemini-2.5-flash-image` and `vertexai/gemini-3.1-flash-lite-image`, the two ids on this component that have no other producer, and is the usual but not the only case for the two below.\n`vertexai/gemini-3-pro-image` (`fal`, `runware`, `wavespeed`) and `vertexai/gemini-3.1-flash-image` (`fal`, `runware`, `wavespeed`) have ALTERNATE PRODUCERS: an eligible request may be served by a partner instead of by Vertex AI. Two PUBLISHED routes reach one, both the alt-provider dispatch in `server/implementation/router_run.go`: `model_provider` names a partner explicitly, and `fallback_provider` (default ON) retries once against a registered alternate when the Vertex AI attempt fails for a reason attributable to Router or to Vertex AI rather than to the request. That retry is suppressed - whatever `fallback_provider` says - on a BYOK call, on a same-key idempotent replay, on a multipart edit operation, and when the model has no registered alternate. SEPARATELY, the `vertexai` partner proxy runs a fallback cascade of its own on these two ids when the platform-credential attempt fails: it is NOT governed by `fallback_provider`, it is disabled only for BYOK requests, and it can therefore fire on a failure the request itself caused and on a call that asked for `fallback_provider: false`. Treat a non-Vertex producer as possible on these two ids whatever the request asked for.\nA response the `fal`, `runware` or `wavespeed` leg served, translated back into this contract, carries every generated image as `fileData.fileUri` holding THE PARTNER'S OWN hosted URL, and never `inlineData.data` - all three partners return a hosted URL and never base64 bytes. The URL's lifetime and access policy are the partner's own rather than the 24-hour Comfy-signed URL described above, and Comfy publishes neither, so download the image promptly rather than storing the link. The proxy-level cascade above is NOT bound to that shape: it substitutes another provider's Gemini-compatible body, which may carry inline bytes instead. So the rule is the one the `uploadImagesToStorage` paragraph already states - branch on which key a part actually carries, never on a producer you inferred.\n`uploadImagesToStorage` does not change any of this. The flag is an upload of Vertex AI's own bytes, and off Vertex AI there are no such bytes to upload, so sending it neither produces `inlineData` nor rewrites the partner's link: it is simply inert on an alt-provider leg. A request that carries the key - whatever its value, `false` included - is told so: the translated alt-provider response lists `uploadImagesToStorage` in `X-Comfy-Router-Dropped-Params`, among the request fields the partner's schema has no place for. Only a translated response carries that disclosure: the proxy-level cascade and a `strict_mode: true` call run no translation and send no such header.\nTHREE RESPONSES SKIP THAT TRANSLATION and return the PARTNER'S OWN envelope rather than this component's: `strict_mode: true` asks for the alternate provider's raw schema by definition, a partner response that arrives with a non-empty `Content-Encoding` is forwarded unchanged because Router does not decompress a body to translate it, and a translation that fails or yields nothing forwards the partner's body unchanged - that body may carry no `candidates` key at all. An alt-provider response that IS translated back carries `candidates` and nothing else: `usageMetadata`, `promptFeedback`, `modelVersion`, `responseId` and `finishReason` are Vertex AI's own fields, and the translation has nothing to map them from.\nWHICH PRODUCER ANSWERED is disclosed more weakly than the shape itself. `X-Comfy-Router-Fallback-Provider` names the provider only on a response Router's OWN fallback retry served: the proxy-level cascade sets no header. A same-key idempotent replay repeats both that header and `X-Comfy-Router-Dropped-Params` exactly as the original call sent them, so a replay discloses what the original call did and no more. The request's own `model_provider` is reliable only in the direction it states - it says a partner was ASKED for, not that none answered when it was absent. The published per-model document of a model with a PUBLISHED leg carries an `x-comfy-router-alt-providers` marker naming it; absence of that marker is NOT a guarantee that the model has no alternate producer, since a registered leg that is not cleared for public disclosure still dispatches without appearing there. Which partner served a given request is a Comfy routing decision that can change without notice, and is not part of this contract.\nAs for the text models, `promptFeedback` and `usageMetadata` are present on a safety-blocked response that generated nothing; `candidates` is the field to branch on. Both are Vertex AI's own fields: on a response an alternate producer served, see the paragraph above for what is absent.\nThe example's `inlineData.data` is the PLACEHOLDER `PGJhc2U2ND4=`, which decodes to the literal text `\u003cbase64\u003e` - a real image would be megabytes, and a real provider response does not belong in a published spec. It is spelled as valid base64 rather than as a bare `\u003cbase64\u003e` marker because `GeminiInlineData.data` declares `format: byte`, which this document's own example validation enforces.\nThe example is authored ONCE for all four ids above and rendered into each per-model document unchanged, so its `modelVersion` is ILLUSTRATIVE RATHER THAN ENDPOINT-SPECIFIC. Vertex AI echoes the id that actually served the call, so a `vertexai/gemini-3-pro-image` response reads `gemini-3-pro-image` there - not the `gemini-2.5-flash-image` the example happens to show - and on an alt-provider-served response the field is absent entirely, per the paragraph above. Where `modelVersion` is present, read it as echoing the served id, never as the literal value printed in the document you are reading.","example":{"candidates":[{"content":{"parts":[{"inlineData":{"data":"PGJhc2U2ND4=","mimeType":"image/png"}}],"role":"model"},"finishReason":"STOP"}],"modelVersion":"gemini-2.5-flash-image","responseId":"7c6b5a49-3827-1605-f4e3-d2c1b0a99887","usageMetadata":{"candidatesTokenCount":1290,"promptTokenCount":11,"totalTokenCount":1301}}}}}}}}}},"components":{"schemas":{"GeminiCandidate":{"properties":{"citationMetadata":{"$ref":"#/components/schemas/GeminiCitationMetadata"},"content":{"$ref":"#/components/schemas/GeminiContent"},"finishReason":{"type":"string"},"safetyRatings":{"items":{"$ref":"#/components/schemas/GeminiSafetyRating"},"type":"array"}},"type":"object"},"GeminiCitation":{"properties":{"authors":{"items":{"type":"string"},"type":"array"},"endIndex":{"type":"integer"},"license":{"type":"string"},"publicationDate":{"format":"date","type":"string"},"startIndex":{"type":"integer"},"title":{"type":"string"},"uri":{"type":"string"}},"type":"object"},"GeminiCitationMetadata":{"properties":{"citations":{"items":{"$ref":"#/components/schemas/GeminiCitation"},"type":"array"}},"type":"object"},"GeminiContent":{"description":"The content of the current conversation with the model. For single-turn queries, this is a single instance. For multi-turn queries, this is a repeated field that contains conversation history and the latest request.\n","properties":{"parts":{"items":{"$ref":"#/components/schemas/GeminiPart"},"type":"array"},"role":{"enum":["user","model"],"example":"user","type":"string"}},"required":["parts"],"type":"object"},"GeminiFileData":{"description":"URI based data.","properties":{"fileUri":{"description":"URI","type":"string"},"mimeType":{"$ref":"#/components/schemas/GeminiMimeType"}},"type":"object"},"GeminiFunctionDeclaration":{"properties":{"description":{"type":"string"},"name":{"type":"string"},"parameters":{"description":"JSON schema for the function parameters","type":"object"}},"required":["name"],"type":"object"},"GeminiGenerateContentResponse":{"properties":{"candidates":{"items":{"$ref":"#/components/schemas/GeminiCandidate"},"type":"array"},"createTime":{"description":"Timestamp when the response was created.","type":"string"},"modelVersion":{"description":"The model version used to generate the response.","type":"string"},"promptFeedback":{"$ref":"#/components/schemas/GeminiPromptFeedback"},"responseId":{"description":"Unique identifier for the response.","type":"string"},"usageMetadata":{"$ref":"#/components/schemas/GeminiUsageMetadata"}},"type":"object"},"GeminiGenerationConfig":{"description":"Sampling, length and output settings for the generation. Every field is optional: the fields below that declare a `default` apply it when omitted, and the rest fall back to the model's own behaviour.\n","properties":{"imageConfig":{"description":"Configuration for image generation","properties":{"aspectRatio":{"description":"Aspect ratio for generated images","type":"string"},"imageOutputOptions":{"description":"Optional. The image output format for generated images.","properties":{"compressionQuality":{"description":"Optional. The compression quality of the output image.","type":"integer"},"mimeType":{"description":"Optional. The image format that the output should be saved as, on the Vertex AI paths: requests served by Comfy's own credentials, and BYOK requests authenticated with a GCP service account. Accepted values there are `image/png` and `image/jpeg`, matched case-insensitively and normalised to lower case before the request is forwarded; any other value is rejected with a 400 naming this field. Defaults to `image/png` when omitted. A BYOK request authenticated with a Google AI Studio API key is the exception: that upstream has no such property and rejects the whole call if it is present, so the field is dropped from the request rather than honoured or rejected, and the output format is whatever AI Studio chooses. On every path, read the media type back from the response part you are given (`inlineData.mimeType`, or `fileData.mimeType` when `uploadImagesToStorage` is set) rather than assuming the value you sent.","type":"string"}},"type":"object"},"imageSize":{"description":"Optional. Specifies the size of generated images. Supported values are 1K, 2K, 4K. If not specified, the model will use default value 1K.","type":"string"}},"type":"object"},"maxOutputTokens":{"description":"Maximum number of tokens that can be generated in the response. A token is approximately 4 characters. 100 tokens correspond to roughly 60-80 words.\n","example":2048,"maximum":65536,"minimum":16,"type":"integer"},"responseModalities":{"items":{"enum":["TEXT","IMAGE"],"type":"string"},"type":"array"},"seed":{"description":"When seed is fixed to a specific value, the model makes a best effort to provide the same response for repeated requests. Deterministic output isn't guaranteed. Also, changing the model or parameter settings, such as the temperature, can cause variations in the response even when you use the same seed value. By default, a random seed value is used. Available for the following models:, gemini-2.5-flash, gemini-2.5-pro, gemini-2.5-flash-preview-04-1, gemini-2.5-pro-preview-05-0, gemini-2.0-flash-lite-00, gemini-2.0-flash-001\n","example":343940597,"type":"integer"},"stopSequences":{"items":{"type":"string"},"type":"array"},"temperature":{"default":1,"description":"The temperature is used for sampling during response generation, which occurs when topP and topK are applied. Temperature controls the degree of randomness in token selection. Lower temperatures are good for prompts that require a less open-ended or creative response, while higher temperatures can lead to more diverse or creative results. A temperature of 0 means that the highest probability tokens are always selected. In this case, responses for a given prompt are mostly deterministic, but a small amount of variation is still possible. If the model returns a response that's too generic, too short, or the model gives a fallback response, try increasing the temperature\n","format":"float","maximum":2,"minimum":0,"type":"number"},"thinkingConfig":{"description":"Optional. Configuration for thinking features. Thinking is a process where the model breaks down a complex task into smaller steps to generate a higher-quality response.","properties":{"includeThoughts":{"description":"Optional. If true, the model will include its thoughts in the response.","type":"boolean"},"thinkingBudget":{"description":"Optional. The token budget for the model's thinking process. The model will make a best effort to stay within this budget.","type":"integer"},"thinkingLevel":{"description":"Optional. The thinking level for the model.","enum":["THINKING_LEVEL_UNSPECIFIED","LOW","MEDIUM","HIGH","MINIMAL"],"type":"string"}},"type":"object"},"topK":{"default":40,"description":"Top-K changes how the model selects tokens for output. A top-K of 1 means the next selected token is the most probable among all tokens in the model's vocabulary. A top-K of 3 means that the next token is selected from among the 3 most probable tokens by using temperature.\n","example":40,"minimum":1,"type":"integer"},"topP":{"default":0.95,"description":"If specified, nucleus sampling is used.\nTop-P changes how the model selects tokens for output. Tokens are selected from the most (see top-K) to least probable until the sum of their probabilities equals the top-P value. For example, if tokens A, B, and C have a probability of 0.3, 0.2, and 0.1 and the top-P value is 0.5, then the model will select either A or B as the next token by using temperature and excludes C as a candidate.\nSpecify a lower value for less random responses and a higher value for more random responses.\n","format":"float","maximum":1,"minimum":0,"type":"number"}},"type":"object"},"GeminiInlineData":{"description":"Inline data in raw bytes. For gemini-2.0-flash-lite and gemini-2.0-flash, you can specify up to 3000 images by using inlineData.\n","properties":{"data":{"description":"The base64 encoding of the image, PDF, or video to include inline in the prompt. When including media inline, you must also specify the media type (mimeType) of the data. Size limit: 20MB\n","format":"byte","type":"string"},"mimeType":{"$ref":"#/components/schemas/GeminiMimeType"}},"type":"object"},"GeminiMimeType":{"description":"The media type of the file specified in the data or fileUri fields. Acceptable values include the following. For gemini-2.0-flash-lite and gemini-2.0-flash, the maximum length of an audio file is 8.4 hours and the maximum length of a video file (without audio) is one hour. For more information, see Gemini audio and video requirements. Text files must be UTF-8 encoded. The contents of the text file count toward the token limit. There is no limit on image resolution.","enum":["application/pdf","audio/mpeg","audio/mp3","audio/wav","image/png","image/jpeg","image/webp","text/plain","video/mov","video/mpeg","video/mp4","video/mpg","video/avi","video/wmv","video/mpegps","video/flv","image/heic","image/heif","audio/flac","video/webm"],"type":"string"},"GeminiOffset":{"description":"Represents a duration offset for video timeline positions.\n","properties":{"nanos":{"description":"Signed fractions of a second at nanosecond resolution. Negative second values with fractions must still have non-negative nanos values.\n","example":0,"maximum":999999999,"minimum":0,"type":"integer"},"seconds":{"description":"Signed seconds of the span of time. Must be from -315,576,000,000 to +315,576,000,000 inclusive.\n","example":60,"maximum":315576000000,"minimum":-315576000000,"type":"integer"}},"type":"object"},"GeminiPart":{"properties":{"fileData":{"$ref":"#/components/schemas/GeminiFileData"},"inlineData":{"$ref":"#/components/schemas/GeminiInlineData"},"mediaProcessing":{"description":"How the model reads this part's video. Set \"AGENTIC\" to let the model decide which segments to inspect, instead of fixed-rate frame sampling. Omit for the default fixed-rate sampling. Supported on gemini-3.7-flash and newer Flash models.\n","type":"string"},"text":{"description":"A text prompt or code snippet.","example":"Write a story about a robot learning to paint","type":"string"},"thought":{"description":"Indicates this part is a thinking/reasoning step from the model.","type":"boolean"}},"type":"object"},"GeminiPromptFeedback":{"properties":{"blockReason":{"type":"string"},"blockReasonMessage":{"type":"string"},"safetyRatings":{"items":{"$ref":"#/components/schemas/GeminiSafetyRating"},"type":"array"}},"type":"object"},"GeminiSafetyCategory":{"enum":["HARM_CATEGORY_SEXUALLY_EXPLICIT","HARM_CATEGORY_HATE_SPEECH","HARM_CATEGORY_HARASSMENT","HARM_CATEGORY_DANGEROUS_CONTENT"],"type":"string"},"GeminiSafetyRating":{"properties":{"category":{"$ref":"#/components/schemas/GeminiSafetyCategory"},"probability":{"description":"The probability that the content violates the specified safety category","enum":["NEGLIGIBLE","LOW","MEDIUM","HIGH","UNKNOWN"],"type":"string"}},"type":"object"},"GeminiSafetySetting":{"description":"Per request settings for blocking unsafe content. Enforced on GenerateContentResponse.candidates.\n","properties":{"category":{"$ref":"#/components/schemas/GeminiSafetyCategory"},"threshold":{"$ref":"#/components/schemas/GeminiSafetyThreshold"}},"required":["category","threshold"],"type":"object"},"GeminiSafetyThreshold":{"enum":["OFF","BLOCK_NONE","BLOCK_LOW_AND_ABOVE","BLOCK_MEDIUM_AND_ABOVE","BLOCK_ONLY_HIGH"],"type":"string"},"GeminiSystemInstructionContent":{"description":"Instructions for the model to steer it toward better performance. For example, \"Answer as concisely as possible\" or \"Don't use technical terms in your response\". The text strings count toward the token limit. The role field of systemInstruction is ignored and doesn't affect the performance of the model. Note: Only text should be used in parts and content in each part should be in a separate paragraph.\n","properties":{"parts":{"description":"A list of ordered parts that make up a single message. Different parts may have different IANA MIME types. For limits on the inputs, such as the maximum number of tokens or the number of images, see the model specifications on the Google models page.\n","items":{"$ref":"#/components/schemas/GeminiTextPart"},"type":"array"},"role":{"description":"The identity of the entity that creates the message. The following values are supported: user: This indicates that the message is sent by a real person, typically a user-generated message. model: This indicates that the message is generated by the model. The model value is used to insert messages from the model into the conversation during multi-turn conversations. For non-multi-turn conversations, this field can be left blank or unset.\n","enum":["user","model"],"example":"user","type":"string"}},"required":["parts"],"type":"object"},"GeminiTextPart":{"properties":{"text":{"description":"A text prompt or code snippet.","example":"Answer as concisely as possible","type":"string"}},"type":"object"},"GeminiTool":{"description":"A piece of code that enables the system to interact with external systems to perform an action, or set of actions, outside of knowledge and scope of the model. See Function calling.\n","properties":{"functionDeclarations":{"items":{"$ref":"#/components/schemas/GeminiFunctionDeclaration"},"type":"array"}},"type":"object"},"GeminiUsageMetadata":{"properties":{"cachedContentTokenCount":{"description":"Output only. Number of tokens in the cached part in the input (the cached content).","type":"integer"},"candidatesTokenCount":{"description":"Number of tokens in the response(s).","type":"integer"},"candidatesTokensDetails":{"description":"Breakdown of candidate tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"promptTokenCount":{"description":"Number of tokens in the request. When cachedContent is set, this is still the total effective prompt size meaning this includes the number of tokens in the cached content.","type":"integer"},"promptTokensDetails":{"description":"Breakdown of prompt tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"thoughtsTokenCount":{"description":"Number of tokens present in thoughts output.","type":"integer"},"toolUsePromptTokenCount":{"description":"Number of tokens present in tool-use prompt(s).","type":"integer"},"toolUsePromptTokensDetails":{"description":"Breakdown of tool-use prompt tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"totalTokenCount":{"description":"Total number of tokens (prompt + candidates).","type":"integer"},"trafficType":{"description":"Traffic type used for the request (e.g., PROVISIONED_THROUGHPUT).","type":"string"}},"type":"object"},"GeminiVideoMetadata":{"description":"For video input, the start and end offset of the video in Duration format. For example, to specify a 10 second clip starting at 1:00, set \"startOffset\": { \"seconds\": 60 } and \"endOffset\": { \"seconds\": 70 }. The metadata should only be specified while the video data is presented in inlineData or fileData.\n","properties":{"endOffset":{"$ref":"#/components/schemas/GeminiOffset"},"startOffset":{"$ref":"#/components/schemas/GeminiOffset"}},"type":"object"},"Modality":{"description":"Type of input or output content modality.","enum":["MODALITY_UNSPECIFIED","TEXT","IMAGE","VIDEO","AUDIO","DOCUMENT"],"type":"string"},"ModalityTokenCount":{"properties":{"modality":{"$ref":"#/components/schemas/Modality"},"tokenCount":{"description":"Number of tokens for the given modality.","type":"integer"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"vertexai/gemini-3.1-flash-image","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image","video"],"x-comfy-router-output-modalities":["image","text"],"x-comfy-router-alt-providers":[{"provider":"fal","model_id":"fal/fal-nano-banana-2"},{"provider":"runware","model_id":"runware/runware-nano-banana-2"},{"provider":"wavespeed","model_id":"wavespeed/wavespeed-nano-banana-2"}]}
+{"openapi":"3.0.2","info":{"title":"vertexai/gemini-3.1-flash-image","description":"The request body Comfy Router accepts for the model \"vertexai/gemini-3.1-flash-image\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"a86e35b5a353"},"paths":{"/v2/models/vertexai/gemini-3.1-flash-image":{"post":{"operationId":"runRouterModel","summary":"Run vertexai/gemini-3.1-flash-image synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"example":{"contents":[{"parts":[{"text":"Describe a robot learning to paint, in two sentences."}],"role":"user"}]},"properties":{"contents":{"items":{"$ref":"#/components/schemas/GeminiContent"},"type":"array"},"generationConfig":{"$ref":"#/components/schemas/GeminiGenerationConfig"},"safetySettings":{"items":{"$ref":"#/components/schemas/GeminiSafetySetting"},"type":"array"},"systemInstruction":{"$ref":"#/components/schemas/GeminiSystemInstructionContent"},"tools":{"items":{"$ref":"#/components/schemas/GeminiTool"},"type":"array"},"uploadImagesToStorage":{"description":"If true, generated images will be uploaded to cloud storage and returned as signed URLs instead of inline base64 data. The URLs expire after 24 hours.","type":"boolean"},"videoMetadata":{"$ref":"#/components/schemas/GeminiVideoMetadata"}},"required":["contents"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/GeminiGenerateContentResponse"}],"description":"Comfy Router output schema for the image-returning Gemini models: the same Vertex AI `generateContent` response the text models return, forwarded unchanged, but the generated image arrives as base64 bytes at `candidates[0].content.parts[0].inlineData.data`, with its media type at `inlineData.mimeType`. A response may carry a text part alongside the image part, so select the part by the field you need rather than by index.\n`uploadImagesToStorage` CHANGES THIS SHAPE. When the request sets it, the proxy uploads each generated image to Comfy storage and replaces the part's `inlineData` with `fileData`, carrying a Comfy-signed URL at `fileData.fileUri` and the media type at `fileData.mimeType`; `inlineData` is then absent for that part. The URL expires 24 hours after it is minted, so download the image rather than storing the link. An image whose upload fails is left as `inlineData`, so ONE response can mix both shapes - branch on which key is present rather than assuming either.\nRouter does not re-host a partner link for this family: Gemini returns the image as bytes and no URL of its own, so `uploadImagesToStorage` is an upload of those bytes that the caller asks for, not a rewrite of somebody else's link. A request that leaves the field unset is answered as base64 from end to end WHENEVER VERTEX AI ITSELF SERVES IT - which is always the case for `vertexai/gemini-2.5-flash-image` and `vertexai/gemini-3.1-flash-lite-image`, the two ids on this component that have no other producer, and is the usual but not the only case for the two below.\n`vertexai/gemini-3-pro-image` (`fal`, `pika`, `runware`, `wavespeed`) and `vertexai/gemini-3.1-flash-image` (`fal`, `pika`, `runware`, `wavespeed`) have ALTERNATE PRODUCERS: an eligible request may be served by a partner instead of by Vertex AI. Two PUBLISHED routes reach one, both the alt-provider dispatch in `server/implementation/router_run.go`: `model_provider` names a partner explicitly, and `fallback_provider` (default ON) retries once against a registered alternate when the Vertex AI attempt fails for a reason attributable to Router or to Vertex AI rather than to the request. That retry is suppressed - whatever `fallback_provider` says - on a BYOK call, on a same-key idempotent replay, on a multipart edit operation, and when the model has no registered alternate. SEPARATELY, the `vertexai` partner proxy runs a fallback cascade of its own on these two ids when the platform-credential attempt fails: it is NOT governed by `fallback_provider`, it is disabled only for BYOK requests, and it can therefore fire on a failure the request itself caused and on a call that asked for `fallback_provider: false`. Treat a non-Vertex producer as possible on these two ids whatever the request asked for.\nA response the `fal`, `pika`, `runware` or `wavespeed` leg served, translated back into this contract, carries every generated image as `fileData.fileUri` holding THE PARTNER'S OWN hosted URL, and never `inlineData.data` - all four partners return a hosted URL and never base64 bytes. The URL's lifetime and access policy are the partner's own rather than the 24-hour Comfy-signed URL described above, and Comfy publishes neither, so download the image promptly rather than storing the link. The proxy-level cascade above is NOT bound to that shape: it substitutes another provider's Gemini-compatible body, which may carry inline bytes instead. So the rule is the one the `uploadImagesToStorage` paragraph already states - branch on which key a part actually carries, never on a producer you inferred.\n`uploadImagesToStorage` does not change any of this. The flag is an upload of Vertex AI's own bytes, and off Vertex AI there are no such bytes to upload, so sending it neither produces `inlineData` nor rewrites the partner's link: it is simply inert on an alt-provider leg. A request that carries the key - whatever its value, `false` included - is told so: the translated alt-provider response lists `uploadImagesToStorage` in `X-Comfy-Router-Dropped-Params`, among the request fields the partner's schema has no place for. Only a translated response carries that disclosure: the proxy-level cascade and a `strict_mode: true` call run no translation and send no such header.\nTHREE RESPONSES SKIP THAT TRANSLATION and return the PARTNER'S OWN envelope rather than this component's: `strict_mode: true` asks for the alternate provider's raw schema by definition, a partner response that arrives with a non-empty `Content-Encoding` is forwarded unchanged because Router does not decompress a body to translate it, and a translation that fails or yields nothing forwards the partner's body unchanged - that body may carry no `candidates` key at all. An alt-provider response that IS translated back carries `candidates` and nothing else: `usageMetadata`, `promptFeedback`, `modelVersion`, `responseId` and `finishReason` are Vertex AI's own fields, and the translation has nothing to map them from.\nWHICH PRODUCER ANSWERED is disclosed more weakly than the shape itself. `X-Comfy-Router-Fallback-Provider` names the provider only on a response Router's OWN fallback retry served: the proxy-level cascade sets no header. A same-key idempotent replay repeats both that header and `X-Comfy-Router-Dropped-Params` exactly as the original call sent them, so a replay discloses what the original call did and no more. The request's own `model_provider` is reliable only in the direction it states - it says a partner was ASKED for, not that none answered when it was absent. The published per-model document of a model with a PUBLISHED leg carries an `x-comfy-router-alt-providers` marker naming it; absence of that marker is NOT a guarantee that the model has no alternate producer, since a registered leg that is not cleared for public disclosure still dispatches without appearing there. Which partner served a given request is a Comfy routing decision that can change without notice, and is not part of this contract.\nAs for the text models, `promptFeedback` and `usageMetadata` are present on a safety-blocked response that generated nothing; `candidates` is the field to branch on. Both are Vertex AI's own fields: on a response an alternate producer served, see the paragraph above for what is absent.\nThe example's `inlineData.data` is the PLACEHOLDER `PGJhc2U2ND4=`, which decodes to the literal text `\u003cbase64\u003e` - a real image would be megabytes, and a real provider response does not belong in a published spec. It is spelled as valid base64 rather than as a bare `\u003cbase64\u003e` marker because `GeminiInlineData.data` declares `format: byte`, which this document's own example validation enforces.\nThe example is authored ONCE for all four ids above and rendered into each per-model document unchanged, so its `modelVersion` is ILLUSTRATIVE RATHER THAN ENDPOINT-SPECIFIC. Vertex AI echoes the id that actually served the call, so a `vertexai/gemini-3-pro-image` response reads `gemini-3-pro-image` there - not the `gemini-2.5-flash-image` the example happens to show - and on an alt-provider-served response the field is absent entirely, per the paragraph above. Where `modelVersion` is present, read it as echoing the served id, never as the literal value printed in the document you are reading.","example":{"candidates":[{"content":{"parts":[{"inlineData":{"data":"PGJhc2U2ND4=","mimeType":"image/png"}}],"role":"model"},"finishReason":"STOP"}],"modelVersion":"gemini-2.5-flash-image","responseId":"7c6b5a49-3827-1605-f4e3-d2c1b0a99887","usageMetadata":{"candidatesTokenCount":1290,"promptTokenCount":11,"totalTokenCount":1301}}}}}}}}}},"components":{"schemas":{"GeminiCandidate":{"properties":{"citationMetadata":{"$ref":"#/components/schemas/GeminiCitationMetadata"},"content":{"$ref":"#/components/schemas/GeminiContent"},"finishReason":{"type":"string"},"safetyRatings":{"items":{"$ref":"#/components/schemas/GeminiSafetyRating"},"type":"array"}},"type":"object"},"GeminiCitation":{"properties":{"authors":{"items":{"type":"string"},"type":"array"},"endIndex":{"type":"integer"},"license":{"type":"string"},"publicationDate":{"format":"date","type":"string"},"startIndex":{"type":"integer"},"title":{"type":"string"},"uri":{"type":"string"}},"type":"object"},"GeminiCitationMetadata":{"properties":{"citations":{"items":{"$ref":"#/components/schemas/GeminiCitation"},"type":"array"}},"type":"object"},"GeminiContent":{"description":"The content of the current conversation with the model. For single-turn queries, this is a single instance. For multi-turn queries, this is a repeated field that contains conversation history and the latest request.\n","properties":{"parts":{"items":{"$ref":"#/components/schemas/GeminiPart"},"type":"array"},"role":{"enum":["user","model"],"example":"user","type":"string"}},"required":["parts"],"type":"object"},"GeminiFileData":{"description":"URI based data.","properties":{"fileUri":{"description":"URI","type":"string"},"mimeType":{"$ref":"#/components/schemas/GeminiMimeType"}},"type":"object"},"GeminiFunctionDeclaration":{"properties":{"description":{"type":"string"},"name":{"type":"string"},"parameters":{"description":"JSON schema for the function parameters","type":"object"}},"required":["name"],"type":"object"},"GeminiGenerateContentResponse":{"properties":{"candidates":{"items":{"$ref":"#/components/schemas/GeminiCandidate"},"type":"array"},"createTime":{"description":"Timestamp when the response was created.","type":"string"},"modelVersion":{"description":"The model version used to generate the response.","type":"string"},"promptFeedback":{"$ref":"#/components/schemas/GeminiPromptFeedback"},"responseId":{"description":"Unique identifier for the response.","type":"string"},"usageMetadata":{"$ref":"#/components/schemas/GeminiUsageMetadata"}},"type":"object"},"GeminiGenerationConfig":{"description":"Sampling, length and output settings for the generation. Every field is optional: the fields below that declare a `default` apply it when omitted, and the rest fall back to the model's own behaviour.\n","properties":{"imageConfig":{"description":"Configuration for image generation","properties":{"aspectRatio":{"description":"Aspect ratio for generated images","type":"string"},"imageOutputOptions":{"description":"Optional. The image output format for generated images.","properties":{"compressionQuality":{"description":"Optional. The compression quality of the output image.","type":"integer"},"mimeType":{"description":"Optional. The image format that the output should be saved as, on the Vertex AI paths: requests served by Comfy's own credentials, and BYOK requests authenticated with a GCP service account. Accepted values there are `image/png` and `image/jpeg`, matched case-insensitively and normalised to lower case before the request is forwarded; any other value is rejected with a 400 naming this field. Defaults to `image/png` when omitted. A BYOK request authenticated with a Google AI Studio API key is the exception: that upstream has no such property and rejects the whole call if it is present, so the field is dropped from the request rather than honoured or rejected, and the output format is whatever AI Studio chooses. On every path, read the media type back from the response part you are given (`inlineData.mimeType`, or `fileData.mimeType` when `uploadImagesToStorage` is set) rather than assuming the value you sent.","type":"string"}},"type":"object"},"imageSize":{"description":"Optional. Specifies the size of generated images. Supported values are 1K, 2K, 4K. If not specified, the model will use default value 1K.","type":"string"}},"type":"object"},"maxOutputTokens":{"description":"Maximum number of tokens that can be generated in the response. A token is approximately 4 characters. 100 tokens correspond to roughly 60-80 words.\n","example":2048,"maximum":65536,"minimum":16,"type":"integer"},"responseModalities":{"items":{"enum":["TEXT","IMAGE"],"type":"string"},"type":"array"},"seed":{"description":"When seed is fixed to a specific value, the model makes a best effort to provide the same response for repeated requests. Deterministic output isn't guaranteed. Also, changing the model or parameter settings, such as the temperature, can cause variations in the response even when you use the same seed value. By default, a random seed value is used. Available for the following models:, gemini-2.5-flash, gemini-2.5-pro, gemini-2.5-flash-preview-04-1, gemini-2.5-pro-preview-05-0, gemini-2.0-flash-lite-00, gemini-2.0-flash-001\n","example":343940597,"type":"integer"},"stopSequences":{"items":{"type":"string"},"type":"array"},"temperature":{"default":1,"description":"The temperature is used for sampling during response generation, which occurs when topP and topK are applied. Temperature controls the degree of randomness in token selection. Lower temperatures are good for prompts that require a less open-ended or creative response, while higher temperatures can lead to more diverse or creative results. A temperature of 0 means that the highest probability tokens are always selected. In this case, responses for a given prompt are mostly deterministic, but a small amount of variation is still possible. If the model returns a response that's too generic, too short, or the model gives a fallback response, try increasing the temperature\n","format":"float","maximum":2,"minimum":0,"type":"number"},"thinkingConfig":{"description":"Optional. Configuration for thinking features. Thinking is a process where the model breaks down a complex task into smaller steps to generate a higher-quality response.","properties":{"includeThoughts":{"description":"Optional. If true, the model will include its thoughts in the response.","type":"boolean"},"thinkingBudget":{"description":"Optional. The token budget for the model's thinking process. The model will make a best effort to stay within this budget.","type":"integer"},"thinkingLevel":{"description":"Optional. The thinking level for the model.","enum":["THINKING_LEVEL_UNSPECIFIED","LOW","MEDIUM","HIGH","MINIMAL"],"type":"string"}},"type":"object"},"topK":{"default":40,"description":"Top-K changes how the model selects tokens for output. A top-K of 1 means the next selected token is the most probable among all tokens in the model's vocabulary. A top-K of 3 means that the next token is selected from among the 3 most probable tokens by using temperature.\n","example":40,"minimum":1,"type":"integer"},"topP":{"default":0.95,"description":"If specified, nucleus sampling is used.\nTop-P changes how the model selects tokens for output. Tokens are selected from the most (see top-K) to least probable until the sum of their probabilities equals the top-P value. For example, if tokens A, B, and C have a probability of 0.3, 0.2, and 0.1 and the top-P value is 0.5, then the model will select either A or B as the next token by using temperature and excludes C as a candidate.\nSpecify a lower value for less random responses and a higher value for more random responses.\n","format":"float","maximum":1,"minimum":0,"type":"number"}},"type":"object"},"GeminiInlineData":{"description":"Inline data in raw bytes. For gemini-2.0-flash-lite and gemini-2.0-flash, you can specify up to 3000 images by using inlineData.\n","properties":{"data":{"description":"The base64 encoding of the image, PDF, or video to include inline in the prompt. When including media inline, you must also specify the media type (mimeType) of the data. Size limit: 20MB\n","format":"byte","type":"string"},"mimeType":{"$ref":"#/components/schemas/GeminiMimeType"}},"type":"object"},"GeminiMimeType":{"description":"The media type of the file specified in the data or fileUri fields. Acceptable values include the following. For gemini-2.0-flash-lite and gemini-2.0-flash, the maximum length of an audio file is 8.4 hours and the maximum length of a video file (without audio) is one hour. For more information, see Gemini audio and video requirements. Text files must be UTF-8 encoded. The contents of the text file count toward the token limit. There is no limit on image resolution.","enum":["application/pdf","audio/mpeg","audio/mp3","audio/wav","image/png","image/jpeg","image/webp","text/plain","video/mov","video/mpeg","video/mp4","video/mpg","video/avi","video/wmv","video/mpegps","video/flv","image/heic","image/heif","audio/flac","video/webm"],"type":"string"},"GeminiOffset":{"description":"Represents a duration offset for video timeline positions.\n","properties":{"nanos":{"description":"Signed fractions of a second at nanosecond resolution. Negative second values with fractions must still have non-negative nanos values.\n","example":0,"maximum":999999999,"minimum":0,"type":"integer"},"seconds":{"description":"Signed seconds of the span of time. Must be from -315,576,000,000 to +315,576,000,000 inclusive.\n","example":60,"maximum":315576000000,"minimum":-315576000000,"type":"integer"}},"type":"object"},"GeminiPart":{"properties":{"fileData":{"$ref":"#/components/schemas/GeminiFileData"},"inlineData":{"$ref":"#/components/schemas/GeminiInlineData"},"mediaProcessing":{"description":"How the model reads this part's video. Set \"AGENTIC\" to let the model decide which segments to inspect, instead of fixed-rate frame sampling. Omit for the default fixed-rate sampling. Supported on gemini-3.7-flash and newer Flash models.\n","type":"string"},"text":{"description":"A text prompt or code snippet.","example":"Write a story about a robot learning to paint","type":"string"},"thought":{"description":"Indicates this part is a thinking/reasoning step from the model.","type":"boolean"}},"type":"object"},"GeminiPromptFeedback":{"properties":{"blockReason":{"type":"string"},"blockReasonMessage":{"type":"string"},"safetyRatings":{"items":{"$ref":"#/components/schemas/GeminiSafetyRating"},"type":"array"}},"type":"object"},"GeminiSafetyCategory":{"enum":["HARM_CATEGORY_SEXUALLY_EXPLICIT","HARM_CATEGORY_HATE_SPEECH","HARM_CATEGORY_HARASSMENT","HARM_CATEGORY_DANGEROUS_CONTENT"],"type":"string"},"GeminiSafetyRating":{"properties":{"category":{"$ref":"#/components/schemas/GeminiSafetyCategory"},"probability":{"description":"The probability that the content violates the specified safety category","enum":["NEGLIGIBLE","LOW","MEDIUM","HIGH","UNKNOWN"],"type":"string"}},"type":"object"},"GeminiSafetySetting":{"description":"Per request settings for blocking unsafe content. Enforced on GenerateContentResponse.candidates.\n","properties":{"category":{"$ref":"#/components/schemas/GeminiSafetyCategory"},"threshold":{"$ref":"#/components/schemas/GeminiSafetyThreshold"}},"required":["category","threshold"],"type":"object"},"GeminiSafetyThreshold":{"enum":["OFF","BLOCK_NONE","BLOCK_LOW_AND_ABOVE","BLOCK_MEDIUM_AND_ABOVE","BLOCK_ONLY_HIGH"],"type":"string"},"GeminiSystemInstructionContent":{"description":"Instructions for the model to steer it toward better performance. For example, \"Answer as concisely as possible\" or \"Don't use technical terms in your response\". The text strings count toward the token limit. The role field of systemInstruction is ignored and doesn't affect the performance of the model. Note: Only text should be used in parts and content in each part should be in a separate paragraph.\n","properties":{"parts":{"description":"A list of ordered parts that make up a single message. Different parts may have different IANA MIME types. For limits on the inputs, such as the maximum number of tokens or the number of images, see the model specifications on the Google models page.\n","items":{"$ref":"#/components/schemas/GeminiTextPart"},"type":"array"},"role":{"description":"The identity of the entity that creates the message. The following values are supported: user: This indicates that the message is sent by a real person, typically a user-generated message. model: This indicates that the message is generated by the model. The model value is used to insert messages from the model into the conversation during multi-turn conversations. For non-multi-turn conversations, this field can be left blank or unset.\n","enum":["user","model"],"example":"user","type":"string"}},"required":["parts"],"type":"object"},"GeminiTextPart":{"properties":{"text":{"description":"A text prompt or code snippet.","example":"Answer as concisely as possible","type":"string"}},"type":"object"},"GeminiTool":{"description":"A piece of code that enables the system to interact with external systems to perform an action, or set of actions, outside of knowledge and scope of the model. See Function calling.\n","properties":{"functionDeclarations":{"items":{"$ref":"#/components/schemas/GeminiFunctionDeclaration"},"type":"array"}},"type":"object"},"GeminiUsageMetadata":{"properties":{"cachedContentTokenCount":{"description":"Output only. Number of tokens in the cached part in the input (the cached content).","type":"integer"},"candidatesTokenCount":{"description":"Number of tokens in the response(s).","type":"integer"},"candidatesTokensDetails":{"description":"Breakdown of candidate tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"promptTokenCount":{"description":"Number of tokens in the request. When cachedContent is set, this is still the total effective prompt size meaning this includes the number of tokens in the cached content.","type":"integer"},"promptTokensDetails":{"description":"Breakdown of prompt tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"thoughtsTokenCount":{"description":"Number of tokens present in thoughts output.","type":"integer"},"toolUsePromptTokenCount":{"description":"Number of tokens present in tool-use prompt(s).","type":"integer"},"toolUsePromptTokensDetails":{"description":"Breakdown of tool-use prompt tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"totalTokenCount":{"description":"Total number of tokens (prompt + candidates).","type":"integer"},"trafficType":{"description":"Traffic type used for the request (e.g., PROVISIONED_THROUGHPUT).","type":"string"}},"type":"object"},"GeminiVideoMetadata":{"description":"For video input, the start and end offset of the video in Duration format. For example, to specify a 10 second clip starting at 1:00, set \"startOffset\": { \"seconds\": 60 } and \"endOffset\": { \"seconds\": 70 }. The metadata should only be specified while the video data is presented in inlineData or fileData.\n","properties":{"endOffset":{"$ref":"#/components/schemas/GeminiOffset"},"startOffset":{"$ref":"#/components/schemas/GeminiOffset"}},"type":"object"},"Modality":{"description":"Type of input or output content modality.","enum":["MODALITY_UNSPECIFIED","TEXT","IMAGE","VIDEO","AUDIO","DOCUMENT"],"type":"string"},"ModalityTokenCount":{"properties":{"modality":{"$ref":"#/components/schemas/Modality"},"tokenCount":{"description":"Number of tokens for the given modality.","type":"integer"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"vertexai/gemini-3.1-flash-image","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image","video"],"x-comfy-router-output-modalities":["image","text"],"x-comfy-router-alt-providers":[{"provider":"fal","model_id":"fal/fal-nano-banana-2"},{"provider":"pika","model_id":"pika/pika-nano-banana-2"},{"provider":"runware","model_id":"runware/runware-nano-banana-2"},{"provider":"wavespeed","model_id":"wavespeed/wavespeed-nano-banana-2"}]}
diff --git a/router-schemas/vertexai/gemini-3.1-flash-lite-image.json b/router-schemas/vertexai/gemini-3.1-flash-lite-image.json
index 67d792d271..a95fb3f404 100644
--- a/router-schemas/vertexai/gemini-3.1-flash-lite-image.json
+++ b/router-schemas/vertexai/gemini-3.1-flash-lite-image.json
@@ -1 +1 @@
-{"openapi":"3.0.2","info":{"title":"vertexai/gemini-3.1-flash-lite-image","description":"The request body Comfy Router accepts for the model \"vertexai/gemini-3.1-flash-lite-image\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"4fddddb86113"},"paths":{"/v2/models/vertexai/gemini-3.1-flash-lite-image":{"post":{"operationId":"runRouterModel","summary":"Run vertexai/gemini-3.1-flash-lite-image synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"example":{"contents":[{"parts":[{"text":"Describe a robot learning to paint, in two sentences."}],"role":"user"}]},"properties":{"contents":{"items":{"$ref":"#/components/schemas/GeminiContent"},"type":"array"},"generationConfig":{"$ref":"#/components/schemas/GeminiGenerationConfig"},"safetySettings":{"items":{"$ref":"#/components/schemas/GeminiSafetySetting"},"type":"array"},"systemInstruction":{"$ref":"#/components/schemas/GeminiSystemInstructionContent"},"tools":{"items":{"$ref":"#/components/schemas/GeminiTool"},"type":"array"},"uploadImagesToStorage":{"description":"If true, generated images will be uploaded to cloud storage and returned as signed URLs instead of inline base64 data. The URLs expire after 24 hours.","type":"boolean"},"videoMetadata":{"$ref":"#/components/schemas/GeminiVideoMetadata"}},"required":["contents"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/GeminiGenerateContentResponse"}],"description":"Comfy Router output schema for the image-returning Gemini models: the same Vertex AI `generateContent` response the text models return, forwarded unchanged, but the generated image arrives as base64 bytes at `candidates[0].content.parts[0].inlineData.data`, with its media type at `inlineData.mimeType`. A response may carry a text part alongside the image part, so select the part by the field you need rather than by index.\n`uploadImagesToStorage` CHANGES THIS SHAPE. When the request sets it, the proxy uploads each generated image to Comfy storage and replaces the part's `inlineData` with `fileData`, carrying a Comfy-signed URL at `fileData.fileUri` and the media type at `fileData.mimeType`; `inlineData` is then absent for that part. The URL expires 24 hours after it is minted, so download the image rather than storing the link. An image whose upload fails is left as `inlineData`, so ONE response can mix both shapes - branch on which key is present rather than assuming either.\nRouter does not re-host a partner link for this family: Gemini returns the image as bytes and no URL of its own, so `uploadImagesToStorage` is an upload of those bytes that the caller asks for, not a rewrite of somebody else's link. A request that leaves the field unset is answered as base64 from end to end WHENEVER VERTEX AI ITSELF SERVES IT - which is always the case for `vertexai/gemini-2.5-flash-image` and `vertexai/gemini-3.1-flash-lite-image`, the two ids on this component that have no other producer, and is the usual but not the only case for the two below.\n`vertexai/gemini-3-pro-image` (`fal`, `runware`, `wavespeed`) and `vertexai/gemini-3.1-flash-image` (`fal`, `runware`, `wavespeed`) have ALTERNATE PRODUCERS: an eligible request may be served by a partner instead of by Vertex AI. Two PUBLISHED routes reach one, both the alt-provider dispatch in `server/implementation/router_run.go`: `model_provider` names a partner explicitly, and `fallback_provider` (default ON) retries once against a registered alternate when the Vertex AI attempt fails for a reason attributable to Router or to Vertex AI rather than to the request. That retry is suppressed - whatever `fallback_provider` says - on a BYOK call, on a same-key idempotent replay, on a multipart edit operation, and when the model has no registered alternate. SEPARATELY, the `vertexai` partner proxy runs a fallback cascade of its own on these two ids when the platform-credential attempt fails: it is NOT governed by `fallback_provider`, it is disabled only for BYOK requests, and it can therefore fire on a failure the request itself caused and on a call that asked for `fallback_provider: false`. Treat a non-Vertex producer as possible on these two ids whatever the request asked for.\nA response the `fal`, `runware` or `wavespeed` leg served, translated back into this contract, carries every generated image as `fileData.fileUri` holding THE PARTNER'S OWN hosted URL, and never `inlineData.data` - all three partners return a hosted URL and never base64 bytes. The URL's lifetime and access policy are the partner's own rather than the 24-hour Comfy-signed URL described above, and Comfy publishes neither, so download the image promptly rather than storing the link. The proxy-level cascade above is NOT bound to that shape: it substitutes another provider's Gemini-compatible body, which may carry inline bytes instead. So the rule is the one the `uploadImagesToStorage` paragraph already states - branch on which key a part actually carries, never on a producer you inferred.\n`uploadImagesToStorage` does not change any of this. The flag is an upload of Vertex AI's own bytes, and off Vertex AI there are no such bytes to upload, so sending it neither produces `inlineData` nor rewrites the partner's link: it is simply inert on an alt-provider leg. A request that carries the key - whatever its value, `false` included - is told so: the translated alt-provider response lists `uploadImagesToStorage` in `X-Comfy-Router-Dropped-Params`, among the request fields the partner's schema has no place for. Only a translated response carries that disclosure: the proxy-level cascade and a `strict_mode: true` call run no translation and send no such header.\nTHREE RESPONSES SKIP THAT TRANSLATION and return the PARTNER'S OWN envelope rather than this component's: `strict_mode: true` asks for the alternate provider's raw schema by definition, a partner response that arrives with a non-empty `Content-Encoding` is forwarded unchanged because Router does not decompress a body to translate it, and a translation that fails or yields nothing forwards the partner's body unchanged - that body may carry no `candidates` key at all. An alt-provider response that IS translated back carries `candidates` and nothing else: `usageMetadata`, `promptFeedback`, `modelVersion`, `responseId` and `finishReason` are Vertex AI's own fields, and the translation has nothing to map them from.\nWHICH PRODUCER ANSWERED is disclosed more weakly than the shape itself. `X-Comfy-Router-Fallback-Provider` names the provider only on a response Router's OWN fallback retry served: the proxy-level cascade sets no header. A same-key idempotent replay repeats both that header and `X-Comfy-Router-Dropped-Params` exactly as the original call sent them, so a replay discloses what the original call did and no more. The request's own `model_provider` is reliable only in the direction it states - it says a partner was ASKED for, not that none answered when it was absent. The published per-model document of a model with a PUBLISHED leg carries an `x-comfy-router-alt-providers` marker naming it; absence of that marker is NOT a guarantee that the model has no alternate producer, since a registered leg that is not cleared for public disclosure still dispatches without appearing there. Which partner served a given request is a Comfy routing decision that can change without notice, and is not part of this contract.\nAs for the text models, `promptFeedback` and `usageMetadata` are present on a safety-blocked response that generated nothing; `candidates` is the field to branch on. Both are Vertex AI's own fields: on a response an alternate producer served, see the paragraph above for what is absent.\nThe example's `inlineData.data` is the PLACEHOLDER `PGJhc2U2ND4=`, which decodes to the literal text `\u003cbase64\u003e` - a real image would be megabytes, and a real provider response does not belong in a published spec. It is spelled as valid base64 rather than as a bare `\u003cbase64\u003e` marker because `GeminiInlineData.data` declares `format: byte`, which this document's own example validation enforces.\nThe example is authored ONCE for all four ids above and rendered into each per-model document unchanged, so its `modelVersion` is ILLUSTRATIVE RATHER THAN ENDPOINT-SPECIFIC. Vertex AI echoes the id that actually served the call, so a `vertexai/gemini-3-pro-image` response reads `gemini-3-pro-image` there - not the `gemini-2.5-flash-image` the example happens to show - and on an alt-provider-served response the field is absent entirely, per the paragraph above. Where `modelVersion` is present, read it as echoing the served id, never as the literal value printed in the document you are reading.","example":{"candidates":[{"content":{"parts":[{"inlineData":{"data":"PGJhc2U2ND4=","mimeType":"image/png"}}],"role":"model"},"finishReason":"STOP"}],"modelVersion":"gemini-2.5-flash-image","responseId":"7c6b5a49-3827-1605-f4e3-d2c1b0a99887","usageMetadata":{"candidatesTokenCount":1290,"promptTokenCount":11,"totalTokenCount":1301}}}}}}}}}},"components":{"schemas":{"GeminiCandidate":{"properties":{"citationMetadata":{"$ref":"#/components/schemas/GeminiCitationMetadata"},"content":{"$ref":"#/components/schemas/GeminiContent"},"finishReason":{"type":"string"},"safetyRatings":{"items":{"$ref":"#/components/schemas/GeminiSafetyRating"},"type":"array"}},"type":"object"},"GeminiCitation":{"properties":{"authors":{"items":{"type":"string"},"type":"array"},"endIndex":{"type":"integer"},"license":{"type":"string"},"publicationDate":{"format":"date","type":"string"},"startIndex":{"type":"integer"},"title":{"type":"string"},"uri":{"type":"string"}},"type":"object"},"GeminiCitationMetadata":{"properties":{"citations":{"items":{"$ref":"#/components/schemas/GeminiCitation"},"type":"array"}},"type":"object"},"GeminiContent":{"description":"The content of the current conversation with the model. For single-turn queries, this is a single instance. For multi-turn queries, this is a repeated field that contains conversation history and the latest request.\n","properties":{"parts":{"items":{"$ref":"#/components/schemas/GeminiPart"},"type":"array"},"role":{"enum":["user","model"],"example":"user","type":"string"}},"required":["parts"],"type":"object"},"GeminiFileData":{"description":"URI based data.","properties":{"fileUri":{"description":"URI","type":"string"},"mimeType":{"$ref":"#/components/schemas/GeminiMimeType"}},"type":"object"},"GeminiFunctionDeclaration":{"properties":{"description":{"type":"string"},"name":{"type":"string"},"parameters":{"description":"JSON schema for the function parameters","type":"object"}},"required":["name"],"type":"object"},"GeminiGenerateContentResponse":{"properties":{"candidates":{"items":{"$ref":"#/components/schemas/GeminiCandidate"},"type":"array"},"createTime":{"description":"Timestamp when the response was created.","type":"string"},"modelVersion":{"description":"The model version used to generate the response.","type":"string"},"promptFeedback":{"$ref":"#/components/schemas/GeminiPromptFeedback"},"responseId":{"description":"Unique identifier for the response.","type":"string"},"usageMetadata":{"$ref":"#/components/schemas/GeminiUsageMetadata"}},"type":"object"},"GeminiGenerationConfig":{"description":"Sampling, length and output settings for the generation. Every field is optional: the fields below that declare a `default` apply it when omitted, and the rest fall back to the model's own behaviour.\n","properties":{"imageConfig":{"description":"Configuration for image generation","properties":{"aspectRatio":{"description":"Aspect ratio for generated images","type":"string"},"imageOutputOptions":{"description":"Optional. The image output format for generated images.","properties":{"compressionQuality":{"description":"Optional. The compression quality of the output image.","type":"integer"},"mimeType":{"description":"Optional. The image format that the output should be saved as, on the Vertex AI paths: requests served by Comfy's own credentials, and BYOK requests authenticated with a GCP service account. Accepted values there are `image/png` and `image/jpeg`, matched case-insensitively and normalised to lower case before the request is forwarded; any other value is rejected with a 400 naming this field. Defaults to `image/png` when omitted. A BYOK request authenticated with a Google AI Studio API key is the exception: that upstream has no such property and rejects the whole call if it is present, so the field is dropped from the request rather than honoured or rejected, and the output format is whatever AI Studio chooses. On every path, read the media type back from the response part you are given (`inlineData.mimeType`, or `fileData.mimeType` when `uploadImagesToStorage` is set) rather than assuming the value you sent.","type":"string"}},"type":"object"},"imageSize":{"description":"Optional. Specifies the size of generated images. Supported values are 1K, 2K, 4K. If not specified, the model will use default value 1K.","type":"string"}},"type":"object"},"maxOutputTokens":{"description":"Maximum number of tokens that can be generated in the response. A token is approximately 4 characters. 100 tokens correspond to roughly 60-80 words.\n","example":2048,"maximum":65536,"minimum":16,"type":"integer"},"responseModalities":{"items":{"enum":["TEXT","IMAGE"],"type":"string"},"type":"array"},"seed":{"description":"When seed is fixed to a specific value, the model makes a best effort to provide the same response for repeated requests. Deterministic output isn't guaranteed. Also, changing the model or parameter settings, such as the temperature, can cause variations in the response even when you use the same seed value. By default, a random seed value is used. Available for the following models:, gemini-2.5-flash, gemini-2.5-pro, gemini-2.5-flash-preview-04-1, gemini-2.5-pro-preview-05-0, gemini-2.0-flash-lite-00, gemini-2.0-flash-001\n","example":343940597,"type":"integer"},"stopSequences":{"items":{"type":"string"},"type":"array"},"temperature":{"default":1,"description":"The temperature is used for sampling during response generation, which occurs when topP and topK are applied. Temperature controls the degree of randomness in token selection. Lower temperatures are good for prompts that require a less open-ended or creative response, while higher temperatures can lead to more diverse or creative results. A temperature of 0 means that the highest probability tokens are always selected. In this case, responses for a given prompt are mostly deterministic, but a small amount of variation is still possible. If the model returns a response that's too generic, too short, or the model gives a fallback response, try increasing the temperature\n","format":"float","maximum":2,"minimum":0,"type":"number"},"thinkingConfig":{"description":"Optional. Configuration for thinking features. Thinking is a process where the model breaks down a complex task into smaller steps to generate a higher-quality response.","properties":{"includeThoughts":{"description":"Optional. If true, the model will include its thoughts in the response.","type":"boolean"},"thinkingBudget":{"description":"Optional. The token budget for the model's thinking process. The model will make a best effort to stay within this budget.","type":"integer"},"thinkingLevel":{"description":"Optional. The thinking level for the model.","enum":["THINKING_LEVEL_UNSPECIFIED","LOW","MEDIUM","HIGH","MINIMAL"],"type":"string"}},"type":"object"},"topK":{"default":40,"description":"Top-K changes how the model selects tokens for output. A top-K of 1 means the next selected token is the most probable among all tokens in the model's vocabulary. A top-K of 3 means that the next token is selected from among the 3 most probable tokens by using temperature.\n","example":40,"minimum":1,"type":"integer"},"topP":{"default":0.95,"description":"If specified, nucleus sampling is used.\nTop-P changes how the model selects tokens for output. Tokens are selected from the most (see top-K) to least probable until the sum of their probabilities equals the top-P value. For example, if tokens A, B, and C have a probability of 0.3, 0.2, and 0.1 and the top-P value is 0.5, then the model will select either A or B as the next token by using temperature and excludes C as a candidate.\nSpecify a lower value for less random responses and a higher value for more random responses.\n","format":"float","maximum":1,"minimum":0,"type":"number"}},"type":"object"},"GeminiInlineData":{"description":"Inline data in raw bytes. For gemini-2.0-flash-lite and gemini-2.0-flash, you can specify up to 3000 images by using inlineData.\n","properties":{"data":{"description":"The base64 encoding of the image, PDF, or video to include inline in the prompt. When including media inline, you must also specify the media type (mimeType) of the data. Size limit: 20MB\n","format":"byte","type":"string"},"mimeType":{"$ref":"#/components/schemas/GeminiMimeType"}},"type":"object"},"GeminiMimeType":{"description":"The media type of the file specified in the data or fileUri fields. Acceptable values include the following. For gemini-2.0-flash-lite and gemini-2.0-flash, the maximum length of an audio file is 8.4 hours and the maximum length of a video file (without audio) is one hour. For more information, see Gemini audio and video requirements. Text files must be UTF-8 encoded. The contents of the text file count toward the token limit. There is no limit on image resolution.","enum":["application/pdf","audio/mpeg","audio/mp3","audio/wav","image/png","image/jpeg","image/webp","text/plain","video/mov","video/mpeg","video/mp4","video/mpg","video/avi","video/wmv","video/mpegps","video/flv","image/heic","image/heif","audio/flac","video/webm"],"type":"string"},"GeminiOffset":{"description":"Represents a duration offset for video timeline positions.\n","properties":{"nanos":{"description":"Signed fractions of a second at nanosecond resolution. Negative second values with fractions must still have non-negative nanos values.\n","example":0,"maximum":999999999,"minimum":0,"type":"integer"},"seconds":{"description":"Signed seconds of the span of time. Must be from -315,576,000,000 to +315,576,000,000 inclusive.\n","example":60,"maximum":315576000000,"minimum":-315576000000,"type":"integer"}},"type":"object"},"GeminiPart":{"properties":{"fileData":{"$ref":"#/components/schemas/GeminiFileData"},"inlineData":{"$ref":"#/components/schemas/GeminiInlineData"},"mediaProcessing":{"description":"How the model reads this part's video. Set \"AGENTIC\" to let the model decide which segments to inspect, instead of fixed-rate frame sampling. Omit for the default fixed-rate sampling. Supported on gemini-3.7-flash and newer Flash models.\n","type":"string"},"text":{"description":"A text prompt or code snippet.","example":"Write a story about a robot learning to paint","type":"string"},"thought":{"description":"Indicates this part is a thinking/reasoning step from the model.","type":"boolean"}},"type":"object"},"GeminiPromptFeedback":{"properties":{"blockReason":{"type":"string"},"blockReasonMessage":{"type":"string"},"safetyRatings":{"items":{"$ref":"#/components/schemas/GeminiSafetyRating"},"type":"array"}},"type":"object"},"GeminiSafetyCategory":{"enum":["HARM_CATEGORY_SEXUALLY_EXPLICIT","HARM_CATEGORY_HATE_SPEECH","HARM_CATEGORY_HARASSMENT","HARM_CATEGORY_DANGEROUS_CONTENT"],"type":"string"},"GeminiSafetyRating":{"properties":{"category":{"$ref":"#/components/schemas/GeminiSafetyCategory"},"probability":{"description":"The probability that the content violates the specified safety category","enum":["NEGLIGIBLE","LOW","MEDIUM","HIGH","UNKNOWN"],"type":"string"}},"type":"object"},"GeminiSafetySetting":{"description":"Per request settings for blocking unsafe content. Enforced on GenerateContentResponse.candidates.\n","properties":{"category":{"$ref":"#/components/schemas/GeminiSafetyCategory"},"threshold":{"$ref":"#/components/schemas/GeminiSafetyThreshold"}},"required":["category","threshold"],"type":"object"},"GeminiSafetyThreshold":{"enum":["OFF","BLOCK_NONE","BLOCK_LOW_AND_ABOVE","BLOCK_MEDIUM_AND_ABOVE","BLOCK_ONLY_HIGH"],"type":"string"},"GeminiSystemInstructionContent":{"description":"Instructions for the model to steer it toward better performance. For example, \"Answer as concisely as possible\" or \"Don't use technical terms in your response\". The text strings count toward the token limit. The role field of systemInstruction is ignored and doesn't affect the performance of the model. Note: Only text should be used in parts and content in each part should be in a separate paragraph.\n","properties":{"parts":{"description":"A list of ordered parts that make up a single message. Different parts may have different IANA MIME types. For limits on the inputs, such as the maximum number of tokens or the number of images, see the model specifications on the Google models page.\n","items":{"$ref":"#/components/schemas/GeminiTextPart"},"type":"array"},"role":{"description":"The identity of the entity that creates the message. The following values are supported: user: This indicates that the message is sent by a real person, typically a user-generated message. model: This indicates that the message is generated by the model. The model value is used to insert messages from the model into the conversation during multi-turn conversations. For non-multi-turn conversations, this field can be left blank or unset.\n","enum":["user","model"],"example":"user","type":"string"}},"required":["parts"],"type":"object"},"GeminiTextPart":{"properties":{"text":{"description":"A text prompt or code snippet.","example":"Answer as concisely as possible","type":"string"}},"type":"object"},"GeminiTool":{"description":"A piece of code that enables the system to interact with external systems to perform an action, or set of actions, outside of knowledge and scope of the model. See Function calling.\n","properties":{"functionDeclarations":{"items":{"$ref":"#/components/schemas/GeminiFunctionDeclaration"},"type":"array"}},"type":"object"},"GeminiUsageMetadata":{"properties":{"cachedContentTokenCount":{"description":"Output only. Number of tokens in the cached part in the input (the cached content).","type":"integer"},"candidatesTokenCount":{"description":"Number of tokens in the response(s).","type":"integer"},"candidatesTokensDetails":{"description":"Breakdown of candidate tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"promptTokenCount":{"description":"Number of tokens in the request. When cachedContent is set, this is still the total effective prompt size meaning this includes the number of tokens in the cached content.","type":"integer"},"promptTokensDetails":{"description":"Breakdown of prompt tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"thoughtsTokenCount":{"description":"Number of tokens present in thoughts output.","type":"integer"},"toolUsePromptTokenCount":{"description":"Number of tokens present in tool-use prompt(s).","type":"integer"},"toolUsePromptTokensDetails":{"description":"Breakdown of tool-use prompt tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"totalTokenCount":{"description":"Total number of tokens (prompt + candidates).","type":"integer"},"trafficType":{"description":"Traffic type used for the request (e.g., PROVISIONED_THROUGHPUT).","type":"string"}},"type":"object"},"GeminiVideoMetadata":{"description":"For video input, the start and end offset of the video in Duration format. For example, to specify a 10 second clip starting at 1:00, set \"startOffset\": { \"seconds\": 60 } and \"endOffset\": { \"seconds\": 70 }. The metadata should only be specified while the video data is presented in inlineData or fileData.\n","properties":{"endOffset":{"$ref":"#/components/schemas/GeminiOffset"},"startOffset":{"$ref":"#/components/schemas/GeminiOffset"}},"type":"object"},"Modality":{"description":"Type of input or output content modality.","enum":["MODALITY_UNSPECIFIED","TEXT","IMAGE","VIDEO","AUDIO","DOCUMENT"],"type":"string"},"ModalityTokenCount":{"properties":{"modality":{"$ref":"#/components/schemas/Modality"},"tokenCount":{"description":"Number of tokens for the given modality.","type":"integer"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"vertexai/gemini-3.1-flash-lite-image","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image","video"],"x-comfy-router-output-modalities":["image","text"]}
+{"openapi":"3.0.2","info":{"title":"vertexai/gemini-3.1-flash-lite-image","description":"The request body Comfy Router accepts for the model \"vertexai/gemini-3.1-flash-lite-image\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"a86e35b5a353"},"paths":{"/v2/models/vertexai/gemini-3.1-flash-lite-image":{"post":{"operationId":"runRouterModel","summary":"Run vertexai/gemini-3.1-flash-lite-image synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"example":{"contents":[{"parts":[{"text":"Describe a robot learning to paint, in two sentences."}],"role":"user"}]},"properties":{"contents":{"items":{"$ref":"#/components/schemas/GeminiContent"},"type":"array"},"generationConfig":{"$ref":"#/components/schemas/GeminiGenerationConfig"},"safetySettings":{"items":{"$ref":"#/components/schemas/GeminiSafetySetting"},"type":"array"},"systemInstruction":{"$ref":"#/components/schemas/GeminiSystemInstructionContent"},"tools":{"items":{"$ref":"#/components/schemas/GeminiTool"},"type":"array"},"uploadImagesToStorage":{"description":"If true, generated images will be uploaded to cloud storage and returned as signed URLs instead of inline base64 data. The URLs expire after 24 hours.","type":"boolean"},"videoMetadata":{"$ref":"#/components/schemas/GeminiVideoMetadata"}},"required":["contents"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/GeminiGenerateContentResponse"}],"description":"Comfy Router output schema for the image-returning Gemini models: the same Vertex AI `generateContent` response the text models return, forwarded unchanged, but the generated image arrives as base64 bytes at `candidates[0].content.parts[0].inlineData.data`, with its media type at `inlineData.mimeType`. A response may carry a text part alongside the image part, so select the part by the field you need rather than by index.\n`uploadImagesToStorage` CHANGES THIS SHAPE. When the request sets it, the proxy uploads each generated image to Comfy storage and replaces the part's `inlineData` with `fileData`, carrying a Comfy-signed URL at `fileData.fileUri` and the media type at `fileData.mimeType`; `inlineData` is then absent for that part. The URL expires 24 hours after it is minted, so download the image rather than storing the link. An image whose upload fails is left as `inlineData`, so ONE response can mix both shapes - branch on which key is present rather than assuming either.\nRouter does not re-host a partner link for this family: Gemini returns the image as bytes and no URL of its own, so `uploadImagesToStorage` is an upload of those bytes that the caller asks for, not a rewrite of somebody else's link. A request that leaves the field unset is answered as base64 from end to end WHENEVER VERTEX AI ITSELF SERVES IT - which is always the case for `vertexai/gemini-2.5-flash-image` and `vertexai/gemini-3.1-flash-lite-image`, the two ids on this component that have no other producer, and is the usual but not the only case for the two below.\n`vertexai/gemini-3-pro-image` (`fal`, `pika`, `runware`, `wavespeed`) and `vertexai/gemini-3.1-flash-image` (`fal`, `pika`, `runware`, `wavespeed`) have ALTERNATE PRODUCERS: an eligible request may be served by a partner instead of by Vertex AI. Two PUBLISHED routes reach one, both the alt-provider dispatch in `server/implementation/router_run.go`: `model_provider` names a partner explicitly, and `fallback_provider` (default ON) retries once against a registered alternate when the Vertex AI attempt fails for a reason attributable to Router or to Vertex AI rather than to the request. That retry is suppressed - whatever `fallback_provider` says - on a BYOK call, on a same-key idempotent replay, on a multipart edit operation, and when the model has no registered alternate. SEPARATELY, the `vertexai` partner proxy runs a fallback cascade of its own on these two ids when the platform-credential attempt fails: it is NOT governed by `fallback_provider`, it is disabled only for BYOK requests, and it can therefore fire on a failure the request itself caused and on a call that asked for `fallback_provider: false`. Treat a non-Vertex producer as possible on these two ids whatever the request asked for.\nA response the `fal`, `pika`, `runware` or `wavespeed` leg served, translated back into this contract, carries every generated image as `fileData.fileUri` holding THE PARTNER'S OWN hosted URL, and never `inlineData.data` - all four partners return a hosted URL and never base64 bytes. The URL's lifetime and access policy are the partner's own rather than the 24-hour Comfy-signed URL described above, and Comfy publishes neither, so download the image promptly rather than storing the link. The proxy-level cascade above is NOT bound to that shape: it substitutes another provider's Gemini-compatible body, which may carry inline bytes instead. So the rule is the one the `uploadImagesToStorage` paragraph already states - branch on which key a part actually carries, never on a producer you inferred.\n`uploadImagesToStorage` does not change any of this. The flag is an upload of Vertex AI's own bytes, and off Vertex AI there are no such bytes to upload, so sending it neither produces `inlineData` nor rewrites the partner's link: it is simply inert on an alt-provider leg. A request that carries the key - whatever its value, `false` included - is told so: the translated alt-provider response lists `uploadImagesToStorage` in `X-Comfy-Router-Dropped-Params`, among the request fields the partner's schema has no place for. Only a translated response carries that disclosure: the proxy-level cascade and a `strict_mode: true` call run no translation and send no such header.\nTHREE RESPONSES SKIP THAT TRANSLATION and return the PARTNER'S OWN envelope rather than this component's: `strict_mode: true` asks for the alternate provider's raw schema by definition, a partner response that arrives with a non-empty `Content-Encoding` is forwarded unchanged because Router does not decompress a body to translate it, and a translation that fails or yields nothing forwards the partner's body unchanged - that body may carry no `candidates` key at all. An alt-provider response that IS translated back carries `candidates` and nothing else: `usageMetadata`, `promptFeedback`, `modelVersion`, `responseId` and `finishReason` are Vertex AI's own fields, and the translation has nothing to map them from.\nWHICH PRODUCER ANSWERED is disclosed more weakly than the shape itself. `X-Comfy-Router-Fallback-Provider` names the provider only on a response Router's OWN fallback retry served: the proxy-level cascade sets no header. A same-key idempotent replay repeats both that header and `X-Comfy-Router-Dropped-Params` exactly as the original call sent them, so a replay discloses what the original call did and no more. The request's own `model_provider` is reliable only in the direction it states - it says a partner was ASKED for, not that none answered when it was absent. The published per-model document of a model with a PUBLISHED leg carries an `x-comfy-router-alt-providers` marker naming it; absence of that marker is NOT a guarantee that the model has no alternate producer, since a registered leg that is not cleared for public disclosure still dispatches without appearing there. Which partner served a given request is a Comfy routing decision that can change without notice, and is not part of this contract.\nAs for the text models, `promptFeedback` and `usageMetadata` are present on a safety-blocked response that generated nothing; `candidates` is the field to branch on. Both are Vertex AI's own fields: on a response an alternate producer served, see the paragraph above for what is absent.\nThe example's `inlineData.data` is the PLACEHOLDER `PGJhc2U2ND4=`, which decodes to the literal text `\u003cbase64\u003e` - a real image would be megabytes, and a real provider response does not belong in a published spec. It is spelled as valid base64 rather than as a bare `\u003cbase64\u003e` marker because `GeminiInlineData.data` declares `format: byte`, which this document's own example validation enforces.\nThe example is authored ONCE for all four ids above and rendered into each per-model document unchanged, so its `modelVersion` is ILLUSTRATIVE RATHER THAN ENDPOINT-SPECIFIC. Vertex AI echoes the id that actually served the call, so a `vertexai/gemini-3-pro-image` response reads `gemini-3-pro-image` there - not the `gemini-2.5-flash-image` the example happens to show - and on an alt-provider-served response the field is absent entirely, per the paragraph above. Where `modelVersion` is present, read it as echoing the served id, never as the literal value printed in the document you are reading.","example":{"candidates":[{"content":{"parts":[{"inlineData":{"data":"PGJhc2U2ND4=","mimeType":"image/png"}}],"role":"model"},"finishReason":"STOP"}],"modelVersion":"gemini-2.5-flash-image","responseId":"7c6b5a49-3827-1605-f4e3-d2c1b0a99887","usageMetadata":{"candidatesTokenCount":1290,"promptTokenCount":11,"totalTokenCount":1301}}}}}}}}}},"components":{"schemas":{"GeminiCandidate":{"properties":{"citationMetadata":{"$ref":"#/components/schemas/GeminiCitationMetadata"},"content":{"$ref":"#/components/schemas/GeminiContent"},"finishReason":{"type":"string"},"safetyRatings":{"items":{"$ref":"#/components/schemas/GeminiSafetyRating"},"type":"array"}},"type":"object"},"GeminiCitation":{"properties":{"authors":{"items":{"type":"string"},"type":"array"},"endIndex":{"type":"integer"},"license":{"type":"string"},"publicationDate":{"format":"date","type":"string"},"startIndex":{"type":"integer"},"title":{"type":"string"},"uri":{"type":"string"}},"type":"object"},"GeminiCitationMetadata":{"properties":{"citations":{"items":{"$ref":"#/components/schemas/GeminiCitation"},"type":"array"}},"type":"object"},"GeminiContent":{"description":"The content of the current conversation with the model. For single-turn queries, this is a single instance. For multi-turn queries, this is a repeated field that contains conversation history and the latest request.\n","properties":{"parts":{"items":{"$ref":"#/components/schemas/GeminiPart"},"type":"array"},"role":{"enum":["user","model"],"example":"user","type":"string"}},"required":["parts"],"type":"object"},"GeminiFileData":{"description":"URI based data.","properties":{"fileUri":{"description":"URI","type":"string"},"mimeType":{"$ref":"#/components/schemas/GeminiMimeType"}},"type":"object"},"GeminiFunctionDeclaration":{"properties":{"description":{"type":"string"},"name":{"type":"string"},"parameters":{"description":"JSON schema for the function parameters","type":"object"}},"required":["name"],"type":"object"},"GeminiGenerateContentResponse":{"properties":{"candidates":{"items":{"$ref":"#/components/schemas/GeminiCandidate"},"type":"array"},"createTime":{"description":"Timestamp when the response was created.","type":"string"},"modelVersion":{"description":"The model version used to generate the response.","type":"string"},"promptFeedback":{"$ref":"#/components/schemas/GeminiPromptFeedback"},"responseId":{"description":"Unique identifier for the response.","type":"string"},"usageMetadata":{"$ref":"#/components/schemas/GeminiUsageMetadata"}},"type":"object"},"GeminiGenerationConfig":{"description":"Sampling, length and output settings for the generation. Every field is optional: the fields below that declare a `default` apply it when omitted, and the rest fall back to the model's own behaviour.\n","properties":{"imageConfig":{"description":"Configuration for image generation","properties":{"aspectRatio":{"description":"Aspect ratio for generated images","type":"string"},"imageOutputOptions":{"description":"Optional. The image output format for generated images.","properties":{"compressionQuality":{"description":"Optional. The compression quality of the output image.","type":"integer"},"mimeType":{"description":"Optional. The image format that the output should be saved as, on the Vertex AI paths: requests served by Comfy's own credentials, and BYOK requests authenticated with a GCP service account. Accepted values there are `image/png` and `image/jpeg`, matched case-insensitively and normalised to lower case before the request is forwarded; any other value is rejected with a 400 naming this field. Defaults to `image/png` when omitted. A BYOK request authenticated with a Google AI Studio API key is the exception: that upstream has no such property and rejects the whole call if it is present, so the field is dropped from the request rather than honoured or rejected, and the output format is whatever AI Studio chooses. On every path, read the media type back from the response part you are given (`inlineData.mimeType`, or `fileData.mimeType` when `uploadImagesToStorage` is set) rather than assuming the value you sent.","type":"string"}},"type":"object"},"imageSize":{"description":"Optional. Specifies the size of generated images. Supported values are 1K, 2K, 4K. If not specified, the model will use default value 1K.","type":"string"}},"type":"object"},"maxOutputTokens":{"description":"Maximum number of tokens that can be generated in the response. A token is approximately 4 characters. 100 tokens correspond to roughly 60-80 words.\n","example":2048,"maximum":65536,"minimum":16,"type":"integer"},"responseModalities":{"items":{"enum":["TEXT","IMAGE"],"type":"string"},"type":"array"},"seed":{"description":"When seed is fixed to a specific value, the model makes a best effort to provide the same response for repeated requests. Deterministic output isn't guaranteed. Also, changing the model or parameter settings, such as the temperature, can cause variations in the response even when you use the same seed value. By default, a random seed value is used. Available for the following models:, gemini-2.5-flash, gemini-2.5-pro, gemini-2.5-flash-preview-04-1, gemini-2.5-pro-preview-05-0, gemini-2.0-flash-lite-00, gemini-2.0-flash-001\n","example":343940597,"type":"integer"},"stopSequences":{"items":{"type":"string"},"type":"array"},"temperature":{"default":1,"description":"The temperature is used for sampling during response generation, which occurs when topP and topK are applied. Temperature controls the degree of randomness in token selection. Lower temperatures are good for prompts that require a less open-ended or creative response, while higher temperatures can lead to more diverse or creative results. A temperature of 0 means that the highest probability tokens are always selected. In this case, responses for a given prompt are mostly deterministic, but a small amount of variation is still possible. If the model returns a response that's too generic, too short, or the model gives a fallback response, try increasing the temperature\n","format":"float","maximum":2,"minimum":0,"type":"number"},"thinkingConfig":{"description":"Optional. Configuration for thinking features. Thinking is a process where the model breaks down a complex task into smaller steps to generate a higher-quality response.","properties":{"includeThoughts":{"description":"Optional. If true, the model will include its thoughts in the response.","type":"boolean"},"thinkingBudget":{"description":"Optional. The token budget for the model's thinking process. The model will make a best effort to stay within this budget.","type":"integer"},"thinkingLevel":{"description":"Optional. The thinking level for the model.","enum":["THINKING_LEVEL_UNSPECIFIED","LOW","MEDIUM","HIGH","MINIMAL"],"type":"string"}},"type":"object"},"topK":{"default":40,"description":"Top-K changes how the model selects tokens for output. A top-K of 1 means the next selected token is the most probable among all tokens in the model's vocabulary. A top-K of 3 means that the next token is selected from among the 3 most probable tokens by using temperature.\n","example":40,"minimum":1,"type":"integer"},"topP":{"default":0.95,"description":"If specified, nucleus sampling is used.\nTop-P changes how the model selects tokens for output. Tokens are selected from the most (see top-K) to least probable until the sum of their probabilities equals the top-P value. For example, if tokens A, B, and C have a probability of 0.3, 0.2, and 0.1 and the top-P value is 0.5, then the model will select either A or B as the next token by using temperature and excludes C as a candidate.\nSpecify a lower value for less random responses and a higher value for more random responses.\n","format":"float","maximum":1,"minimum":0,"type":"number"}},"type":"object"},"GeminiInlineData":{"description":"Inline data in raw bytes. For gemini-2.0-flash-lite and gemini-2.0-flash, you can specify up to 3000 images by using inlineData.\n","properties":{"data":{"description":"The base64 encoding of the image, PDF, or video to include inline in the prompt. When including media inline, you must also specify the media type (mimeType) of the data. Size limit: 20MB\n","format":"byte","type":"string"},"mimeType":{"$ref":"#/components/schemas/GeminiMimeType"}},"type":"object"},"GeminiMimeType":{"description":"The media type of the file specified in the data or fileUri fields. Acceptable values include the following. For gemini-2.0-flash-lite and gemini-2.0-flash, the maximum length of an audio file is 8.4 hours and the maximum length of a video file (without audio) is one hour. For more information, see Gemini audio and video requirements. Text files must be UTF-8 encoded. The contents of the text file count toward the token limit. There is no limit on image resolution.","enum":["application/pdf","audio/mpeg","audio/mp3","audio/wav","image/png","image/jpeg","image/webp","text/plain","video/mov","video/mpeg","video/mp4","video/mpg","video/avi","video/wmv","video/mpegps","video/flv","image/heic","image/heif","audio/flac","video/webm"],"type":"string"},"GeminiOffset":{"description":"Represents a duration offset for video timeline positions.\n","properties":{"nanos":{"description":"Signed fractions of a second at nanosecond resolution. Negative second values with fractions must still have non-negative nanos values.\n","example":0,"maximum":999999999,"minimum":0,"type":"integer"},"seconds":{"description":"Signed seconds of the span of time. Must be from -315,576,000,000 to +315,576,000,000 inclusive.\n","example":60,"maximum":315576000000,"minimum":-315576000000,"type":"integer"}},"type":"object"},"GeminiPart":{"properties":{"fileData":{"$ref":"#/components/schemas/GeminiFileData"},"inlineData":{"$ref":"#/components/schemas/GeminiInlineData"},"mediaProcessing":{"description":"How the model reads this part's video. Set \"AGENTIC\" to let the model decide which segments to inspect, instead of fixed-rate frame sampling. Omit for the default fixed-rate sampling. Supported on gemini-3.7-flash and newer Flash models.\n","type":"string"},"text":{"description":"A text prompt or code snippet.","example":"Write a story about a robot learning to paint","type":"string"},"thought":{"description":"Indicates this part is a thinking/reasoning step from the model.","type":"boolean"}},"type":"object"},"GeminiPromptFeedback":{"properties":{"blockReason":{"type":"string"},"blockReasonMessage":{"type":"string"},"safetyRatings":{"items":{"$ref":"#/components/schemas/GeminiSafetyRating"},"type":"array"}},"type":"object"},"GeminiSafetyCategory":{"enum":["HARM_CATEGORY_SEXUALLY_EXPLICIT","HARM_CATEGORY_HATE_SPEECH","HARM_CATEGORY_HARASSMENT","HARM_CATEGORY_DANGEROUS_CONTENT"],"type":"string"},"GeminiSafetyRating":{"properties":{"category":{"$ref":"#/components/schemas/GeminiSafetyCategory"},"probability":{"description":"The probability that the content violates the specified safety category","enum":["NEGLIGIBLE","LOW","MEDIUM","HIGH","UNKNOWN"],"type":"string"}},"type":"object"},"GeminiSafetySetting":{"description":"Per request settings for blocking unsafe content. Enforced on GenerateContentResponse.candidates.\n","properties":{"category":{"$ref":"#/components/schemas/GeminiSafetyCategory"},"threshold":{"$ref":"#/components/schemas/GeminiSafetyThreshold"}},"required":["category","threshold"],"type":"object"},"GeminiSafetyThreshold":{"enum":["OFF","BLOCK_NONE","BLOCK_LOW_AND_ABOVE","BLOCK_MEDIUM_AND_ABOVE","BLOCK_ONLY_HIGH"],"type":"string"},"GeminiSystemInstructionContent":{"description":"Instructions for the model to steer it toward better performance. For example, \"Answer as concisely as possible\" or \"Don't use technical terms in your response\". The text strings count toward the token limit. The role field of systemInstruction is ignored and doesn't affect the performance of the model. Note: Only text should be used in parts and content in each part should be in a separate paragraph.\n","properties":{"parts":{"description":"A list of ordered parts that make up a single message. Different parts may have different IANA MIME types. For limits on the inputs, such as the maximum number of tokens or the number of images, see the model specifications on the Google models page.\n","items":{"$ref":"#/components/schemas/GeminiTextPart"},"type":"array"},"role":{"description":"The identity of the entity that creates the message. The following values are supported: user: This indicates that the message is sent by a real person, typically a user-generated message. model: This indicates that the message is generated by the model. The model value is used to insert messages from the model into the conversation during multi-turn conversations. For non-multi-turn conversations, this field can be left blank or unset.\n","enum":["user","model"],"example":"user","type":"string"}},"required":["parts"],"type":"object"},"GeminiTextPart":{"properties":{"text":{"description":"A text prompt or code snippet.","example":"Answer as concisely as possible","type":"string"}},"type":"object"},"GeminiTool":{"description":"A piece of code that enables the system to interact with external systems to perform an action, or set of actions, outside of knowledge and scope of the model. See Function calling.\n","properties":{"functionDeclarations":{"items":{"$ref":"#/components/schemas/GeminiFunctionDeclaration"},"type":"array"}},"type":"object"},"GeminiUsageMetadata":{"properties":{"cachedContentTokenCount":{"description":"Output only. Number of tokens in the cached part in the input (the cached content).","type":"integer"},"candidatesTokenCount":{"description":"Number of tokens in the response(s).","type":"integer"},"candidatesTokensDetails":{"description":"Breakdown of candidate tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"promptTokenCount":{"description":"Number of tokens in the request. When cachedContent is set, this is still the total effective prompt size meaning this includes the number of tokens in the cached content.","type":"integer"},"promptTokensDetails":{"description":"Breakdown of prompt tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"thoughtsTokenCount":{"description":"Number of tokens present in thoughts output.","type":"integer"},"toolUsePromptTokenCount":{"description":"Number of tokens present in tool-use prompt(s).","type":"integer"},"toolUsePromptTokensDetails":{"description":"Breakdown of tool-use prompt tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"totalTokenCount":{"description":"Total number of tokens (prompt + candidates).","type":"integer"},"trafficType":{"description":"Traffic type used for the request (e.g., PROVISIONED_THROUGHPUT).","type":"string"}},"type":"object"},"GeminiVideoMetadata":{"description":"For video input, the start and end offset of the video in Duration format. For example, to specify a 10 second clip starting at 1:00, set \"startOffset\": { \"seconds\": 60 } and \"endOffset\": { \"seconds\": 70 }. The metadata should only be specified while the video data is presented in inlineData or fileData.\n","properties":{"endOffset":{"$ref":"#/components/schemas/GeminiOffset"},"startOffset":{"$ref":"#/components/schemas/GeminiOffset"}},"type":"object"},"Modality":{"description":"Type of input or output content modality.","enum":["MODALITY_UNSPECIFIED","TEXT","IMAGE","VIDEO","AUDIO","DOCUMENT"],"type":"string"},"ModalityTokenCount":{"properties":{"modality":{"$ref":"#/components/schemas/Modality"},"tokenCount":{"description":"Number of tokens for the given modality.","type":"integer"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"vertexai/gemini-3.1-flash-lite-image","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image","video"],"x-comfy-router-output-modalities":["image","text"]}
diff --git a/router-schemas/vertexai/gemini-nano-banana-2.1.json b/router-schemas/vertexai/gemini-nano-banana-2.1.json
index 7e141a2e15..31fe3e07ba 100644
--- a/router-schemas/vertexai/gemini-nano-banana-2.1.json
+++ b/router-schemas/vertexai/gemini-nano-banana-2.1.json
@@ -1 +1 @@
-{"openapi":"3.0.2","info":{"title":"vertexai/gemini-nano-banana-2.1","description":"The request body Comfy Router accepts for the model \"vertexai/gemini-nano-banana-2.1\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"4fddddb86113"},"paths":{"/v2/models/vertexai/gemini-nano-banana-2.1":{"post":{"operationId":"runRouterModel","summary":"Run vertexai/gemini-nano-banana-2.1 synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"example":{"contents":[{"parts":[{"text":"Describe a robot learning to paint, in two sentences."}],"role":"user"}]},"properties":{"contents":{"items":{"$ref":"#/components/schemas/GeminiContent"},"type":"array"},"generationConfig":{"$ref":"#/components/schemas/GeminiGenerationConfig"},"safetySettings":{"items":{"$ref":"#/components/schemas/GeminiSafetySetting"},"type":"array"},"systemInstruction":{"$ref":"#/components/schemas/GeminiSystemInstructionContent"},"tools":{"items":{"$ref":"#/components/schemas/GeminiTool"},"type":"array"},"uploadImagesToStorage":{"description":"If true, generated images will be uploaded to cloud storage and returned as signed URLs instead of inline base64 data. The URLs expire after 24 hours.","type":"boolean"},"videoMetadata":{"$ref":"#/components/schemas/GeminiVideoMetadata"}},"required":["contents"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/GeminiGenerateContentResponse"}],"description":"Comfy Router output schema for the image-returning Gemini models: the same Vertex AI `generateContent` response the text models return, forwarded unchanged, but the generated image arrives as base64 bytes at `candidates[0].content.parts[0].inlineData.data`, with its media type at `inlineData.mimeType`. A response may carry a text part alongside the image part, so select the part by the field you need rather than by index.\n`uploadImagesToStorage` CHANGES THIS SHAPE. When the request sets it, the proxy uploads each generated image to Comfy storage and replaces the part's `inlineData` with `fileData`, carrying a Comfy-signed URL at `fileData.fileUri` and the media type at `fileData.mimeType`; `inlineData` is then absent for that part. The URL expires 24 hours after it is minted, so download the image rather than storing the link. An image whose upload fails is left as `inlineData`, so ONE response can mix both shapes - branch on which key is present rather than assuming either.\nRouter does not re-host a partner link for this family: Gemini returns the image as bytes and no URL of its own, so `uploadImagesToStorage` is an upload of those bytes that the caller asks for, not a rewrite of somebody else's link. A request that leaves the field unset is answered as base64 from end to end WHENEVER VERTEX AI ITSELF SERVES IT - which is always the case for `vertexai/gemini-2.5-flash-image` and `vertexai/gemini-3.1-flash-lite-image`, the two ids on this component that have no other producer, and is the usual but not the only case for the two below.\n`vertexai/gemini-3-pro-image` (`fal`, `runware`, `wavespeed`) and `vertexai/gemini-3.1-flash-image` (`fal`, `runware`, `wavespeed`) have ALTERNATE PRODUCERS: an eligible request may be served by a partner instead of by Vertex AI. Two PUBLISHED routes reach one, both the alt-provider dispatch in `server/implementation/router_run.go`: `model_provider` names a partner explicitly, and `fallback_provider` (default ON) retries once against a registered alternate when the Vertex AI attempt fails for a reason attributable to Router or to Vertex AI rather than to the request. That retry is suppressed - whatever `fallback_provider` says - on a BYOK call, on a same-key idempotent replay, on a multipart edit operation, and when the model has no registered alternate. SEPARATELY, the `vertexai` partner proxy runs a fallback cascade of its own on these two ids when the platform-credential attempt fails: it is NOT governed by `fallback_provider`, it is disabled only for BYOK requests, and it can therefore fire on a failure the request itself caused and on a call that asked for `fallback_provider: false`. Treat a non-Vertex producer as possible on these two ids whatever the request asked for.\nA response the `fal`, `runware` or `wavespeed` leg served, translated back into this contract, carries every generated image as `fileData.fileUri` holding THE PARTNER'S OWN hosted URL, and never `inlineData.data` - all three partners return a hosted URL and never base64 bytes. The URL's lifetime and access policy are the partner's own rather than the 24-hour Comfy-signed URL described above, and Comfy publishes neither, so download the image promptly rather than storing the link. The proxy-level cascade above is NOT bound to that shape: it substitutes another provider's Gemini-compatible body, which may carry inline bytes instead. So the rule is the one the `uploadImagesToStorage` paragraph already states - branch on which key a part actually carries, never on a producer you inferred.\n`uploadImagesToStorage` does not change any of this. The flag is an upload of Vertex AI's own bytes, and off Vertex AI there are no such bytes to upload, so sending it neither produces `inlineData` nor rewrites the partner's link: it is simply inert on an alt-provider leg. A request that carries the key - whatever its value, `false` included - is told so: the translated alt-provider response lists `uploadImagesToStorage` in `X-Comfy-Router-Dropped-Params`, among the request fields the partner's schema has no place for. Only a translated response carries that disclosure: the proxy-level cascade and a `strict_mode: true` call run no translation and send no such header.\nTHREE RESPONSES SKIP THAT TRANSLATION and return the PARTNER'S OWN envelope rather than this component's: `strict_mode: true` asks for the alternate provider's raw schema by definition, a partner response that arrives with a non-empty `Content-Encoding` is forwarded unchanged because Router does not decompress a body to translate it, and a translation that fails or yields nothing forwards the partner's body unchanged - that body may carry no `candidates` key at all. An alt-provider response that IS translated back carries `candidates` and nothing else: `usageMetadata`, `promptFeedback`, `modelVersion`, `responseId` and `finishReason` are Vertex AI's own fields, and the translation has nothing to map them from.\nWHICH PRODUCER ANSWERED is disclosed more weakly than the shape itself. `X-Comfy-Router-Fallback-Provider` names the provider only on a response Router's OWN fallback retry served: the proxy-level cascade sets no header. A same-key idempotent replay repeats both that header and `X-Comfy-Router-Dropped-Params` exactly as the original call sent them, so a replay discloses what the original call did and no more. The request's own `model_provider` is reliable only in the direction it states - it says a partner was ASKED for, not that none answered when it was absent. The published per-model document of a model with a PUBLISHED leg carries an `x-comfy-router-alt-providers` marker naming it; absence of that marker is NOT a guarantee that the model has no alternate producer, since a registered leg that is not cleared for public disclosure still dispatches without appearing there. Which partner served a given request is a Comfy routing decision that can change without notice, and is not part of this contract.\nAs for the text models, `promptFeedback` and `usageMetadata` are present on a safety-blocked response that generated nothing; `candidates` is the field to branch on. Both are Vertex AI's own fields: on a response an alternate producer served, see the paragraph above for what is absent.\nThe example's `inlineData.data` is the PLACEHOLDER `PGJhc2U2ND4=`, which decodes to the literal text `\u003cbase64\u003e` - a real image would be megabytes, and a real provider response does not belong in a published spec. It is spelled as valid base64 rather than as a bare `\u003cbase64\u003e` marker because `GeminiInlineData.data` declares `format: byte`, which this document's own example validation enforces.\nThe example is authored ONCE for all four ids above and rendered into each per-model document unchanged, so its `modelVersion` is ILLUSTRATIVE RATHER THAN ENDPOINT-SPECIFIC. Vertex AI echoes the id that actually served the call, so a `vertexai/gemini-3-pro-image` response reads `gemini-3-pro-image` there - not the `gemini-2.5-flash-image` the example happens to show - and on an alt-provider-served response the field is absent entirely, per the paragraph above. Where `modelVersion` is present, read it as echoing the served id, never as the literal value printed in the document you are reading.","example":{"candidates":[{"content":{"parts":[{"inlineData":{"data":"PGJhc2U2ND4=","mimeType":"image/png"}}],"role":"model"},"finishReason":"STOP"}],"modelVersion":"gemini-2.5-flash-image","responseId":"7c6b5a49-3827-1605-f4e3-d2c1b0a99887","usageMetadata":{"candidatesTokenCount":1290,"promptTokenCount":11,"totalTokenCount":1301}}}}}}}}}},"components":{"schemas":{"GeminiCandidate":{"properties":{"citationMetadata":{"$ref":"#/components/schemas/GeminiCitationMetadata"},"content":{"$ref":"#/components/schemas/GeminiContent"},"finishReason":{"type":"string"},"safetyRatings":{"items":{"$ref":"#/components/schemas/GeminiSafetyRating"},"type":"array"}},"type":"object"},"GeminiCitation":{"properties":{"authors":{"items":{"type":"string"},"type":"array"},"endIndex":{"type":"integer"},"license":{"type":"string"},"publicationDate":{"format":"date","type":"string"},"startIndex":{"type":"integer"},"title":{"type":"string"},"uri":{"type":"string"}},"type":"object"},"GeminiCitationMetadata":{"properties":{"citations":{"items":{"$ref":"#/components/schemas/GeminiCitation"},"type":"array"}},"type":"object"},"GeminiContent":{"description":"The content of the current conversation with the model. For single-turn queries, this is a single instance. For multi-turn queries, this is a repeated field that contains conversation history and the latest request.\n","properties":{"parts":{"items":{"$ref":"#/components/schemas/GeminiPart"},"type":"array"},"role":{"enum":["user","model"],"example":"user","type":"string"}},"required":["parts"],"type":"object"},"GeminiFileData":{"description":"URI based data.","properties":{"fileUri":{"description":"URI","type":"string"},"mimeType":{"$ref":"#/components/schemas/GeminiMimeType"}},"type":"object"},"GeminiFunctionDeclaration":{"properties":{"description":{"type":"string"},"name":{"type":"string"},"parameters":{"description":"JSON schema for the function parameters","type":"object"}},"required":["name"],"type":"object"},"GeminiGenerateContentResponse":{"properties":{"candidates":{"items":{"$ref":"#/components/schemas/GeminiCandidate"},"type":"array"},"createTime":{"description":"Timestamp when the response was created.","type":"string"},"modelVersion":{"description":"The model version used to generate the response.","type":"string"},"promptFeedback":{"$ref":"#/components/schemas/GeminiPromptFeedback"},"responseId":{"description":"Unique identifier for the response.","type":"string"},"usageMetadata":{"$ref":"#/components/schemas/GeminiUsageMetadata"}},"type":"object"},"GeminiGenerationConfig":{"description":"Sampling, length and output settings for the generation. Every field is optional: the fields below that declare a `default` apply it when omitted, and the rest fall back to the model's own behaviour.\n","properties":{"imageConfig":{"description":"Configuration for image generation","properties":{"aspectRatio":{"description":"Aspect ratio for generated images","type":"string"},"imageOutputOptions":{"description":"Optional. The image output format for generated images.","properties":{"compressionQuality":{"description":"Optional. The compression quality of the output image.","type":"integer"},"mimeType":{"description":"Optional. The image format that the output should be saved as, on the Vertex AI paths: requests served by Comfy's own credentials, and BYOK requests authenticated with a GCP service account. Accepted values there are `image/png` and `image/jpeg`, matched case-insensitively and normalised to lower case before the request is forwarded; any other value is rejected with a 400 naming this field. Defaults to `image/png` when omitted. A BYOK request authenticated with a Google AI Studio API key is the exception: that upstream has no such property and rejects the whole call if it is present, so the field is dropped from the request rather than honoured or rejected, and the output format is whatever AI Studio chooses. On every path, read the media type back from the response part you are given (`inlineData.mimeType`, or `fileData.mimeType` when `uploadImagesToStorage` is set) rather than assuming the value you sent.","type":"string"}},"type":"object"},"imageSize":{"description":"Optional. Specifies the size of generated images. Supported values are 1K, 2K, 4K. If not specified, the model will use default value 1K.","type":"string"}},"type":"object"},"maxOutputTokens":{"description":"Maximum number of tokens that can be generated in the response. A token is approximately 4 characters. 100 tokens correspond to roughly 60-80 words.\n","example":2048,"maximum":65536,"minimum":16,"type":"integer"},"responseModalities":{"items":{"enum":["TEXT","IMAGE"],"type":"string"},"type":"array"},"seed":{"description":"When seed is fixed to a specific value, the model makes a best effort to provide the same response for repeated requests. Deterministic output isn't guaranteed. Also, changing the model or parameter settings, such as the temperature, can cause variations in the response even when you use the same seed value. By default, a random seed value is used. Available for the following models:, gemini-2.5-flash, gemini-2.5-pro, gemini-2.5-flash-preview-04-1, gemini-2.5-pro-preview-05-0, gemini-2.0-flash-lite-00, gemini-2.0-flash-001\n","example":343940597,"type":"integer"},"stopSequences":{"items":{"type":"string"},"type":"array"},"temperature":{"default":1,"description":"The temperature is used for sampling during response generation, which occurs when topP and topK are applied. Temperature controls the degree of randomness in token selection. Lower temperatures are good for prompts that require a less open-ended or creative response, while higher temperatures can lead to more diverse or creative results. A temperature of 0 means that the highest probability tokens are always selected. In this case, responses for a given prompt are mostly deterministic, but a small amount of variation is still possible. If the model returns a response that's too generic, too short, or the model gives a fallback response, try increasing the temperature\n","format":"float","maximum":2,"minimum":0,"type":"number"},"thinkingConfig":{"description":"Optional. Configuration for thinking features. Thinking is a process where the model breaks down a complex task into smaller steps to generate a higher-quality response.","properties":{"includeThoughts":{"description":"Optional. If true, the model will include its thoughts in the response.","type":"boolean"},"thinkingBudget":{"description":"Optional. The token budget for the model's thinking process. The model will make a best effort to stay within this budget.","type":"integer"},"thinkingLevel":{"description":"Optional. The thinking level for the model.","enum":["THINKING_LEVEL_UNSPECIFIED","LOW","MEDIUM","HIGH","MINIMAL"],"type":"string"}},"type":"object"},"topK":{"default":40,"description":"Top-K changes how the model selects tokens for output. A top-K of 1 means the next selected token is the most probable among all tokens in the model's vocabulary. A top-K of 3 means that the next token is selected from among the 3 most probable tokens by using temperature.\n","example":40,"minimum":1,"type":"integer"},"topP":{"default":0.95,"description":"If specified, nucleus sampling is used.\nTop-P changes how the model selects tokens for output. Tokens are selected from the most (see top-K) to least probable until the sum of their probabilities equals the top-P value. For example, if tokens A, B, and C have a probability of 0.3, 0.2, and 0.1 and the top-P value is 0.5, then the model will select either A or B as the next token by using temperature and excludes C as a candidate.\nSpecify a lower value for less random responses and a higher value for more random responses.\n","format":"float","maximum":1,"minimum":0,"type":"number"}},"type":"object"},"GeminiInlineData":{"description":"Inline data in raw bytes. For gemini-2.0-flash-lite and gemini-2.0-flash, you can specify up to 3000 images by using inlineData.\n","properties":{"data":{"description":"The base64 encoding of the image, PDF, or video to include inline in the prompt. When including media inline, you must also specify the media type (mimeType) of the data. Size limit: 20MB\n","format":"byte","type":"string"},"mimeType":{"$ref":"#/components/schemas/GeminiMimeType"}},"type":"object"},"GeminiMimeType":{"description":"The media type of the file specified in the data or fileUri fields. Acceptable values include the following. For gemini-2.0-flash-lite and gemini-2.0-flash, the maximum length of an audio file is 8.4 hours and the maximum length of a video file (without audio) is one hour. For more information, see Gemini audio and video requirements. Text files must be UTF-8 encoded. The contents of the text file count toward the token limit. There is no limit on image resolution.","enum":["application/pdf","audio/mpeg","audio/mp3","audio/wav","image/png","image/jpeg","image/webp","text/plain","video/mov","video/mpeg","video/mp4","video/mpg","video/avi","video/wmv","video/mpegps","video/flv","image/heic","image/heif","audio/flac","video/webm"],"type":"string"},"GeminiOffset":{"description":"Represents a duration offset for video timeline positions.\n","properties":{"nanos":{"description":"Signed fractions of a second at nanosecond resolution. Negative second values with fractions must still have non-negative nanos values.\n","example":0,"maximum":999999999,"minimum":0,"type":"integer"},"seconds":{"description":"Signed seconds of the span of time. Must be from -315,576,000,000 to +315,576,000,000 inclusive.\n","example":60,"maximum":315576000000,"minimum":-315576000000,"type":"integer"}},"type":"object"},"GeminiPart":{"properties":{"fileData":{"$ref":"#/components/schemas/GeminiFileData"},"inlineData":{"$ref":"#/components/schemas/GeminiInlineData"},"mediaProcessing":{"description":"How the model reads this part's video. Set \"AGENTIC\" to let the model decide which segments to inspect, instead of fixed-rate frame sampling. Omit for the default fixed-rate sampling. Supported on gemini-3.7-flash and newer Flash models.\n","type":"string"},"text":{"description":"A text prompt or code snippet.","example":"Write a story about a robot learning to paint","type":"string"},"thought":{"description":"Indicates this part is a thinking/reasoning step from the model.","type":"boolean"}},"type":"object"},"GeminiPromptFeedback":{"properties":{"blockReason":{"type":"string"},"blockReasonMessage":{"type":"string"},"safetyRatings":{"items":{"$ref":"#/components/schemas/GeminiSafetyRating"},"type":"array"}},"type":"object"},"GeminiSafetyCategory":{"enum":["HARM_CATEGORY_SEXUALLY_EXPLICIT","HARM_CATEGORY_HATE_SPEECH","HARM_CATEGORY_HARASSMENT","HARM_CATEGORY_DANGEROUS_CONTENT"],"type":"string"},"GeminiSafetyRating":{"properties":{"category":{"$ref":"#/components/schemas/GeminiSafetyCategory"},"probability":{"description":"The probability that the content violates the specified safety category","enum":["NEGLIGIBLE","LOW","MEDIUM","HIGH","UNKNOWN"],"type":"string"}},"type":"object"},"GeminiSafetySetting":{"description":"Per request settings for blocking unsafe content. Enforced on GenerateContentResponse.candidates.\n","properties":{"category":{"$ref":"#/components/schemas/GeminiSafetyCategory"},"threshold":{"$ref":"#/components/schemas/GeminiSafetyThreshold"}},"required":["category","threshold"],"type":"object"},"GeminiSafetyThreshold":{"enum":["OFF","BLOCK_NONE","BLOCK_LOW_AND_ABOVE","BLOCK_MEDIUM_AND_ABOVE","BLOCK_ONLY_HIGH"],"type":"string"},"GeminiSystemInstructionContent":{"description":"Instructions for the model to steer it toward better performance. For example, \"Answer as concisely as possible\" or \"Don't use technical terms in your response\". The text strings count toward the token limit. The role field of systemInstruction is ignored and doesn't affect the performance of the model. Note: Only text should be used in parts and content in each part should be in a separate paragraph.\n","properties":{"parts":{"description":"A list of ordered parts that make up a single message. Different parts may have different IANA MIME types. For limits on the inputs, such as the maximum number of tokens or the number of images, see the model specifications on the Google models page.\n","items":{"$ref":"#/components/schemas/GeminiTextPart"},"type":"array"},"role":{"description":"The identity of the entity that creates the message. The following values are supported: user: This indicates that the message is sent by a real person, typically a user-generated message. model: This indicates that the message is generated by the model. The model value is used to insert messages from the model into the conversation during multi-turn conversations. For non-multi-turn conversations, this field can be left blank or unset.\n","enum":["user","model"],"example":"user","type":"string"}},"required":["parts"],"type":"object"},"GeminiTextPart":{"properties":{"text":{"description":"A text prompt or code snippet.","example":"Answer as concisely as possible","type":"string"}},"type":"object"},"GeminiTool":{"description":"A piece of code that enables the system to interact with external systems to perform an action, or set of actions, outside of knowledge and scope of the model. See Function calling.\n","properties":{"functionDeclarations":{"items":{"$ref":"#/components/schemas/GeminiFunctionDeclaration"},"type":"array"}},"type":"object"},"GeminiUsageMetadata":{"properties":{"cachedContentTokenCount":{"description":"Output only. Number of tokens in the cached part in the input (the cached content).","type":"integer"},"candidatesTokenCount":{"description":"Number of tokens in the response(s).","type":"integer"},"candidatesTokensDetails":{"description":"Breakdown of candidate tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"promptTokenCount":{"description":"Number of tokens in the request. When cachedContent is set, this is still the total effective prompt size meaning this includes the number of tokens in the cached content.","type":"integer"},"promptTokensDetails":{"description":"Breakdown of prompt tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"thoughtsTokenCount":{"description":"Number of tokens present in thoughts output.","type":"integer"},"toolUsePromptTokenCount":{"description":"Number of tokens present in tool-use prompt(s).","type":"integer"},"toolUsePromptTokensDetails":{"description":"Breakdown of tool-use prompt tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"totalTokenCount":{"description":"Total number of tokens (prompt + candidates).","type":"integer"},"trafficType":{"description":"Traffic type used for the request (e.g., PROVISIONED_THROUGHPUT).","type":"string"}},"type":"object"},"GeminiVideoMetadata":{"description":"For video input, the start and end offset of the video in Duration format. For example, to specify a 10 second clip starting at 1:00, set \"startOffset\": { \"seconds\": 60 } and \"endOffset\": { \"seconds\": 70 }. The metadata should only be specified while the video data is presented in inlineData or fileData.\n","properties":{"endOffset":{"$ref":"#/components/schemas/GeminiOffset"},"startOffset":{"$ref":"#/components/schemas/GeminiOffset"}},"type":"object"},"Modality":{"description":"Type of input or output content modality.","enum":["MODALITY_UNSPECIFIED","TEXT","IMAGE","VIDEO","AUDIO","DOCUMENT"],"type":"string"},"ModalityTokenCount":{"properties":{"modality":{"$ref":"#/components/schemas/Modality"},"tokenCount":{"description":"Number of tokens for the given modality.","type":"integer"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"vertexai/gemini-nano-banana-2.1","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image","video"],"x-comfy-router-output-modalities":["image","text"]}
+{"openapi":"3.0.2","info":{"title":"vertexai/gemini-nano-banana-2.1","description":"The request body Comfy Router accepts for the model \"vertexai/gemini-nano-banana-2.1\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"a86e35b5a353"},"paths":{"/v2/models/vertexai/gemini-nano-banana-2.1":{"post":{"operationId":"runRouterModel","summary":"Run vertexai/gemini-nano-banana-2.1 synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"example":{"contents":[{"parts":[{"text":"Describe a robot learning to paint, in two sentences."}],"role":"user"}]},"properties":{"contents":{"items":{"$ref":"#/components/schemas/GeminiContent"},"type":"array"},"generationConfig":{"$ref":"#/components/schemas/GeminiGenerationConfig"},"safetySettings":{"items":{"$ref":"#/components/schemas/GeminiSafetySetting"},"type":"array"},"systemInstruction":{"$ref":"#/components/schemas/GeminiSystemInstructionContent"},"tools":{"items":{"$ref":"#/components/schemas/GeminiTool"},"type":"array"},"uploadImagesToStorage":{"description":"If true, generated images will be uploaded to cloud storage and returned as signed URLs instead of inline base64 data. The URLs expire after 24 hours.","type":"boolean"},"videoMetadata":{"$ref":"#/components/schemas/GeminiVideoMetadata"}},"required":["contents"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/GeminiGenerateContentResponse"}],"description":"Comfy Router output schema for the image-returning Gemini models: the same Vertex AI `generateContent` response the text models return, forwarded unchanged, but the generated image arrives as base64 bytes at `candidates[0].content.parts[0].inlineData.data`, with its media type at `inlineData.mimeType`. A response may carry a text part alongside the image part, so select the part by the field you need rather than by index.\n`uploadImagesToStorage` CHANGES THIS SHAPE. When the request sets it, the proxy uploads each generated image to Comfy storage and replaces the part's `inlineData` with `fileData`, carrying a Comfy-signed URL at `fileData.fileUri` and the media type at `fileData.mimeType`; `inlineData` is then absent for that part. The URL expires 24 hours after it is minted, so download the image rather than storing the link. An image whose upload fails is left as `inlineData`, so ONE response can mix both shapes - branch on which key is present rather than assuming either.\nRouter does not re-host a partner link for this family: Gemini returns the image as bytes and no URL of its own, so `uploadImagesToStorage` is an upload of those bytes that the caller asks for, not a rewrite of somebody else's link. A request that leaves the field unset is answered as base64 from end to end WHENEVER VERTEX AI ITSELF SERVES IT - which is always the case for `vertexai/gemini-2.5-flash-image` and `vertexai/gemini-3.1-flash-lite-image`, the two ids on this component that have no other producer, and is the usual but not the only case for the two below.\n`vertexai/gemini-3-pro-image` (`fal`, `pika`, `runware`, `wavespeed`) and `vertexai/gemini-3.1-flash-image` (`fal`, `pika`, `runware`, `wavespeed`) have ALTERNATE PRODUCERS: an eligible request may be served by a partner instead of by Vertex AI. Two PUBLISHED routes reach one, both the alt-provider dispatch in `server/implementation/router_run.go`: `model_provider` names a partner explicitly, and `fallback_provider` (default ON) retries once against a registered alternate when the Vertex AI attempt fails for a reason attributable to Router or to Vertex AI rather than to the request. That retry is suppressed - whatever `fallback_provider` says - on a BYOK call, on a same-key idempotent replay, on a multipart edit operation, and when the model has no registered alternate. SEPARATELY, the `vertexai` partner proxy runs a fallback cascade of its own on these two ids when the platform-credential attempt fails: it is NOT governed by `fallback_provider`, it is disabled only for BYOK requests, and it can therefore fire on a failure the request itself caused and on a call that asked for `fallback_provider: false`. Treat a non-Vertex producer as possible on these two ids whatever the request asked for.\nA response the `fal`, `pika`, `runware` or `wavespeed` leg served, translated back into this contract, carries every generated image as `fileData.fileUri` holding THE PARTNER'S OWN hosted URL, and never `inlineData.data` - all four partners return a hosted URL and never base64 bytes. The URL's lifetime and access policy are the partner's own rather than the 24-hour Comfy-signed URL described above, and Comfy publishes neither, so download the image promptly rather than storing the link. The proxy-level cascade above is NOT bound to that shape: it substitutes another provider's Gemini-compatible body, which may carry inline bytes instead. So the rule is the one the `uploadImagesToStorage` paragraph already states - branch on which key a part actually carries, never on a producer you inferred.\n`uploadImagesToStorage` does not change any of this. The flag is an upload of Vertex AI's own bytes, and off Vertex AI there are no such bytes to upload, so sending it neither produces `inlineData` nor rewrites the partner's link: it is simply inert on an alt-provider leg. A request that carries the key - whatever its value, `false` included - is told so: the translated alt-provider response lists `uploadImagesToStorage` in `X-Comfy-Router-Dropped-Params`, among the request fields the partner's schema has no place for. Only a translated response carries that disclosure: the proxy-level cascade and a `strict_mode: true` call run no translation and send no such header.\nTHREE RESPONSES SKIP THAT TRANSLATION and return the PARTNER'S OWN envelope rather than this component's: `strict_mode: true` asks for the alternate provider's raw schema by definition, a partner response that arrives with a non-empty `Content-Encoding` is forwarded unchanged because Router does not decompress a body to translate it, and a translation that fails or yields nothing forwards the partner's body unchanged - that body may carry no `candidates` key at all. An alt-provider response that IS translated back carries `candidates` and nothing else: `usageMetadata`, `promptFeedback`, `modelVersion`, `responseId` and `finishReason` are Vertex AI's own fields, and the translation has nothing to map them from.\nWHICH PRODUCER ANSWERED is disclosed more weakly than the shape itself. `X-Comfy-Router-Fallback-Provider` names the provider only on a response Router's OWN fallback retry served: the proxy-level cascade sets no header. A same-key idempotent replay repeats both that header and `X-Comfy-Router-Dropped-Params` exactly as the original call sent them, so a replay discloses what the original call did and no more. The request's own `model_provider` is reliable only in the direction it states - it says a partner was ASKED for, not that none answered when it was absent. The published per-model document of a model with a PUBLISHED leg carries an `x-comfy-router-alt-providers` marker naming it; absence of that marker is NOT a guarantee that the model has no alternate producer, since a registered leg that is not cleared for public disclosure still dispatches without appearing there. Which partner served a given request is a Comfy routing decision that can change without notice, and is not part of this contract.\nAs for the text models, `promptFeedback` and `usageMetadata` are present on a safety-blocked response that generated nothing; `candidates` is the field to branch on. Both are Vertex AI's own fields: on a response an alternate producer served, see the paragraph above for what is absent.\nThe example's `inlineData.data` is the PLACEHOLDER `PGJhc2U2ND4=`, which decodes to the literal text `\u003cbase64\u003e` - a real image would be megabytes, and a real provider response does not belong in a published spec. It is spelled as valid base64 rather than as a bare `\u003cbase64\u003e` marker because `GeminiInlineData.data` declares `format: byte`, which this document's own example validation enforces.\nThe example is authored ONCE for all four ids above and rendered into each per-model document unchanged, so its `modelVersion` is ILLUSTRATIVE RATHER THAN ENDPOINT-SPECIFIC. Vertex AI echoes the id that actually served the call, so a `vertexai/gemini-3-pro-image` response reads `gemini-3-pro-image` there - not the `gemini-2.5-flash-image` the example happens to show - and on an alt-provider-served response the field is absent entirely, per the paragraph above. Where `modelVersion` is present, read it as echoing the served id, never as the literal value printed in the document you are reading.","example":{"candidates":[{"content":{"parts":[{"inlineData":{"data":"PGJhc2U2ND4=","mimeType":"image/png"}}],"role":"model"},"finishReason":"STOP"}],"modelVersion":"gemini-2.5-flash-image","responseId":"7c6b5a49-3827-1605-f4e3-d2c1b0a99887","usageMetadata":{"candidatesTokenCount":1290,"promptTokenCount":11,"totalTokenCount":1301}}}}}}}}}},"components":{"schemas":{"GeminiCandidate":{"properties":{"citationMetadata":{"$ref":"#/components/schemas/GeminiCitationMetadata"},"content":{"$ref":"#/components/schemas/GeminiContent"},"finishReason":{"type":"string"},"safetyRatings":{"items":{"$ref":"#/components/schemas/GeminiSafetyRating"},"type":"array"}},"type":"object"},"GeminiCitation":{"properties":{"authors":{"items":{"type":"string"},"type":"array"},"endIndex":{"type":"integer"},"license":{"type":"string"},"publicationDate":{"format":"date","type":"string"},"startIndex":{"type":"integer"},"title":{"type":"string"},"uri":{"type":"string"}},"type":"object"},"GeminiCitationMetadata":{"properties":{"citations":{"items":{"$ref":"#/components/schemas/GeminiCitation"},"type":"array"}},"type":"object"},"GeminiContent":{"description":"The content of the current conversation with the model. For single-turn queries, this is a single instance. For multi-turn queries, this is a repeated field that contains conversation history and the latest request.\n","properties":{"parts":{"items":{"$ref":"#/components/schemas/GeminiPart"},"type":"array"},"role":{"enum":["user","model"],"example":"user","type":"string"}},"required":["parts"],"type":"object"},"GeminiFileData":{"description":"URI based data.","properties":{"fileUri":{"description":"URI","type":"string"},"mimeType":{"$ref":"#/components/schemas/GeminiMimeType"}},"type":"object"},"GeminiFunctionDeclaration":{"properties":{"description":{"type":"string"},"name":{"type":"string"},"parameters":{"description":"JSON schema for the function parameters","type":"object"}},"required":["name"],"type":"object"},"GeminiGenerateContentResponse":{"properties":{"candidates":{"items":{"$ref":"#/components/schemas/GeminiCandidate"},"type":"array"},"createTime":{"description":"Timestamp when the response was created.","type":"string"},"modelVersion":{"description":"The model version used to generate the response.","type":"string"},"promptFeedback":{"$ref":"#/components/schemas/GeminiPromptFeedback"},"responseId":{"description":"Unique identifier for the response.","type":"string"},"usageMetadata":{"$ref":"#/components/schemas/GeminiUsageMetadata"}},"type":"object"},"GeminiGenerationConfig":{"description":"Sampling, length and output settings for the generation. Every field is optional: the fields below that declare a `default` apply it when omitted, and the rest fall back to the model's own behaviour.\n","properties":{"imageConfig":{"description":"Configuration for image generation","properties":{"aspectRatio":{"description":"Aspect ratio for generated images","type":"string"},"imageOutputOptions":{"description":"Optional. The image output format for generated images.","properties":{"compressionQuality":{"description":"Optional. The compression quality of the output image.","type":"integer"},"mimeType":{"description":"Optional. The image format that the output should be saved as, on the Vertex AI paths: requests served by Comfy's own credentials, and BYOK requests authenticated with a GCP service account. Accepted values there are `image/png` and `image/jpeg`, matched case-insensitively and normalised to lower case before the request is forwarded; any other value is rejected with a 400 naming this field. Defaults to `image/png` when omitted. A BYOK request authenticated with a Google AI Studio API key is the exception: that upstream has no such property and rejects the whole call if it is present, so the field is dropped from the request rather than honoured or rejected, and the output format is whatever AI Studio chooses. On every path, read the media type back from the response part you are given (`inlineData.mimeType`, or `fileData.mimeType` when `uploadImagesToStorage` is set) rather than assuming the value you sent.","type":"string"}},"type":"object"},"imageSize":{"description":"Optional. Specifies the size of generated images. Supported values are 1K, 2K, 4K. If not specified, the model will use default value 1K.","type":"string"}},"type":"object"},"maxOutputTokens":{"description":"Maximum number of tokens that can be generated in the response. A token is approximately 4 characters. 100 tokens correspond to roughly 60-80 words.\n","example":2048,"maximum":65536,"minimum":16,"type":"integer"},"responseModalities":{"items":{"enum":["TEXT","IMAGE"],"type":"string"},"type":"array"},"seed":{"description":"When seed is fixed to a specific value, the model makes a best effort to provide the same response for repeated requests. Deterministic output isn't guaranteed. Also, changing the model or parameter settings, such as the temperature, can cause variations in the response even when you use the same seed value. By default, a random seed value is used. Available for the following models:, gemini-2.5-flash, gemini-2.5-pro, gemini-2.5-flash-preview-04-1, gemini-2.5-pro-preview-05-0, gemini-2.0-flash-lite-00, gemini-2.0-flash-001\n","example":343940597,"type":"integer"},"stopSequences":{"items":{"type":"string"},"type":"array"},"temperature":{"default":1,"description":"The temperature is used for sampling during response generation, which occurs when topP and topK are applied. Temperature controls the degree of randomness in token selection. Lower temperatures are good for prompts that require a less open-ended or creative response, while higher temperatures can lead to more diverse or creative results. A temperature of 0 means that the highest probability tokens are always selected. In this case, responses for a given prompt are mostly deterministic, but a small amount of variation is still possible. If the model returns a response that's too generic, too short, or the model gives a fallback response, try increasing the temperature\n","format":"float","maximum":2,"minimum":0,"type":"number"},"thinkingConfig":{"description":"Optional. Configuration for thinking features. Thinking is a process where the model breaks down a complex task into smaller steps to generate a higher-quality response.","properties":{"includeThoughts":{"description":"Optional. If true, the model will include its thoughts in the response.","type":"boolean"},"thinkingBudget":{"description":"Optional. The token budget for the model's thinking process. The model will make a best effort to stay within this budget.","type":"integer"},"thinkingLevel":{"description":"Optional. The thinking level for the model.","enum":["THINKING_LEVEL_UNSPECIFIED","LOW","MEDIUM","HIGH","MINIMAL"],"type":"string"}},"type":"object"},"topK":{"default":40,"description":"Top-K changes how the model selects tokens for output. A top-K of 1 means the next selected token is the most probable among all tokens in the model's vocabulary. A top-K of 3 means that the next token is selected from among the 3 most probable tokens by using temperature.\n","example":40,"minimum":1,"type":"integer"},"topP":{"default":0.95,"description":"If specified, nucleus sampling is used.\nTop-P changes how the model selects tokens for output. Tokens are selected from the most (see top-K) to least probable until the sum of their probabilities equals the top-P value. For example, if tokens A, B, and C have a probability of 0.3, 0.2, and 0.1 and the top-P value is 0.5, then the model will select either A or B as the next token by using temperature and excludes C as a candidate.\nSpecify a lower value for less random responses and a higher value for more random responses.\n","format":"float","maximum":1,"minimum":0,"type":"number"}},"type":"object"},"GeminiInlineData":{"description":"Inline data in raw bytes. For gemini-2.0-flash-lite and gemini-2.0-flash, you can specify up to 3000 images by using inlineData.\n","properties":{"data":{"description":"The base64 encoding of the image, PDF, or video to include inline in the prompt. When including media inline, you must also specify the media type (mimeType) of the data. Size limit: 20MB\n","format":"byte","type":"string"},"mimeType":{"$ref":"#/components/schemas/GeminiMimeType"}},"type":"object"},"GeminiMimeType":{"description":"The media type of the file specified in the data or fileUri fields. Acceptable values include the following. For gemini-2.0-flash-lite and gemini-2.0-flash, the maximum length of an audio file is 8.4 hours and the maximum length of a video file (without audio) is one hour. For more information, see Gemini audio and video requirements. Text files must be UTF-8 encoded. The contents of the text file count toward the token limit. There is no limit on image resolution.","enum":["application/pdf","audio/mpeg","audio/mp3","audio/wav","image/png","image/jpeg","image/webp","text/plain","video/mov","video/mpeg","video/mp4","video/mpg","video/avi","video/wmv","video/mpegps","video/flv","image/heic","image/heif","audio/flac","video/webm"],"type":"string"},"GeminiOffset":{"description":"Represents a duration offset for video timeline positions.\n","properties":{"nanos":{"description":"Signed fractions of a second at nanosecond resolution. Negative second values with fractions must still have non-negative nanos values.\n","example":0,"maximum":999999999,"minimum":0,"type":"integer"},"seconds":{"description":"Signed seconds of the span of time. Must be from -315,576,000,000 to +315,576,000,000 inclusive.\n","example":60,"maximum":315576000000,"minimum":-315576000000,"type":"integer"}},"type":"object"},"GeminiPart":{"properties":{"fileData":{"$ref":"#/components/schemas/GeminiFileData"},"inlineData":{"$ref":"#/components/schemas/GeminiInlineData"},"mediaProcessing":{"description":"How the model reads this part's video. Set \"AGENTIC\" to let the model decide which segments to inspect, instead of fixed-rate frame sampling. Omit for the default fixed-rate sampling. Supported on gemini-3.7-flash and newer Flash models.\n","type":"string"},"text":{"description":"A text prompt or code snippet.","example":"Write a story about a robot learning to paint","type":"string"},"thought":{"description":"Indicates this part is a thinking/reasoning step from the model.","type":"boolean"}},"type":"object"},"GeminiPromptFeedback":{"properties":{"blockReason":{"type":"string"},"blockReasonMessage":{"type":"string"},"safetyRatings":{"items":{"$ref":"#/components/schemas/GeminiSafetyRating"},"type":"array"}},"type":"object"},"GeminiSafetyCategory":{"enum":["HARM_CATEGORY_SEXUALLY_EXPLICIT","HARM_CATEGORY_HATE_SPEECH","HARM_CATEGORY_HARASSMENT","HARM_CATEGORY_DANGEROUS_CONTENT"],"type":"string"},"GeminiSafetyRating":{"properties":{"category":{"$ref":"#/components/schemas/GeminiSafetyCategory"},"probability":{"description":"The probability that the content violates the specified safety category","enum":["NEGLIGIBLE","LOW","MEDIUM","HIGH","UNKNOWN"],"type":"string"}},"type":"object"},"GeminiSafetySetting":{"description":"Per request settings for blocking unsafe content. Enforced on GenerateContentResponse.candidates.\n","properties":{"category":{"$ref":"#/components/schemas/GeminiSafetyCategory"},"threshold":{"$ref":"#/components/schemas/GeminiSafetyThreshold"}},"required":["category","threshold"],"type":"object"},"GeminiSafetyThreshold":{"enum":["OFF","BLOCK_NONE","BLOCK_LOW_AND_ABOVE","BLOCK_MEDIUM_AND_ABOVE","BLOCK_ONLY_HIGH"],"type":"string"},"GeminiSystemInstructionContent":{"description":"Instructions for the model to steer it toward better performance. For example, \"Answer as concisely as possible\" or \"Don't use technical terms in your response\". The text strings count toward the token limit. The role field of systemInstruction is ignored and doesn't affect the performance of the model. Note: Only text should be used in parts and content in each part should be in a separate paragraph.\n","properties":{"parts":{"description":"A list of ordered parts that make up a single message. Different parts may have different IANA MIME types. For limits on the inputs, such as the maximum number of tokens or the number of images, see the model specifications on the Google models page.\n","items":{"$ref":"#/components/schemas/GeminiTextPart"},"type":"array"},"role":{"description":"The identity of the entity that creates the message. The following values are supported: user: This indicates that the message is sent by a real person, typically a user-generated message. model: This indicates that the message is generated by the model. The model value is used to insert messages from the model into the conversation during multi-turn conversations. For non-multi-turn conversations, this field can be left blank or unset.\n","enum":["user","model"],"example":"user","type":"string"}},"required":["parts"],"type":"object"},"GeminiTextPart":{"properties":{"text":{"description":"A text prompt or code snippet.","example":"Answer as concisely as possible","type":"string"}},"type":"object"},"GeminiTool":{"description":"A piece of code that enables the system to interact with external systems to perform an action, or set of actions, outside of knowledge and scope of the model. See Function calling.\n","properties":{"functionDeclarations":{"items":{"$ref":"#/components/schemas/GeminiFunctionDeclaration"},"type":"array"}},"type":"object"},"GeminiUsageMetadata":{"properties":{"cachedContentTokenCount":{"description":"Output only. Number of tokens in the cached part in the input (the cached content).","type":"integer"},"candidatesTokenCount":{"description":"Number of tokens in the response(s).","type":"integer"},"candidatesTokensDetails":{"description":"Breakdown of candidate tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"promptTokenCount":{"description":"Number of tokens in the request. When cachedContent is set, this is still the total effective prompt size meaning this includes the number of tokens in the cached content.","type":"integer"},"promptTokensDetails":{"description":"Breakdown of prompt tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"thoughtsTokenCount":{"description":"Number of tokens present in thoughts output.","type":"integer"},"toolUsePromptTokenCount":{"description":"Number of tokens present in tool-use prompt(s).","type":"integer"},"toolUsePromptTokensDetails":{"description":"Breakdown of tool-use prompt tokens by modality.","items":{"$ref":"#/components/schemas/ModalityTokenCount"},"type":"array"},"totalTokenCount":{"description":"Total number of tokens (prompt + candidates).","type":"integer"},"trafficType":{"description":"Traffic type used for the request (e.g., PROVISIONED_THROUGHPUT).","type":"string"}},"type":"object"},"GeminiVideoMetadata":{"description":"For video input, the start and end offset of the video in Duration format. For example, to specify a 10 second clip starting at 1:00, set \"startOffset\": { \"seconds\": 60 } and \"endOffset\": { \"seconds\": 70 }. The metadata should only be specified while the video data is presented in inlineData or fileData.\n","properties":{"endOffset":{"$ref":"#/components/schemas/GeminiOffset"},"startOffset":{"$ref":"#/components/schemas/GeminiOffset"}},"type":"object"},"Modality":{"description":"Type of input or output content modality.","enum":["MODALITY_UNSPECIFIED","TEXT","IMAGE","VIDEO","AUDIO","DOCUMENT"],"type":"string"},"ModalityTokenCount":{"properties":{"modality":{"$ref":"#/components/schemas/Modality"},"tokenCount":{"description":"Number of tokens for the given modality.","type":"integer"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"vertexai/gemini-nano-banana-2.1","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image","video"],"x-comfy-router-output-modalities":["image","text"]}
diff --git a/router-schemas/xai/grok-imagine-video-1.5-lite.json b/router-schemas/xai/grok-imagine-video-1.5-lite.json
index c13fcca64d..26299f7f8f 100644
--- a/router-schemas/xai/grok-imagine-video-1.5-lite.json
+++ b/router-schemas/xai/grok-imagine-video-1.5-lite.json
@@ -1 +1 @@
-{"openapi":"3.0.2","info":{"title":"xai/grok-imagine-video-1.5-lite","description":"The request body Comfy Router accepts for the model \"xai/grok-imagine-video-1.5-lite\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"4888f1b583c9"},"paths":{"/v2/models/xai/grok-imagine-video-1.5-lite":{"post":{"operationId":"runRouterModel","summary":"Run xai/grok-imagine-video-1.5-lite synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"description":"Request body for xAI Grok Imagine video generation.\nSupports three modes: text-to-video (prompt only), image-to-video (prompt + image),\nand reference-to-video (prompt + reference_images).\nThe fields image, reference_images, and video are mutually exclusive.\n","example":{"duration":4,"prompt":"a single red maple leaf falling onto still water, slow motion"},"not":{"required":["image","reference_images"]},"properties":{"aspect_ratio":{"default":"16:9","description":"Aspect ratio of the generated video","enum":["1:1","16:9","9:16","4:3","3:4","3:2","2:3"],"type":"string"},"duration":{"default":8,"description":"Video duration in seconds. Range [1, 15]. Default 8.","maximum":15,"minimum":1,"nullable":true,"type":"integer"},"image":{"$ref":"#/components/schemas/XAIImageObject"},"model":{"description":"Model to be used. Supported: grok-imagine-video (default), grok-imagine-video-1.5-preview, grok-imagine-video-1.5, grok-imagine-video-1.5-lite. The deprecated grok-imagine-video-beta id is aliased to grok-imagine-video.","nullable":true,"type":"string"},"output":{"description":"Optional output destination for generated video","nullable":true,"type":"object"},"prompt":{"description":"Prompt for video generation. Maximum 4,096 characters.","maxLength":4096,"type":"string"},"reference_images":{"description":"One or more reference images to guide the video generation (reference-to-video mode). Mutually exclusive with image. Router forwards application/json only and neither this object nor XAIImageObject accepts a file_id, so file-backed image inputs are not supported on this route.","items":{"$ref":"#/components/schemas/XAIReferenceImageObject"},"minItems":1,"type":"array"},"resolution":{"description":"Resolution of the output video","nullable":true,"type":"string"},"size":{"description":"Size of the output video","nullable":true,"type":"string"},"user":{"description":"A unique identifier representing your end-user","nullable":true,"type":"string"}},"required":["prompt"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/XAIVideoResultResponse"}],"description":"Comfy Router output schema for the xAI Grok Imagine video models: the terminal `GET /v1/videos/{request_id}` document, forwarded unchanged EXCEPT for the re-hosted `video.url` described below - every other field is xAI's own. This family is SUBMIT-AND-POLL — the submit answers with an `XAIVideoAsyncResponse` handle carrying `request_id`, and Router polls `GET /v1/videos/{request_id}` on the caller's behalf, so the body a caller receives is the finished result rather than that handle.\nThe generated video is in `video`. Its `url` is NULLABLE, and Router treats a success whose `video.url` is empty as `success_without_output` rather than as a completed generation.\nThat `url` is RE-HOSTED: Router copies the finished video onto Comfy storage and answers a Comfy-signed URL valid for up to 24 hours in place of xAI's own, so it is a Comfy link and not the partner's. Durability is per entry, not per response, exactly as it is on the image family: a video whose re-host could not be performed keeps xAI's own short-lived URL rather than a Comfy one, so a caller that stores or replays this document should not assume the URL outlives the call. 24 hours is the CEILING, not a guarantee: the Comfy link is signed for 24 hours from the moment it is minted, and Router memoises it for 23 hours, so a later poll or an `Idempotency-Key` replay can hand back a link with as little as an hour left. Either way the link expires, so download the video rather than storing it.\n`status` is xAI's own two-value vocabulary — `pending` or `done`, NOT `completed` — and `status`, `model` and `usage` are all populated on a PENDING poll and on an input-moderation refusal alike. A caller must therefore key completion off `video.url`, NOT off `video`: the `video` object can be present with a null `url`, which is the `success_without_output` case above, so the container test reports a generation that produced nothing as finished. It is the same rule Router applies when it decides a poll SUCCEEDED: `video.url` must be non-empty.","example":{"model":"grok-imagine-video","status":"done","usage":{"cost_in_usd_ticks":3500000000},"video":{"duration":4,"respect_moderation":true,"url":"https://example.invalid/xai/grok-imagine-video/generated.mp4"}}}}}}}}}},"components":{"schemas":{"XAIGeneratedVideo":{"description":"A generated video from xAI","properties":{"duration":{"description":"Duration of the generated video in seconds","type":"integer"},"respect_moderation":{"description":"Whether the video generated by the model respects moderation rules","type":"boolean"},"url":{"description":"Download URL for the generated video. Router re-hosts the video onto Comfy storage and rewrites this field, so it is normally a Comfy-signed URL valid for up to 24 hours - signed for 24 hours when minted and replayed from a 23-hour memo, so a later poll can hand back one with as little as an hour left. When the re-host could not be performed the field keeps xAI's own short-lived URL instead. NULLABLE: a success whose `url` is empty is not a completed generation. Either way the link expires, so download the video rather than storing the URL.","nullable":true,"type":"string"}},"type":"object"},"XAIImageObject":{"description":"Input image object for xAI endpoints","properties":{"type":{"description":"Type of the image input","enum":["image_url"],"type":"string"},"url":{"description":"URL of the input image (public URL or base64-encoded data URI)","type":"string"}},"required":["url"],"type":"object"},"XAIReferenceImageObject":{"description":"A reference image used to guide video generation in R2V mode","properties":{"url":{"description":"URL of the reference image. Supports HTTPS URLs (public) or base64-encoded data URLs (e.g., data:image/jpeg;base64,...).","type":"string"}},"required":["url"],"type":"object"},"XAIVideoResultResponse":{"description":"Response from getting video generation result","properties":{"block_reason":{"description":"If the request was blocked by input moderation, contains the block reason","nullable":true,"type":"string"},"model":{"description":"The model used to generate the video","type":"string"},"status":{"description":"Status of the deferred request: \"pending\" or \"done\"","enum":["pending","done"],"type":"string"},"usage":{"$ref":"#/components/schemas/XAIVideoUsage"},"video":{"$ref":"#/components/schemas/XAIGeneratedVideo"}},"type":"object"},"XAIVideoUsage":{"description":"Usage information for the video generation request","properties":{"cost_in_usd_ticks":{"description":"The cost of this request expressed in USD ticks. One USD cent equals 100,000,000 ticks, so one US dollar equals 10,000,000,000 ticks.\n","type":"integer"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"xai/grok-imagine-video-1.5-lite","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image"],"x-comfy-router-output-modalities":["video"]}
+{"openapi":"3.0.2","info":{"title":"xai/grok-imagine-video-1.5-lite","description":"The request body Comfy Router accepts for the model \"xai/grok-imagine-video-1.5-lite\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"dc9596926447"},"paths":{"/v2/models/xai/grok-imagine-video-1.5-lite":{"post":{"operationId":"runRouterModel","summary":"Run xai/grok-imagine-video-1.5-lite synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"description":"Request body for xAI Grok Imagine video generation.\nSupports three modes: text-to-video (prompt only), image-to-video (prompt + image),\nand reference-to-video (prompt + reference_images).\nThe fields image, reference_images, and video are mutually exclusive.\n","example":{"duration":4,"prompt":"a single red maple leaf falling onto still water, slow motion"},"not":{"required":["image","reference_images"]},"properties":{"aspect_ratio":{"default":"16:9","description":"Aspect ratio of the generated video","enum":["1:1","16:9","9:16","4:3","3:4","3:2","2:3"],"type":"string"},"duration":{"default":8,"description":"Video duration in seconds. Range [1, 15]. Default 8.","maximum":15,"minimum":1,"nullable":true,"type":"integer"},"image":{"$ref":"#/components/schemas/XAIImageObject"},"model":{"description":"Model to be used. Supported: grok-imagine-video (default), grok-imagine-video-1.5-preview, grok-imagine-video-1.5, grok-imagine-video-1.5-lite. The deprecated grok-imagine-video-beta id is aliased to grok-imagine-video.","nullable":true,"type":"string"},"output":{"description":"Optional output destination for generated video","nullable":true,"type":"object"},"prompt":{"description":"Prompt for video generation. Maximum 4,096 characters.","maxLength":4096,"type":"string"},"reference_images":{"description":"One or more reference images to guide the video generation (reference-to-video mode). Mutually exclusive with image. Router forwards application/json only and neither this object nor XAIImageObject accepts a file_id, so file-backed image inputs are not supported on this route.","items":{"$ref":"#/components/schemas/XAIReferenceImageObject"},"minItems":1,"type":"array"},"resolution":{"description":"Resolution of the output video","nullable":true,"type":"string"},"size":{"description":"Size of the output video","nullable":true,"type":"string"},"user":{"description":"A unique identifier representing your end-user","nullable":true,"type":"string"}},"required":["prompt"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/XAIVideoResultResponse"},{"properties":{"block_reason":{"description":"Never carries a reason on this document: a non-empty block reason is an input-moderation refusal, which Router answers as a Comfy Router error rather than with this body. Absent, null or empty here.","maxLength":0,"nullable":true,"type":"string"},"status":{"description":"Absent or `done` on this document. `pending` is the body of a poll Router has not finished and is never returned as a `200`; Router accepts a missing status when `video.url` is populated, so a caller must not require it.","enum":["done"],"type":"string"},"video":{"description":"The finished generation. Required here, unlike on the poll document: a terminal result with no `video.url` is answered as a Comfy Router error, so a `200` always carries it.","properties":{"duration":{"description":"Duration of the generated video in seconds","type":"integer"},"respect_moderation":{"description":"Whether the video generated by the model respects moderation rules","type":"boolean"},"url":{"description":"Non-empty on this document. Router reports success only when this leaf is populated; it is the re-hosted Comfy-signed URL described above, or xAI's own short-lived URL when the re-host could not be performed.","minLength":1,"type":"string"}},"required":["url"],"type":"object"}},"required":["video"],"type":"object"}],"description":"Comfy Router output schema for the xAI Grok Imagine video models: the terminal `GET /v1/videos/{request_id}` document, forwarded unchanged EXCEPT for the re-hosted `video.url` described below - every other field is xAI's own. This family is SUBMIT-AND-POLL — the submit answers with an `XAIVideoAsyncResponse` handle carrying `request_id`, and Router polls `GET /v1/videos/{request_id}` on the caller's behalf, so the body a caller receives is the finished result rather than that handle.\nThe generated video is in `video`. On the poll document its `url` is NULLABLE, and Router treats a success whose `video.url` is empty as `success_without_output` rather than as a completed generation.\nThat `url` is RE-HOSTED: Router copies the finished video onto Comfy storage and answers a Comfy-signed URL valid for up to 24 hours in place of xAI's own, so it is a Comfy link and not the partner's. Durability is per entry, not per response, exactly as it is on the image family: a video whose re-host could not be performed keeps xAI's own short-lived URL rather than a Comfy one, so a caller that stores or replays this document should not assume the URL outlives the call. 24 hours is the CEILING, not a guarantee: the Comfy link is signed for 24 hours from the moment it is minted, and Router memoises it for 23 hours, so a later poll or an `Idempotency-Key` replay can hand back a link with as little as an hour left. Either way the link expires, so download the video rather than storing it.\n`status` is xAI's own two-value vocabulary — `pending` or `done`, NOT `completed` — and `status`, `model` and `usage` are all populated on a PENDING poll and on an input-moderation refusal alike. A caller must therefore key completion off `video.url`, NOT off `video`: on the poll document the `video` object can be present with a null `url`, which is the `success_without_output` case above, so the container test reports a generation that produced nothing as finished. It is the same rule Router applies when it decides a poll SUCCEEDED: `video.url` must be non-empty.\nThe composition NARROWS `XAIVideoResultResponse` rather than republishing it, because `XAIVideoResultResponse` describes every poll of the request — including the pending and refused ones a caller never sees — while this document describes only the body a `200` carries. Router reports success on one shape: `video.url` non-empty, no non-empty `block_reason`, and `status` absent or `done`. A `pending` poll is polled again rather than returned, a non-empty `block_reason` is an input-moderation refusal answered as a Comfy Router error, and a terminal result with no `video.url` is the `success_without_output` error above, so `video` and its `url` are required here and `block_reason` is at most empty. `status` is narrowed to the single terminal spelling `done` because Router compares it WITHOUT a case fold, so no other spelling can reach a `200`; it stays optional, because Router also reports success when `status` is missing and `video.url` is populated.","example":{"model":"grok-imagine-video","status":"done","usage":{"cost_in_usd_ticks":3500000000},"video":{"duration":4,"respect_moderation":true,"url":"https://example.invalid/xai/grok-imagine-video/generated.mp4"}}}}}}}}}},"components":{"schemas":{"XAIGeneratedVideo":{"description":"A generated video from xAI","properties":{"duration":{"description":"Duration of the generated video in seconds","type":"integer"},"respect_moderation":{"description":"Whether the video generated by the model respects moderation rules","type":"boolean"},"url":{"description":"Download URL for the generated video. Router re-hosts the video onto Comfy storage and rewrites this field, so it is normally a Comfy-signed URL valid for up to 24 hours - signed for 24 hours when minted and replayed from a 23-hour memo, so a later poll can hand back one with as little as an hour left. When the re-host could not be performed the field keeps xAI's own short-lived URL instead. NULLABLE: a success whose `url` is empty is not a completed generation. Either way the link expires, so download the video rather than storing the URL.","nullable":true,"type":"string"}},"type":"object"},"XAIImageObject":{"description":"Input image object for xAI endpoints","properties":{"type":{"description":"Type of the image input","enum":["image_url"],"type":"string"},"url":{"description":"URL of the input image (public URL or base64-encoded data URI)","type":"string"}},"required":["url"],"type":"object"},"XAIReferenceImageObject":{"description":"A reference image used to guide video generation in R2V mode","properties":{"url":{"description":"URL of the reference image. Supports HTTPS URLs (public) or base64-encoded data URLs (e.g., data:image/jpeg;base64,...).","type":"string"}},"required":["url"],"type":"object"},"XAIVideoResultResponse":{"description":"Response from getting video generation result","properties":{"block_reason":{"description":"If the request was blocked by input moderation, contains the block reason","nullable":true,"type":"string"},"model":{"description":"The model used to generate the video","type":"string"},"status":{"description":"Status of the deferred request: \"pending\" or \"done\"","enum":["pending","done"],"type":"string"},"usage":{"$ref":"#/components/schemas/XAIVideoUsage"},"video":{"$ref":"#/components/schemas/XAIGeneratedVideo"}},"type":"object"},"XAIVideoUsage":{"description":"Usage information for the video generation request","properties":{"cost_in_usd_ticks":{"description":"The cost of this request expressed in USD ticks. One USD cent equals 100,000,000 ticks, so one US dollar equals 10,000,000,000 ticks.\n","type":"integer"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"xai/grok-imagine-video-1.5-lite","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image"],"x-comfy-router-output-modalities":["video"]}
diff --git a/router-schemas/xai/grok-imagine-video-1.5-preview.json b/router-schemas/xai/grok-imagine-video-1.5-preview.json
index 60cbbcc3f1..6ab9ff3c14 100644
--- a/router-schemas/xai/grok-imagine-video-1.5-preview.json
+++ b/router-schemas/xai/grok-imagine-video-1.5-preview.json
@@ -1 +1 @@
-{"openapi":"3.0.2","info":{"title":"xai/grok-imagine-video-1.5-preview","description":"The request body Comfy Router accepts for the model \"xai/grok-imagine-video-1.5-preview\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"4888f1b583c9"},"paths":{"/v2/models/xai/grok-imagine-video-1.5-preview":{"post":{"operationId":"runRouterModel","summary":"Run xai/grok-imagine-video-1.5-preview synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"description":"Request body for xAI Grok Imagine video generation.\nSupports three modes: text-to-video (prompt only), image-to-video (prompt + image),\nand reference-to-video (prompt + reference_images).\nThe fields image, reference_images, and video are mutually exclusive.\n","example":{"duration":4,"prompt":"a single red maple leaf falling onto still water, slow motion"},"not":{"required":["image","reference_images"]},"properties":{"aspect_ratio":{"default":"16:9","description":"Aspect ratio of the generated video","enum":["1:1","16:9","9:16","4:3","3:4","3:2","2:3"],"type":"string"},"duration":{"default":8,"description":"Video duration in seconds. Range [1, 15]. Default 8.","maximum":15,"minimum":1,"nullable":true,"type":"integer"},"image":{"$ref":"#/components/schemas/XAIImageObject"},"model":{"description":"Model to be used. Supported: grok-imagine-video (default), grok-imagine-video-1.5-preview, grok-imagine-video-1.5, grok-imagine-video-1.5-lite. The deprecated grok-imagine-video-beta id is aliased to grok-imagine-video.","nullable":true,"type":"string"},"output":{"description":"Optional output destination for generated video","nullable":true,"type":"object"},"prompt":{"description":"Prompt for video generation. Maximum 4,096 characters.","maxLength":4096,"type":"string"},"reference_images":{"description":"One or more reference images to guide the video generation (reference-to-video mode). Mutually exclusive with image. Router forwards application/json only and neither this object nor XAIImageObject accepts a file_id, so file-backed image inputs are not supported on this route.","items":{"$ref":"#/components/schemas/XAIReferenceImageObject"},"minItems":1,"type":"array"},"resolution":{"description":"Resolution of the output video","nullable":true,"type":"string"},"size":{"description":"Size of the output video","nullable":true,"type":"string"},"user":{"description":"A unique identifier representing your end-user","nullable":true,"type":"string"}},"required":["prompt"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/XAIVideoResultResponse"}],"description":"Comfy Router output schema for the xAI Grok Imagine video models: the terminal `GET /v1/videos/{request_id}` document, forwarded unchanged EXCEPT for the re-hosted `video.url` described below - every other field is xAI's own. This family is SUBMIT-AND-POLL — the submit answers with an `XAIVideoAsyncResponse` handle carrying `request_id`, and Router polls `GET /v1/videos/{request_id}` on the caller's behalf, so the body a caller receives is the finished result rather than that handle.\nThe generated video is in `video`. Its `url` is NULLABLE, and Router treats a success whose `video.url` is empty as `success_without_output` rather than as a completed generation.\nThat `url` is RE-HOSTED: Router copies the finished video onto Comfy storage and answers a Comfy-signed URL valid for up to 24 hours in place of xAI's own, so it is a Comfy link and not the partner's. Durability is per entry, not per response, exactly as it is on the image family: a video whose re-host could not be performed keeps xAI's own short-lived URL rather than a Comfy one, so a caller that stores or replays this document should not assume the URL outlives the call. 24 hours is the CEILING, not a guarantee: the Comfy link is signed for 24 hours from the moment it is minted, and Router memoises it for 23 hours, so a later poll or an `Idempotency-Key` replay can hand back a link with as little as an hour left. Either way the link expires, so download the video rather than storing it.\n`status` is xAI's own two-value vocabulary — `pending` or `done`, NOT `completed` — and `status`, `model` and `usage` are all populated on a PENDING poll and on an input-moderation refusal alike. A caller must therefore key completion off `video.url`, NOT off `video`: the `video` object can be present with a null `url`, which is the `success_without_output` case above, so the container test reports a generation that produced nothing as finished. It is the same rule Router applies when it decides a poll SUCCEEDED: `video.url` must be non-empty.","example":{"model":"grok-imagine-video","status":"done","usage":{"cost_in_usd_ticks":3500000000},"video":{"duration":4,"respect_moderation":true,"url":"https://example.invalid/xai/grok-imagine-video/generated.mp4"}}}}}}}}}},"components":{"schemas":{"XAIGeneratedVideo":{"description":"A generated video from xAI","properties":{"duration":{"description":"Duration of the generated video in seconds","type":"integer"},"respect_moderation":{"description":"Whether the video generated by the model respects moderation rules","type":"boolean"},"url":{"description":"Download URL for the generated video. Router re-hosts the video onto Comfy storage and rewrites this field, so it is normally a Comfy-signed URL valid for up to 24 hours - signed for 24 hours when minted and replayed from a 23-hour memo, so a later poll can hand back one with as little as an hour left. When the re-host could not be performed the field keeps xAI's own short-lived URL instead. NULLABLE: a success whose `url` is empty is not a completed generation. Either way the link expires, so download the video rather than storing the URL.","nullable":true,"type":"string"}},"type":"object"},"XAIImageObject":{"description":"Input image object for xAI endpoints","properties":{"type":{"description":"Type of the image input","enum":["image_url"],"type":"string"},"url":{"description":"URL of the input image (public URL or base64-encoded data URI)","type":"string"}},"required":["url"],"type":"object"},"XAIReferenceImageObject":{"description":"A reference image used to guide video generation in R2V mode","properties":{"url":{"description":"URL of the reference image. Supports HTTPS URLs (public) or base64-encoded data URLs (e.g., data:image/jpeg;base64,...).","type":"string"}},"required":["url"],"type":"object"},"XAIVideoResultResponse":{"description":"Response from getting video generation result","properties":{"block_reason":{"description":"If the request was blocked by input moderation, contains the block reason","nullable":true,"type":"string"},"model":{"description":"The model used to generate the video","type":"string"},"status":{"description":"Status of the deferred request: \"pending\" or \"done\"","enum":["pending","done"],"type":"string"},"usage":{"$ref":"#/components/schemas/XAIVideoUsage"},"video":{"$ref":"#/components/schemas/XAIGeneratedVideo"}},"type":"object"},"XAIVideoUsage":{"description":"Usage information for the video generation request","properties":{"cost_in_usd_ticks":{"description":"The cost of this request expressed in USD ticks. One USD cent equals 100,000,000 ticks, so one US dollar equals 10,000,000,000 ticks.\n","type":"integer"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"xai/grok-imagine-video-1.5-preview","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image"],"x-comfy-router-output-modalities":["video"]}
+{"openapi":"3.0.2","info":{"title":"xai/grok-imagine-video-1.5-preview","description":"The request body Comfy Router accepts for the model \"xai/grok-imagine-video-1.5-preview\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"dc9596926447"},"paths":{"/v2/models/xai/grok-imagine-video-1.5-preview":{"post":{"operationId":"runRouterModel","summary":"Run xai/grok-imagine-video-1.5-preview synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"description":"Request body for xAI Grok Imagine video generation.\nSupports three modes: text-to-video (prompt only), image-to-video (prompt + image),\nand reference-to-video (prompt + reference_images).\nThe fields image, reference_images, and video are mutually exclusive.\n","example":{"duration":4,"prompt":"a single red maple leaf falling onto still water, slow motion"},"not":{"required":["image","reference_images"]},"properties":{"aspect_ratio":{"default":"16:9","description":"Aspect ratio of the generated video","enum":["1:1","16:9","9:16","4:3","3:4","3:2","2:3"],"type":"string"},"duration":{"default":8,"description":"Video duration in seconds. Range [1, 15]. Default 8.","maximum":15,"minimum":1,"nullable":true,"type":"integer"},"image":{"$ref":"#/components/schemas/XAIImageObject"},"model":{"description":"Model to be used. Supported: grok-imagine-video (default), grok-imagine-video-1.5-preview, grok-imagine-video-1.5, grok-imagine-video-1.5-lite. The deprecated grok-imagine-video-beta id is aliased to grok-imagine-video.","nullable":true,"type":"string"},"output":{"description":"Optional output destination for generated video","nullable":true,"type":"object"},"prompt":{"description":"Prompt for video generation. Maximum 4,096 characters.","maxLength":4096,"type":"string"},"reference_images":{"description":"One or more reference images to guide the video generation (reference-to-video mode). Mutually exclusive with image. Router forwards application/json only and neither this object nor XAIImageObject accepts a file_id, so file-backed image inputs are not supported on this route.","items":{"$ref":"#/components/schemas/XAIReferenceImageObject"},"minItems":1,"type":"array"},"resolution":{"description":"Resolution of the output video","nullable":true,"type":"string"},"size":{"description":"Size of the output video","nullable":true,"type":"string"},"user":{"description":"A unique identifier representing your end-user","nullable":true,"type":"string"}},"required":["prompt"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/XAIVideoResultResponse"},{"properties":{"block_reason":{"description":"Never carries a reason on this document: a non-empty block reason is an input-moderation refusal, which Router answers as a Comfy Router error rather than with this body. Absent, null or empty here.","maxLength":0,"nullable":true,"type":"string"},"status":{"description":"Absent or `done` on this document. `pending` is the body of a poll Router has not finished and is never returned as a `200`; Router accepts a missing status when `video.url` is populated, so a caller must not require it.","enum":["done"],"type":"string"},"video":{"description":"The finished generation. Required here, unlike on the poll document: a terminal result with no `video.url` is answered as a Comfy Router error, so a `200` always carries it.","properties":{"duration":{"description":"Duration of the generated video in seconds","type":"integer"},"respect_moderation":{"description":"Whether the video generated by the model respects moderation rules","type":"boolean"},"url":{"description":"Non-empty on this document. Router reports success only when this leaf is populated; it is the re-hosted Comfy-signed URL described above, or xAI's own short-lived URL when the re-host could not be performed.","minLength":1,"type":"string"}},"required":["url"],"type":"object"}},"required":["video"],"type":"object"}],"description":"Comfy Router output schema for the xAI Grok Imagine video models: the terminal `GET /v1/videos/{request_id}` document, forwarded unchanged EXCEPT for the re-hosted `video.url` described below - every other field is xAI's own. This family is SUBMIT-AND-POLL — the submit answers with an `XAIVideoAsyncResponse` handle carrying `request_id`, and Router polls `GET /v1/videos/{request_id}` on the caller's behalf, so the body a caller receives is the finished result rather than that handle.\nThe generated video is in `video`. On the poll document its `url` is NULLABLE, and Router treats a success whose `video.url` is empty as `success_without_output` rather than as a completed generation.\nThat `url` is RE-HOSTED: Router copies the finished video onto Comfy storage and answers a Comfy-signed URL valid for up to 24 hours in place of xAI's own, so it is a Comfy link and not the partner's. Durability is per entry, not per response, exactly as it is on the image family: a video whose re-host could not be performed keeps xAI's own short-lived URL rather than a Comfy one, so a caller that stores or replays this document should not assume the URL outlives the call. 24 hours is the CEILING, not a guarantee: the Comfy link is signed for 24 hours from the moment it is minted, and Router memoises it for 23 hours, so a later poll or an `Idempotency-Key` replay can hand back a link with as little as an hour left. Either way the link expires, so download the video rather than storing it.\n`status` is xAI's own two-value vocabulary — `pending` or `done`, NOT `completed` — and `status`, `model` and `usage` are all populated on a PENDING poll and on an input-moderation refusal alike. A caller must therefore key completion off `video.url`, NOT off `video`: on the poll document the `video` object can be present with a null `url`, which is the `success_without_output` case above, so the container test reports a generation that produced nothing as finished. It is the same rule Router applies when it decides a poll SUCCEEDED: `video.url` must be non-empty.\nThe composition NARROWS `XAIVideoResultResponse` rather than republishing it, because `XAIVideoResultResponse` describes every poll of the request — including the pending and refused ones a caller never sees — while this document describes only the body a `200` carries. Router reports success on one shape: `video.url` non-empty, no non-empty `block_reason`, and `status` absent or `done`. A `pending` poll is polled again rather than returned, a non-empty `block_reason` is an input-moderation refusal answered as a Comfy Router error, and a terminal result with no `video.url` is the `success_without_output` error above, so `video` and its `url` are required here and `block_reason` is at most empty. `status` is narrowed to the single terminal spelling `done` because Router compares it WITHOUT a case fold, so no other spelling can reach a `200`; it stays optional, because Router also reports success when `status` is missing and `video.url` is populated.","example":{"model":"grok-imagine-video","status":"done","usage":{"cost_in_usd_ticks":3500000000},"video":{"duration":4,"respect_moderation":true,"url":"https://example.invalid/xai/grok-imagine-video/generated.mp4"}}}}}}}}}},"components":{"schemas":{"XAIGeneratedVideo":{"description":"A generated video from xAI","properties":{"duration":{"description":"Duration of the generated video in seconds","type":"integer"},"respect_moderation":{"description":"Whether the video generated by the model respects moderation rules","type":"boolean"},"url":{"description":"Download URL for the generated video. Router re-hosts the video onto Comfy storage and rewrites this field, so it is normally a Comfy-signed URL valid for up to 24 hours - signed for 24 hours when minted and replayed from a 23-hour memo, so a later poll can hand back one with as little as an hour left. When the re-host could not be performed the field keeps xAI's own short-lived URL instead. NULLABLE: a success whose `url` is empty is not a completed generation. Either way the link expires, so download the video rather than storing the URL.","nullable":true,"type":"string"}},"type":"object"},"XAIImageObject":{"description":"Input image object for xAI endpoints","properties":{"type":{"description":"Type of the image input","enum":["image_url"],"type":"string"},"url":{"description":"URL of the input image (public URL or base64-encoded data URI)","type":"string"}},"required":["url"],"type":"object"},"XAIReferenceImageObject":{"description":"A reference image used to guide video generation in R2V mode","properties":{"url":{"description":"URL of the reference image. Supports HTTPS URLs (public) or base64-encoded data URLs (e.g., data:image/jpeg;base64,...).","type":"string"}},"required":["url"],"type":"object"},"XAIVideoResultResponse":{"description":"Response from getting video generation result","properties":{"block_reason":{"description":"If the request was blocked by input moderation, contains the block reason","nullable":true,"type":"string"},"model":{"description":"The model used to generate the video","type":"string"},"status":{"description":"Status of the deferred request: \"pending\" or \"done\"","enum":["pending","done"],"type":"string"},"usage":{"$ref":"#/components/schemas/XAIVideoUsage"},"video":{"$ref":"#/components/schemas/XAIGeneratedVideo"}},"type":"object"},"XAIVideoUsage":{"description":"Usage information for the video generation request","properties":{"cost_in_usd_ticks":{"description":"The cost of this request expressed in USD ticks. One USD cent equals 100,000,000 ticks, so one US dollar equals 10,000,000,000 ticks.\n","type":"integer"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"xai/grok-imagine-video-1.5-preview","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image"],"x-comfy-router-output-modalities":["video"]}
diff --git a/router-schemas/xai/grok-imagine-video-1.5.json b/router-schemas/xai/grok-imagine-video-1.5.json
index c184532c58..9b79123f18 100644
--- a/router-schemas/xai/grok-imagine-video-1.5.json
+++ b/router-schemas/xai/grok-imagine-video-1.5.json
@@ -1 +1 @@
-{"openapi":"3.0.2","info":{"title":"xai/grok-imagine-video-1.5","description":"The request body Comfy Router accepts for the model \"xai/grok-imagine-video-1.5\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"4888f1b583c9"},"paths":{"/v2/models/xai/grok-imagine-video-1.5":{"post":{"operationId":"runRouterModel","summary":"Run xai/grok-imagine-video-1.5 synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"description":"Request body for xAI Grok Imagine video generation.\nSupports three modes: text-to-video (prompt only), image-to-video (prompt + image),\nand reference-to-video (prompt + reference_images).\nThe fields image, reference_images, and video are mutually exclusive.\n","example":{"duration":4,"prompt":"a single red maple leaf falling onto still water, slow motion"},"not":{"required":["image","reference_images"]},"properties":{"aspect_ratio":{"default":"16:9","description":"Aspect ratio of the generated video","enum":["1:1","16:9","9:16","4:3","3:4","3:2","2:3"],"type":"string"},"duration":{"default":8,"description":"Video duration in seconds. Range [1, 15]. Default 8.","maximum":15,"minimum":1,"nullable":true,"type":"integer"},"image":{"$ref":"#/components/schemas/XAIImageObject"},"model":{"description":"Model to be used. Supported: grok-imagine-video (default), grok-imagine-video-1.5-preview, grok-imagine-video-1.5, grok-imagine-video-1.5-lite. The deprecated grok-imagine-video-beta id is aliased to grok-imagine-video.","nullable":true,"type":"string"},"output":{"description":"Optional output destination for generated video","nullable":true,"type":"object"},"prompt":{"description":"Prompt for video generation. Maximum 4,096 characters.","maxLength":4096,"type":"string"},"reference_images":{"description":"One or more reference images to guide the video generation (reference-to-video mode). Mutually exclusive with image. Router forwards application/json only and neither this object nor XAIImageObject accepts a file_id, so file-backed image inputs are not supported on this route.","items":{"$ref":"#/components/schemas/XAIReferenceImageObject"},"minItems":1,"type":"array"},"resolution":{"description":"Resolution of the output video","nullable":true,"type":"string"},"size":{"description":"Size of the output video","nullable":true,"type":"string"},"user":{"description":"A unique identifier representing your end-user","nullable":true,"type":"string"}},"required":["prompt"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/XAIVideoResultResponse"}],"description":"Comfy Router output schema for the xAI Grok Imagine video models: the terminal `GET /v1/videos/{request_id}` document, forwarded unchanged EXCEPT for the re-hosted `video.url` described below - every other field is xAI's own. This family is SUBMIT-AND-POLL — the submit answers with an `XAIVideoAsyncResponse` handle carrying `request_id`, and Router polls `GET /v1/videos/{request_id}` on the caller's behalf, so the body a caller receives is the finished result rather than that handle.\nThe generated video is in `video`. Its `url` is NULLABLE, and Router treats a success whose `video.url` is empty as `success_without_output` rather than as a completed generation.\nThat `url` is RE-HOSTED: Router copies the finished video onto Comfy storage and answers a Comfy-signed URL valid for up to 24 hours in place of xAI's own, so it is a Comfy link and not the partner's. Durability is per entry, not per response, exactly as it is on the image family: a video whose re-host could not be performed keeps xAI's own short-lived URL rather than a Comfy one, so a caller that stores or replays this document should not assume the URL outlives the call. 24 hours is the CEILING, not a guarantee: the Comfy link is signed for 24 hours from the moment it is minted, and Router memoises it for 23 hours, so a later poll or an `Idempotency-Key` replay can hand back a link with as little as an hour left. Either way the link expires, so download the video rather than storing it.\n`status` is xAI's own two-value vocabulary — `pending` or `done`, NOT `completed` — and `status`, `model` and `usage` are all populated on a PENDING poll and on an input-moderation refusal alike. A caller must therefore key completion off `video.url`, NOT off `video`: the `video` object can be present with a null `url`, which is the `success_without_output` case above, so the container test reports a generation that produced nothing as finished. It is the same rule Router applies when it decides a poll SUCCEEDED: `video.url` must be non-empty.","example":{"model":"grok-imagine-video","status":"done","usage":{"cost_in_usd_ticks":3500000000},"video":{"duration":4,"respect_moderation":true,"url":"https://example.invalid/xai/grok-imagine-video/generated.mp4"}}}}}}}}}},"components":{"schemas":{"XAIGeneratedVideo":{"description":"A generated video from xAI","properties":{"duration":{"description":"Duration of the generated video in seconds","type":"integer"},"respect_moderation":{"description":"Whether the video generated by the model respects moderation rules","type":"boolean"},"url":{"description":"Download URL for the generated video. Router re-hosts the video onto Comfy storage and rewrites this field, so it is normally a Comfy-signed URL valid for up to 24 hours - signed for 24 hours when minted and replayed from a 23-hour memo, so a later poll can hand back one with as little as an hour left. When the re-host could not be performed the field keeps xAI's own short-lived URL instead. NULLABLE: a success whose `url` is empty is not a completed generation. Either way the link expires, so download the video rather than storing the URL.","nullable":true,"type":"string"}},"type":"object"},"XAIImageObject":{"description":"Input image object for xAI endpoints","properties":{"type":{"description":"Type of the image input","enum":["image_url"],"type":"string"},"url":{"description":"URL of the input image (public URL or base64-encoded data URI)","type":"string"}},"required":["url"],"type":"object"},"XAIReferenceImageObject":{"description":"A reference image used to guide video generation in R2V mode","properties":{"url":{"description":"URL of the reference image. Supports HTTPS URLs (public) or base64-encoded data URLs (e.g., data:image/jpeg;base64,...).","type":"string"}},"required":["url"],"type":"object"},"XAIVideoResultResponse":{"description":"Response from getting video generation result","properties":{"block_reason":{"description":"If the request was blocked by input moderation, contains the block reason","nullable":true,"type":"string"},"model":{"description":"The model used to generate the video","type":"string"},"status":{"description":"Status of the deferred request: \"pending\" or \"done\"","enum":["pending","done"],"type":"string"},"usage":{"$ref":"#/components/schemas/XAIVideoUsage"},"video":{"$ref":"#/components/schemas/XAIGeneratedVideo"}},"type":"object"},"XAIVideoUsage":{"description":"Usage information for the video generation request","properties":{"cost_in_usd_ticks":{"description":"The cost of this request expressed in USD ticks. One USD cent equals 100,000,000 ticks, so one US dollar equals 10,000,000,000 ticks.\n","type":"integer"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"xai/grok-imagine-video-1.5","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image"],"x-comfy-router-output-modalities":["video"]}
+{"openapi":"3.0.2","info":{"title":"xai/grok-imagine-video-1.5","description":"The request body Comfy Router accepts for the model \"xai/grok-imagine-video-1.5\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"dc9596926447"},"paths":{"/v2/models/xai/grok-imagine-video-1.5":{"post":{"operationId":"runRouterModel","summary":"Run xai/grok-imagine-video-1.5 synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"description":"Request body for xAI Grok Imagine video generation.\nSupports three modes: text-to-video (prompt only), image-to-video (prompt + image),\nand reference-to-video (prompt + reference_images).\nThe fields image, reference_images, and video are mutually exclusive.\n","example":{"duration":4,"prompt":"a single red maple leaf falling onto still water, slow motion"},"not":{"required":["image","reference_images"]},"properties":{"aspect_ratio":{"default":"16:9","description":"Aspect ratio of the generated video","enum":["1:1","16:9","9:16","4:3","3:4","3:2","2:3"],"type":"string"},"duration":{"default":8,"description":"Video duration in seconds. Range [1, 15]. Default 8.","maximum":15,"minimum":1,"nullable":true,"type":"integer"},"image":{"$ref":"#/components/schemas/XAIImageObject"},"model":{"description":"Model to be used. Supported: grok-imagine-video (default), grok-imagine-video-1.5-preview, grok-imagine-video-1.5, grok-imagine-video-1.5-lite. The deprecated grok-imagine-video-beta id is aliased to grok-imagine-video.","nullable":true,"type":"string"},"output":{"description":"Optional output destination for generated video","nullable":true,"type":"object"},"prompt":{"description":"Prompt for video generation. Maximum 4,096 characters.","maxLength":4096,"type":"string"},"reference_images":{"description":"One or more reference images to guide the video generation (reference-to-video mode). Mutually exclusive with image. Router forwards application/json only and neither this object nor XAIImageObject accepts a file_id, so file-backed image inputs are not supported on this route.","items":{"$ref":"#/components/schemas/XAIReferenceImageObject"},"minItems":1,"type":"array"},"resolution":{"description":"Resolution of the output video","nullable":true,"type":"string"},"size":{"description":"Size of the output video","nullable":true,"type":"string"},"user":{"description":"A unique identifier representing your end-user","nullable":true,"type":"string"}},"required":["prompt"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/XAIVideoResultResponse"},{"properties":{"block_reason":{"description":"Never carries a reason on this document: a non-empty block reason is an input-moderation refusal, which Router answers as a Comfy Router error rather than with this body. Absent, null or empty here.","maxLength":0,"nullable":true,"type":"string"},"status":{"description":"Absent or `done` on this document. `pending` is the body of a poll Router has not finished and is never returned as a `200`; Router accepts a missing status when `video.url` is populated, so a caller must not require it.","enum":["done"],"type":"string"},"video":{"description":"The finished generation. Required here, unlike on the poll document: a terminal result with no `video.url` is answered as a Comfy Router error, so a `200` always carries it.","properties":{"duration":{"description":"Duration of the generated video in seconds","type":"integer"},"respect_moderation":{"description":"Whether the video generated by the model respects moderation rules","type":"boolean"},"url":{"description":"Non-empty on this document. Router reports success only when this leaf is populated; it is the re-hosted Comfy-signed URL described above, or xAI's own short-lived URL when the re-host could not be performed.","minLength":1,"type":"string"}},"required":["url"],"type":"object"}},"required":["video"],"type":"object"}],"description":"Comfy Router output schema for the xAI Grok Imagine video models: the terminal `GET /v1/videos/{request_id}` document, forwarded unchanged EXCEPT for the re-hosted `video.url` described below - every other field is xAI's own. This family is SUBMIT-AND-POLL — the submit answers with an `XAIVideoAsyncResponse` handle carrying `request_id`, and Router polls `GET /v1/videos/{request_id}` on the caller's behalf, so the body a caller receives is the finished result rather than that handle.\nThe generated video is in `video`. On the poll document its `url` is NULLABLE, and Router treats a success whose `video.url` is empty as `success_without_output` rather than as a completed generation.\nThat `url` is RE-HOSTED: Router copies the finished video onto Comfy storage and answers a Comfy-signed URL valid for up to 24 hours in place of xAI's own, so it is a Comfy link and not the partner's. Durability is per entry, not per response, exactly as it is on the image family: a video whose re-host could not be performed keeps xAI's own short-lived URL rather than a Comfy one, so a caller that stores or replays this document should not assume the URL outlives the call. 24 hours is the CEILING, not a guarantee: the Comfy link is signed for 24 hours from the moment it is minted, and Router memoises it for 23 hours, so a later poll or an `Idempotency-Key` replay can hand back a link with as little as an hour left. Either way the link expires, so download the video rather than storing it.\n`status` is xAI's own two-value vocabulary — `pending` or `done`, NOT `completed` — and `status`, `model` and `usage` are all populated on a PENDING poll and on an input-moderation refusal alike. A caller must therefore key completion off `video.url`, NOT off `video`: on the poll document the `video` object can be present with a null `url`, which is the `success_without_output` case above, so the container test reports a generation that produced nothing as finished. It is the same rule Router applies when it decides a poll SUCCEEDED: `video.url` must be non-empty.\nThe composition NARROWS `XAIVideoResultResponse` rather than republishing it, because `XAIVideoResultResponse` describes every poll of the request — including the pending and refused ones a caller never sees — while this document describes only the body a `200` carries. Router reports success on one shape: `video.url` non-empty, no non-empty `block_reason`, and `status` absent or `done`. A `pending` poll is polled again rather than returned, a non-empty `block_reason` is an input-moderation refusal answered as a Comfy Router error, and a terminal result with no `video.url` is the `success_without_output` error above, so `video` and its `url` are required here and `block_reason` is at most empty. `status` is narrowed to the single terminal spelling `done` because Router compares it WITHOUT a case fold, so no other spelling can reach a `200`; it stays optional, because Router also reports success when `status` is missing and `video.url` is populated.","example":{"model":"grok-imagine-video","status":"done","usage":{"cost_in_usd_ticks":3500000000},"video":{"duration":4,"respect_moderation":true,"url":"https://example.invalid/xai/grok-imagine-video/generated.mp4"}}}}}}}}}},"components":{"schemas":{"XAIGeneratedVideo":{"description":"A generated video from xAI","properties":{"duration":{"description":"Duration of the generated video in seconds","type":"integer"},"respect_moderation":{"description":"Whether the video generated by the model respects moderation rules","type":"boolean"},"url":{"description":"Download URL for the generated video. Router re-hosts the video onto Comfy storage and rewrites this field, so it is normally a Comfy-signed URL valid for up to 24 hours - signed for 24 hours when minted and replayed from a 23-hour memo, so a later poll can hand back one with as little as an hour left. When the re-host could not be performed the field keeps xAI's own short-lived URL instead. NULLABLE: a success whose `url` is empty is not a completed generation. Either way the link expires, so download the video rather than storing the URL.","nullable":true,"type":"string"}},"type":"object"},"XAIImageObject":{"description":"Input image object for xAI endpoints","properties":{"type":{"description":"Type of the image input","enum":["image_url"],"type":"string"},"url":{"description":"URL of the input image (public URL or base64-encoded data URI)","type":"string"}},"required":["url"],"type":"object"},"XAIReferenceImageObject":{"description":"A reference image used to guide video generation in R2V mode","properties":{"url":{"description":"URL of the reference image. Supports HTTPS URLs (public) or base64-encoded data URLs (e.g., data:image/jpeg;base64,...).","type":"string"}},"required":["url"],"type":"object"},"XAIVideoResultResponse":{"description":"Response from getting video generation result","properties":{"block_reason":{"description":"If the request was blocked by input moderation, contains the block reason","nullable":true,"type":"string"},"model":{"description":"The model used to generate the video","type":"string"},"status":{"description":"Status of the deferred request: \"pending\" or \"done\"","enum":["pending","done"],"type":"string"},"usage":{"$ref":"#/components/schemas/XAIVideoUsage"},"video":{"$ref":"#/components/schemas/XAIGeneratedVideo"}},"type":"object"},"XAIVideoUsage":{"description":"Usage information for the video generation request","properties":{"cost_in_usd_ticks":{"description":"The cost of this request expressed in USD ticks. One USD cent equals 100,000,000 ticks, so one US dollar equals 10,000,000,000 ticks.\n","type":"integer"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"xai/grok-imagine-video-1.5","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image"],"x-comfy-router-output-modalities":["video"]}
diff --git a/router-schemas/xai/grok-imagine-video.json b/router-schemas/xai/grok-imagine-video.json
index b7cb4c8e27..fdbab98ab6 100644
--- a/router-schemas/xai/grok-imagine-video.json
+++ b/router-schemas/xai/grok-imagine-video.json
@@ -1 +1 @@
-{"openapi":"3.0.2","info":{"title":"xai/grok-imagine-video","description":"The request body Comfy Router accepts for the model \"xai/grok-imagine-video\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"4888f1b583c9"},"paths":{"/v2/models/xai/grok-imagine-video":{"post":{"operationId":"runRouterModel","summary":"Run xai/grok-imagine-video synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"description":"Request body for xAI Grok Imagine video generation.\nSupports three modes: text-to-video (prompt only), image-to-video (prompt + image),\nand reference-to-video (prompt + reference_images).\nThe fields image, reference_images, and video are mutually exclusive.\n","example":{"duration":4,"prompt":"a single red maple leaf falling onto still water, slow motion"},"not":{"required":["image","reference_images"]},"properties":{"aspect_ratio":{"default":"16:9","description":"Aspect ratio of the generated video","enum":["1:1","16:9","9:16","4:3","3:4","3:2","2:3"],"type":"string"},"duration":{"default":8,"description":"Video duration in seconds. Range [1, 15]. Default 8.","maximum":15,"minimum":1,"nullable":true,"type":"integer"},"image":{"$ref":"#/components/schemas/XAIImageObject"},"model":{"description":"Model to be used. Supported: grok-imagine-video (default), grok-imagine-video-1.5-preview, grok-imagine-video-1.5, grok-imagine-video-1.5-lite. The deprecated grok-imagine-video-beta id is aliased to grok-imagine-video.","nullable":true,"type":"string"},"output":{"description":"Optional output destination for generated video","nullable":true,"type":"object"},"prompt":{"description":"Prompt for video generation. Maximum 4,096 characters.","maxLength":4096,"type":"string"},"reference_images":{"description":"One or more reference images to guide the video generation (reference-to-video mode). Mutually exclusive with image. Router forwards application/json only and neither this object nor XAIImageObject accepts a file_id, so file-backed image inputs are not supported on this route.","items":{"$ref":"#/components/schemas/XAIReferenceImageObject"},"minItems":1,"type":"array"},"resolution":{"description":"Resolution of the output video","nullable":true,"type":"string"},"size":{"description":"Size of the output video","nullable":true,"type":"string"},"user":{"description":"A unique identifier representing your end-user","nullable":true,"type":"string"}},"required":["prompt"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/XAIVideoResultResponse"}],"description":"Comfy Router output schema for the xAI Grok Imagine video models: the terminal `GET /v1/videos/{request_id}` document, forwarded unchanged EXCEPT for the re-hosted `video.url` described below - every other field is xAI's own. This family is SUBMIT-AND-POLL — the submit answers with an `XAIVideoAsyncResponse` handle carrying `request_id`, and Router polls `GET /v1/videos/{request_id}` on the caller's behalf, so the body a caller receives is the finished result rather than that handle.\nThe generated video is in `video`. Its `url` is NULLABLE, and Router treats a success whose `video.url` is empty as `success_without_output` rather than as a completed generation.\nThat `url` is RE-HOSTED: Router copies the finished video onto Comfy storage and answers a Comfy-signed URL valid for up to 24 hours in place of xAI's own, so it is a Comfy link and not the partner's. Durability is per entry, not per response, exactly as it is on the image family: a video whose re-host could not be performed keeps xAI's own short-lived URL rather than a Comfy one, so a caller that stores or replays this document should not assume the URL outlives the call. 24 hours is the CEILING, not a guarantee: the Comfy link is signed for 24 hours from the moment it is minted, and Router memoises it for 23 hours, so a later poll or an `Idempotency-Key` replay can hand back a link with as little as an hour left. Either way the link expires, so download the video rather than storing it.\n`status` is xAI's own two-value vocabulary — `pending` or `done`, NOT `completed` — and `status`, `model` and `usage` are all populated on a PENDING poll and on an input-moderation refusal alike. A caller must therefore key completion off `video.url`, NOT off `video`: the `video` object can be present with a null `url`, which is the `success_without_output` case above, so the container test reports a generation that produced nothing as finished. It is the same rule Router applies when it decides a poll SUCCEEDED: `video.url` must be non-empty.","example":{"model":"grok-imagine-video","status":"done","usage":{"cost_in_usd_ticks":3500000000},"video":{"duration":4,"respect_moderation":true,"url":"https://example.invalid/xai/grok-imagine-video/generated.mp4"}}}}}}}}}},"components":{"schemas":{"XAIGeneratedVideo":{"description":"A generated video from xAI","properties":{"duration":{"description":"Duration of the generated video in seconds","type":"integer"},"respect_moderation":{"description":"Whether the video generated by the model respects moderation rules","type":"boolean"},"url":{"description":"Download URL for the generated video. Router re-hosts the video onto Comfy storage and rewrites this field, so it is normally a Comfy-signed URL valid for up to 24 hours - signed for 24 hours when minted and replayed from a 23-hour memo, so a later poll can hand back one with as little as an hour left. When the re-host could not be performed the field keeps xAI's own short-lived URL instead. NULLABLE: a success whose `url` is empty is not a completed generation. Either way the link expires, so download the video rather than storing the URL.","nullable":true,"type":"string"}},"type":"object"},"XAIImageObject":{"description":"Input image object for xAI endpoints","properties":{"type":{"description":"Type of the image input","enum":["image_url"],"type":"string"},"url":{"description":"URL of the input image (public URL or base64-encoded data URI)","type":"string"}},"required":["url"],"type":"object"},"XAIReferenceImageObject":{"description":"A reference image used to guide video generation in R2V mode","properties":{"url":{"description":"URL of the reference image. Supports HTTPS URLs (public) or base64-encoded data URLs (e.g., data:image/jpeg;base64,...).","type":"string"}},"required":["url"],"type":"object"},"XAIVideoResultResponse":{"description":"Response from getting video generation result","properties":{"block_reason":{"description":"If the request was blocked by input moderation, contains the block reason","nullable":true,"type":"string"},"model":{"description":"The model used to generate the video","type":"string"},"status":{"description":"Status of the deferred request: \"pending\" or \"done\"","enum":["pending","done"],"type":"string"},"usage":{"$ref":"#/components/schemas/XAIVideoUsage"},"video":{"$ref":"#/components/schemas/XAIGeneratedVideo"}},"type":"object"},"XAIVideoUsage":{"description":"Usage information for the video generation request","properties":{"cost_in_usd_ticks":{"description":"The cost of this request expressed in USD ticks. One USD cent equals 100,000,000 ticks, so one US dollar equals 10,000,000,000 ticks.\n","type":"integer"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"xai/grok-imagine-video","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image"],"x-comfy-router-output-modalities":["video"]}
+{"openapi":"3.0.2","info":{"title":"xai/grok-imagine-video","description":"The request body Comfy Router accepts for the model \"xai/grok-imagine-video\", and the response body it returns. The INPUT schema is the same schema the server validates a call against before it reaches the provider, so what is published and what is enforced cannot differ. `x-comfy-router-input-modalities` records the media kinds the model is documented to accept; the request schema is shared across sibling models and may admit media kinds this model ignores. The OUTPUT schema describes the provider's native result document exactly as Router returns it: Router does not validate, narrow or re-envelope the response, so the output schema is descriptive rather than enforced, and Comfy owns no output shape of its own.","version":"dc9596926447"},"paths":{"/v2/models/xai/grok-imagine-video":{"post":{"operationId":"runRouterModel","summary":"Run xai/grok-imagine-video synchronously.","security":[{"BearerAuth":[]},{"ApiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"description":"Request body for xAI Grok Imagine video generation.\nSupports three modes: text-to-video (prompt only), image-to-video (prompt + image),\nand reference-to-video (prompt + reference_images).\nThe fields image, reference_images, and video are mutually exclusive.\n","example":{"duration":4,"prompt":"a single red maple leaf falling onto still water, slow motion"},"not":{"required":["image","reference_images"]},"properties":{"aspect_ratio":{"default":"16:9","description":"Aspect ratio of the generated video","enum":["1:1","16:9","9:16","4:3","3:4","3:2","2:3"],"type":"string"},"duration":{"default":8,"description":"Video duration in seconds. Range [1, 15]. Default 8.","maximum":15,"minimum":1,"nullable":true,"type":"integer"},"image":{"$ref":"#/components/schemas/XAIImageObject"},"model":{"description":"Model to be used. Supported: grok-imagine-video (default), grok-imagine-video-1.5-preview, grok-imagine-video-1.5, grok-imagine-video-1.5-lite. The deprecated grok-imagine-video-beta id is aliased to grok-imagine-video.","nullable":true,"type":"string"},"output":{"description":"Optional output destination for generated video","nullable":true,"type":"object"},"prompt":{"description":"Prompt for video generation. Maximum 4,096 characters.","maxLength":4096,"type":"string"},"reference_images":{"description":"One or more reference images to guide the video generation (reference-to-video mode). Mutually exclusive with image. Router forwards application/json only and neither this object nor XAIImageObject accepts a file_id, so file-backed image inputs are not supported on this route.","items":{"$ref":"#/components/schemas/XAIReferenceImageObject"},"minItems":1,"type":"array"},"resolution":{"description":"Resolution of the output video","nullable":true,"type":"string"},"size":{"description":"Size of the output video","nullable":true,"type":"string"},"user":{"description":"A unique identifier representing your end-user","nullable":true,"type":"string"}},"required":["prompt"],"type":"object"}}}},"responses":{"200":{"description":"OK - the model's native JSON output, returned unchanged; the schema describes the provider's terminal result document as Router returns it.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/XAIVideoResultResponse"},{"properties":{"block_reason":{"description":"Never carries a reason on this document: a non-empty block reason is an input-moderation refusal, which Router answers as a Comfy Router error rather than with this body. Absent, null or empty here.","maxLength":0,"nullable":true,"type":"string"},"status":{"description":"Absent or `done` on this document. `pending` is the body of a poll Router has not finished and is never returned as a `200`; Router accepts a missing status when `video.url` is populated, so a caller must not require it.","enum":["done"],"type":"string"},"video":{"description":"The finished generation. Required here, unlike on the poll document: a terminal result with no `video.url` is answered as a Comfy Router error, so a `200` always carries it.","properties":{"duration":{"description":"Duration of the generated video in seconds","type":"integer"},"respect_moderation":{"description":"Whether the video generated by the model respects moderation rules","type":"boolean"},"url":{"description":"Non-empty on this document. Router reports success only when this leaf is populated; it is the re-hosted Comfy-signed URL described above, or xAI's own short-lived URL when the re-host could not be performed.","minLength":1,"type":"string"}},"required":["url"],"type":"object"}},"required":["video"],"type":"object"}],"description":"Comfy Router output schema for the xAI Grok Imagine video models: the terminal `GET /v1/videos/{request_id}` document, forwarded unchanged EXCEPT for the re-hosted `video.url` described below - every other field is xAI's own. This family is SUBMIT-AND-POLL — the submit answers with an `XAIVideoAsyncResponse` handle carrying `request_id`, and Router polls `GET /v1/videos/{request_id}` on the caller's behalf, so the body a caller receives is the finished result rather than that handle.\nThe generated video is in `video`. On the poll document its `url` is NULLABLE, and Router treats a success whose `video.url` is empty as `success_without_output` rather than as a completed generation.\nThat `url` is RE-HOSTED: Router copies the finished video onto Comfy storage and answers a Comfy-signed URL valid for up to 24 hours in place of xAI's own, so it is a Comfy link and not the partner's. Durability is per entry, not per response, exactly as it is on the image family: a video whose re-host could not be performed keeps xAI's own short-lived URL rather than a Comfy one, so a caller that stores or replays this document should not assume the URL outlives the call. 24 hours is the CEILING, not a guarantee: the Comfy link is signed for 24 hours from the moment it is minted, and Router memoises it for 23 hours, so a later poll or an `Idempotency-Key` replay can hand back a link with as little as an hour left. Either way the link expires, so download the video rather than storing it.\n`status` is xAI's own two-value vocabulary — `pending` or `done`, NOT `completed` — and `status`, `model` and `usage` are all populated on a PENDING poll and on an input-moderation refusal alike. A caller must therefore key completion off `video.url`, NOT off `video`: on the poll document the `video` object can be present with a null `url`, which is the `success_without_output` case above, so the container test reports a generation that produced nothing as finished. It is the same rule Router applies when it decides a poll SUCCEEDED: `video.url` must be non-empty.\nThe composition NARROWS `XAIVideoResultResponse` rather than republishing it, because `XAIVideoResultResponse` describes every poll of the request — including the pending and refused ones a caller never sees — while this document describes only the body a `200` carries. Router reports success on one shape: `video.url` non-empty, no non-empty `block_reason`, and `status` absent or `done`. A `pending` poll is polled again rather than returned, a non-empty `block_reason` is an input-moderation refusal answered as a Comfy Router error, and a terminal result with no `video.url` is the `success_without_output` error above, so `video` and its `url` are required here and `block_reason` is at most empty. `status` is narrowed to the single terminal spelling `done` because Router compares it WITHOUT a case fold, so no other spelling can reach a `200`; it stays optional, because Router also reports success when `status` is missing and `video.url` is populated.","example":{"model":"grok-imagine-video","status":"done","usage":{"cost_in_usd_ticks":3500000000},"video":{"duration":4,"respect_moderation":true,"url":"https://example.invalid/xai/grok-imagine-video/generated.mp4"}}}}}}}}}},"components":{"schemas":{"XAIGeneratedVideo":{"description":"A generated video from xAI","properties":{"duration":{"description":"Duration of the generated video in seconds","type":"integer"},"respect_moderation":{"description":"Whether the video generated by the model respects moderation rules","type":"boolean"},"url":{"description":"Download URL for the generated video. Router re-hosts the video onto Comfy storage and rewrites this field, so it is normally a Comfy-signed URL valid for up to 24 hours - signed for 24 hours when minted and replayed from a 23-hour memo, so a later poll can hand back one with as little as an hour left. When the re-host could not be performed the field keeps xAI's own short-lived URL instead. NULLABLE: a success whose `url` is empty is not a completed generation. Either way the link expires, so download the video rather than storing the URL.","nullable":true,"type":"string"}},"type":"object"},"XAIImageObject":{"description":"Input image object for xAI endpoints","properties":{"type":{"description":"Type of the image input","enum":["image_url"],"type":"string"},"url":{"description":"URL of the input image (public URL or base64-encoded data URI)","type":"string"}},"required":["url"],"type":"object"},"XAIReferenceImageObject":{"description":"A reference image used to guide video generation in R2V mode","properties":{"url":{"description":"URL of the reference image. Supports HTTPS URLs (public) or base64-encoded data URLs (e.g., data:image/jpeg;base64,...).","type":"string"}},"required":["url"],"type":"object"},"XAIVideoResultResponse":{"description":"Response from getting video generation result","properties":{"block_reason":{"description":"If the request was blocked by input moderation, contains the block reason","nullable":true,"type":"string"},"model":{"description":"The model used to generate the video","type":"string"},"status":{"description":"Status of the deferred request: \"pending\" or \"done\"","enum":["pending","done"],"type":"string"},"usage":{"$ref":"#/components/schemas/XAIVideoUsage"},"video":{"$ref":"#/components/schemas/XAIGeneratedVideo"}},"type":"object"},"XAIVideoUsage":{"description":"Usage information for the video generation request","properties":{"cost_in_usd_ticks":{"description":"The cost of this request expressed in USD ticks. One USD cent equals 100,000,000 ticks, so one US dollar equals 10,000,000,000 ticks.\n","type":"integer"}},"type":"object"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}},"x-comfy-router-model-id":"xai/grok-imagine-video","x-comfy-input-schema-authored":true,"x-comfy-output-schema-authored":true,"x-comfy-router-input-modalities":["text","image"],"x-comfy-router-output-modalities":["video"]}
From 7152bc0ac132ec6b312059d571a85156f7afea22 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Sun, 11 Oct 2026 09:00:34 +0000
Subject: [PATCH 2/3] Sync Router pricing translations
---
development/comfy-router/pricing.mdx | 1314 +++++++++++++++++++++--
ja/development/comfy-router/pricing.mdx | 40 +-
ko/development/comfy-router/pricing.mdx | 40 +-
zh/development/comfy-router/pricing.mdx | 40 +-
4 files changed, 1335 insertions(+), 99 deletions(-)
diff --git a/development/comfy-router/pricing.mdx b/development/comfy-router/pricing.mdx
index 756bb57f80..8037e17dd3 100644
--- a/development/comfy-router/pricing.mdx
+++ b/development/comfy-router/pricing.mdx
@@ -1,141 +1,1269 @@
---
title: "Comfy Router pricing"
sidebarTitle: "Pricing"
-description: "What a Router generation costs, how credits convert to dollars, and how to compare the providers serving the same model."
+description: "Compare Comfy Router model prices in credits or USD by serving provider across image, video, text, audio, and 3D."
+mode: "wide"
---
-Router is pay per use. Each generation draws credits from your workspace balance, with no subscription. What a generation costs depends on the model and on which provider serves it.
+import { PricingCurrencyToggle } from "../../snippets/router-pricing-currency.jsx";
-## Credits and dollars
+{/* Generated pricing page. */}
-Comfy prices in credits at a fixed rate:
+211 credits = $1 USD.
-**$1.00 USD = 211 credits.** One credit is $0.004739.
+
+
+
-Buy credits at [workspace billing](https://platform.comfy.org). A workspace with no balance is refused with `402` and `insufficient_credits` before the request body is validated, so an unfunded workspace can answer `402` even for a malformed request. A call billed to [your own provider key](#bring-your-own-provider-key) does not draw credits and is not refused for an empty balance.
+
-## Read the price off the response
+
-A successful generation can carry an `X-Comfy-Credits-Used` response header giving what that run cost, in credits. It is priced from the same rate card the charge itself is billed against, and on a run that reached the provider through more than one billed call it is their sum.
+#### Token rates
-```bash
-curl -sD - -o /dev/null \
- -H "Authorization: Bearer $COMFY_API_KEY" \
- -H "Content-Type: application/json" \
- -d '{"contents":[{"role":"user","parts":[{"text":"a red bicycle"}]}]}' \
- https://api.comfy.org/v2/models/vertexai/gemini-3.1-flash-image \
- | grep -i x-comfy-credits-used
-```
+| Name | Model ID | Input credits / 1M tokens | Output credits / 1M tokens |
+| --- | --- | ---: | ---: |
+| [Gemini 2.5 Flash Image](/development/comfy-router/models/google/gemini-2-5-flash-image/code) | `vertexai/gemini-2.5-flash-image` | Image / Text / Video: 63.3
Audio: 211 | Text: 527.5
Image: 6330 |
+| [Gemini Nano Banana 2.1](/development/comfy-router/models/google/gemini-nano-banana-2-1/code) | `vertexai/gemini-nano-banana-2.1` | Image / Text / Video: 452.595 | Text / Reasoning: 2262.975
Image: 9051.9 |
+| [GPT Image 2.5 Flare](/development/comfy-router/models/openai/gpt-image-2-5-flare/code) | `openai/gpt-image-2.5-flare` | Text: 1508.65
Image: 2413.84 | Text: 3017.3
Image: 9051.9 |
+| [GPT Image 2.5 Sunburst](/development/comfy-router/models/openai/gpt-image-2-5-sunburst/code) | `openai/gpt-image-2.5-sunburst` | Text: 1508.65
Image: 2413.84 | Text: 3017.3
Image: 9051.9 |
+| [GPT Image 1.5](/development/comfy-router/models/openai/gpt-image-1-5/code) | `openai/gpt-image-1.5` | Text: 1055
Image: 1688 | Text: 2110
Image: 6752 |
+| [GPT Image 1](/development/comfy-router/models/openai/gpt-image-1/code) | `openai/gpt-image-1` | Text: 1055
Image: 2110 | Image: 8440 |
+| [Nano Banana 2](/development/comfy-router/models/google/nano-banana-2/code) | `vertexai/gemini-3.1-flash-image` | Image / Text / Video: 126.6 | Text / Reasoning: 759.6
Image: 15192 |
+| [Nano Banana 2 Lite](/development/comfy-router/models/google/nano-banana-2-lite/code) | `vertexai/gemini-3.1-flash-lite-image` | Image / Text / Video: 63.3 | Text / Reasoning: 379.8
Image: 7596 |
+| [Nano Banana Pro](/development/comfy-router/models/google/nano-banana-pro/code) | `vertexai/gemini-3-pro-image` | Image / Text: 506.4 | Text / Reasoning: 3038.4
Image: 30384 |
-Divide by 211 for dollars: a header reading `25.32` means that run cost $0.12. This model's default provider is metered, so that figure is one run's cost, not the model's price — the next request can read differently. The value is rounded to two decimals, so treat it as a figure to display and reconcile, not as an exact ledger amount.
+#### Usage-based rates
-
-**The header is not present on every response, and absent does not mean free.**
+Comfy usage-based rates are billed per active GPU-second; total cost depends on runtime.
-- **Coverage is partial.** Not every model reports it yet, and coverage is widening. A missing header means "not reported", never "free" — branch on whether the header is present, not on whether its value is non-zero.
-- **It is absent** on a call billed to your own provider key, on a call whose cost could not be rated, and on every error response — including a charged content-policy refusal.
-- **It is a price, not a receipt.** On a model that submits and then polls, it is the price the charge will settle at, recorded before that charge settles. Reconcile against your workspace's usage and billing records rather than against the header alone.
-- **A rated run that genuinely cost nothing reads `0`** — a reported cost, not a missing one. Never sum absent headers as zero.
-
+| Name | Model ID | Credits |
+| --- | --- | ---: |
+| [AI Image Upscaler Precision V2](/development/comfy-router/models/freepik/ai-image-upscaler-precision-v2/code) | `freepik/ai-image-upscaler-precision-v2` | 0.266 / GPU-second |
+| [Erase V1](/development/comfy-router/models/black-forest-labs/erase-v1/code) | `bfl/erase-v1` | 0.266 / GPU-second |
+| [FLUX 2 Max](/development/comfy-router/models/black-forest-labs/flux-2-max/code) | `bfl/flux-2-max` | 0.266 / GPU-second |
+| [FLUX 2 Pro](/development/comfy-router/models/black-forest-labs/flux-2-pro/code) | `bfl/flux-2-pro` | 0.266 / GPU-second |
+| [FLUX 3 Image](/development/comfy-router/models/black-forest-labs/flux-3-image/code) | `bfl/flux-3-image` | 0.266 / GPU-second |
+| [GPT Image 2](/development/comfy-router/models/openai/gpt-image-2/code) | `openai/gpt-image-2` | 0.266 / GPU-second |
+| [Grok Imagine Image](/development/comfy-router/models/xai/grok-imagine-image/code) | `xai/grok-imagine-image` | 0.266 / GPU-second |
+| [Grok Imagine Image 2.0](/development/comfy-router/models/xai/grok-imagine-image-2-0/code) | `xai/grok-imagine-image-2.0` | 0.266 / GPU-second |
+| [Grok Imagine Image Pro](/development/comfy-router/models/xai/grok-imagine-image-pro/code) | `xai/grok-imagine-image-pro` | 0.266 / GPU-second |
+| [Grok Imagine Image Quality](/development/comfy-router/models/xai/grok-imagine-image-quality/code) | `xai/grok-imagine-image-quality` | 0.266 / GPU-second |
+| [VTO V1](/development/comfy-router/models/black-forest-labs/vto-v1/code) | `bfl/vto-v1` | 0.266 / GPU-second |
-Within a single run the header is the **sum** of every billed call that run made. Across retries it is not: a replayed response — a same-key retry answered with `Idempotent-Replayed: true` — is not charged a second time, and when it carries the header it restates the original run's figure rather than a new charge. **Do not add the header up across retries of one key**, or a cost-tracking integration will overstate spend.
+#### Image generation and edit rates
-
-Where a figure on this page and the header disagree, the header reflects the rate card the charge is billed against — treat this page as a guide to the shape of a model's pricing, not as the rate card.
-
+| Name | Model ID | Credits / generation |
+| --- | --- | ---: |
+| [AI Skin Enhancer Creative](/development/comfy-router/models/freepik/ai-skin-enhancer-creative/code) | `freepik/ai-skin-enhancer-creative` | 61.19 |
+| [AI Skin Enhancer Faithful](/development/comfy-router/models/freepik/ai-skin-enhancer-faithful/code) | `freepik/ai-skin-enhancer-faithful` | 78.07 |
+| [AI Skin Enhancer Flexible](/development/comfy-router/models/freepik/ai-skin-enhancer-flexible/code) | `freepik/ai-skin-enhancer-flexible` | 94.95 |
+| [FLUX 1.1 Pro](/development/comfy-router/models/black-forest-labs/flux-1-1-pro-ultra-image/code) | `bfl/flux-pro-1.1` | 8.44 |
+| [FLUX 1.1 Pro Ultra](/development/comfy-router/models/black-forest-labs/flux-1-1-pro-ultra-image/code) | `bfl/flux-pro-1.1-ultra` | 12.66 |
+| [FLUX Pro 1.0 Expand](/development/comfy-router/models/black-forest-labs/flux-pro-1-0-expand/code) | `bfl/flux-pro-1.0-expand` | 10.55 |
+| [FLUX Pro 1.0 Fill](/development/comfy-router/models/black-forest-labs/flux-pro-1-0-fill/code) | `bfl/flux-pro-1.0-fill` | 10.55 |
+| [FLUX.1 Kontext Max](/development/comfy-router/models/black-forest-labs/flux-1-kontext/code) | `bfl/flux-kontext-max` | 16.88 |
+| [FLUX.1 Kontext Pro](/development/comfy-router/models/black-forest-labs/flux-1-kontext/code) | `bfl/flux-kontext-pro` | 8.44 |
+| [Gen 4 Image](/development/comfy-router/models/runway/gen4-image/code) | `runway/gen4_image` | 24.1384 |
+| [Photon 1](/development/comfy-router/models/luma/photon-1/code) | `luma/photon-1` | 2.2026 |
+| [Photon Flash 1](/development/comfy-router/models/luma/photon-flash-1/code) | `luma/photon-flash-1` | 0.5733 |
+| [Seedream 4.0](/development/comfy-router/models/byteplus/seedream-4-0-250828/code) | `byteplus/seedream-4-0-250828` | 6.33 |
+| [Seedream 4.5](/development/comfy-router/models/byteplus/seedream-4-5-251128/code) | `byteplus/seedream-4-5-251128` | 8.44 |
+| [Seedream 5.0](/development/comfy-router/models/byteplus/seedream-5-0-260128/code) | `byteplus/seedream-5-0-260128` | 7.385 |
+| [Seedream 5.0 Flash](/development/comfy-router/models/byteplus/seedream-5-0-flash-260915/code) | `byteplus/seedream-5-0-flash-260915` | 5.4311 |
+| [Fibo](/development/comfy-router/models/bria/fibo/code) | `bria/fibo` | 8.44 |
+| [Add Object By Text](/development/comfy-router/models/bria/image-edit-add-object-by-text/code) | `bria/image-edit-add-object-by-text` | 12.0692 |
+| [Erase](/development/comfy-router/models/bria/image-edit-erase/code) | `bria/image-edit-erase` | 6.0346 |
+| [Erase By Text](/development/comfy-router/models/bria/image-edit-erase-by-text/code) | `bria/image-edit-erase-by-text` | 12.0692 |
+| [Erase Foreground](/development/comfy-router/models/bria/image-edit-erase-foreground/code) | `bria/image-edit-erase-foreground` | 12.0692 |
+| [Expand](/development/comfy-router/models/bria/image-edit-expand/code) | `bria/image-edit-expand` | 6.0346 |
+| [Generative Fill](/development/comfy-router/models/bria/image-edit-gen-fill/code) | `bria/image-edit-gen-fill` | 9.0519 |
+| [Increase Resolution](/development/comfy-router/models/bria/image-edit-increase-resolution/code) | `bria/image-edit-increase-resolution` | 6.0346 |
+| [Relight](/development/comfy-router/models/bria/image-edit-relight/code) | `bria/image-edit-relight` | 12.0692 |
+| [Remove Background](/development/comfy-router/models/bria/image-edit-remove-background/code) | `bria/image-edit-remove-background` | 3.798 |
+| [Replace Background](/development/comfy-router/models/bria/image-edit-replace-background/code) | `bria/image-edit-replace-background` | 12.0692 |
+| [Replace Object By Text](/development/comfy-router/models/bria/image-edit-replace-object-by-text/code) | `bria/image-edit-replace-object-by-text` | 12.0692 |
+| [Reseason](/development/comfy-router/models/bria/image-edit-reseason/code) | `bria/image-edit-reseason` | 12.0692 |
+| [Restore](/development/comfy-router/models/bria/image-edit-restore/code) | `bria/image-edit-restore` | 12.0692 |
+| [Kling Image O1](/development/comfy-router/models/kling/kling-image-o1/code) | `kling/kling-image-o1` | 5.908 |
+| [Wan 2.5 I2I Preview](/development/comfy-router/models/wan/wan2-5-i2i-preview/code) | `wan/wan2.5-i2i-preview` | 6.33 |
+| [Wan 2.5 T2I Preview](/development/comfy-router/models/wan/wan2-5-t2i-preview/code) | `wan/wan2.5-t2i-preview` | 6.33 |
-## Two ways a model is priced
+#### Not published
-Models do not all price the same way, and the difference is what decides whether a single number can describe a model at all.
+| Name | Model ID | Credits |
+| --- | --- | ---: |
+| [Ideogram 4.5](/development/comfy-router/models/ideogram/ideogram-4-5/code) | `ideogram/ideogram-4-5` | Not published |
+| [P Image Ideogram](/development/comfy-router/models/ideogram/p-image-ideogram/code) | `ideogram/p-image-ideogram` | Not published |
+| [Text To Image](/development/comfy-router/models/moonvalley/text-to-image/code) | `moonvalley/text-to-image` | Not published |
-| Shape | What you pay | How it behaves |
-| --- | --- | --- |
-| **Flat per generation** | One price per successful call, sometimes varying across a small set of named options such as resolution, mode, quality, style, or rendering speed. | Predictable. The same request served by the same provider always costs the same. |
-| **Metered per unit** | A rate applied to what the request actually consumed — image or text tokens, output resolution, video duration. | Varies per call, sometimes by more than an order of magnitude between a small cheap request and a large one. |
+#### Rates per input image
-Which shape applies is a property of the model and the provider serving it, so the same model can be flat on one provider and metered on another. A flat shape is only predictable while the call stays on the provider you chose — see [provider fallback](#provider-fallback-can-change-the-price).
+| Name | Model ID | Option | Credits |
+| --- | --- | --- | ---: |
+| [Qwen Image 3.0](/development/comfy-router/models/qwen/qwen-image-3-0/code) | `qwen/qwen-image-3.0` | Input image · 1K / 2K | 0.9052 / input image |
+| [Qwen Image 3.0 Pro](/development/comfy-router/models/qwen/qwen-image-3-0-pro/code) | `qwen/qwen-image-3.0-pro` | Input image · 1K / 2K | 0.9052 / input image |
+| [Seedream 5.0 Pro](/development/comfy-router/models/byteplus/seedream-5-0-pro-260628/code) | `byteplus/seedream-5-0-pro-260628` | Input images after the first | 0.633 / input image |
-Most token-priced models — the Gemini, GPT, and Claude families — are metered. Most dedicated image and video generators are flat, or flat within a named option.
+#### Rates per Recraft credit
-## Comparing providers for the same model
+| Name | Model ID | Credits |
+| --- | --- | ---: |
+| [Recraft V2](/development/comfy-router/models/recraft/recraftv2/code) | `recraft/recraftv2` | 0.3017 / Recraft credit |
+| [Recraft V3](/development/comfy-router/models/recraft/recraftv3/code) | `recraft/recraftv3` | 0.3017 / Recraft credit |
+| [Recraft V4](/development/comfy-router/models/recraft/recraftv4/code) | `recraft/recraftv4` | 0.3017 / Recraft credit |
+| [Recraft V4 Pro](/development/comfy-router/models/recraft/recraftv4-pro/code) | `recraft/recraftv4_pro` | 0.3017 / Recraft credit |
+| [Recraft V4 Styles](/development/comfy-router/models/recraft/recraftv4-styles/code) | `recraft/recraftv4_styles` | 0.3017 / Recraft credit |
+| [Recraft V4 Styles Pro](/development/comfy-router/models/recraft/recraftv4-styles-pro/code) | `recraft/recraftv4_styles_pro` | 0.3017 / Recraft credit |
+| [Recraft V4 Styles Pro Vector](/development/comfy-router/models/recraft/recraftv4-styles-pro-vector/code) | `recraft/recraftv4_styles_pro_vector` | 0.3017 / Recraft credit |
+| [Recraft V4 Styles Vector](/development/comfy-router/models/recraft/recraftv4-styles-vector/code) | `recraft/recraftv4_styles_vector` | 0.3017 / Recraft credit |
+| [Recraft V4.1](/development/comfy-router/models/recraft/recraftv4-1/code) | `recraft/recraftv4_1` | 0.3017 / Recraft credit |
+| [Recraft V4.1 Pro](/development/comfy-router/models/recraft/recraftv4-1-pro/code) | `recraft/recraftv4_1_pro` | 0.3017 / Recraft credit |
+| [Recraft V4.1 Pro Vector](/development/comfy-router/models/recraft/recraftv4-1-pro-vector/code) | `recraft/recraftv4_1_pro_vector` | 0.3017 / Recraft credit |
+| [Recraft V4.1 Utility](/development/comfy-router/models/recraft/recraftv4-1-utility/code) | `recraft/recraftv4_1_utility` | 0.3017 / Recraft credit |
+| [Recraft V4.1 Utility Pro](/development/comfy-router/models/recraft/recraftv4-1-utility-pro/code) | `recraft/recraftv4_1_utility_pro` | 0.3017 / Recraft credit |
+| [Recraft V4.1 Utility Pro Vector](/development/comfy-router/models/recraft/recraftv4-1-utility-pro-vector/code) | `recraft/recraftv4_1_utility_pro_vector` | 0.3017 / Recraft credit |
+| [Recraft V4.1 Utility Vector](/development/comfy-router/models/recraft/recraftv4-1-utility-vector/code) | `recraft/recraftv4_1_utility_vector` | 0.3017 / Recraft credit |
+| [Recraft V4.1 Vector](/development/comfy-router/models/recraft/recraftv4-1-vector/code) | `recraft/recraftv4_1_vector` | 0.3017 / Recraft credit |
-Some models are served by more than one provider. The model is the same; the price and the pricing shape are not. Where a model has alternates, they are listed on its page and on [Providers](/development/comfy-router/providers).
+#### Rates per reference image
-Two image models where the difference is large and worth knowing:
+| Name | Model ID | Credits |
+| --- | --- | ---: |
+| [Uni 1](/development/comfy-router/models/luma_2/uni-1/code) | `luma_2/uni-1` | 0.633 / reference image |
+| [Uni 1 Max](/development/comfy-router/models/luma_2/uni-1-max/code) | `luma_2/uni-1-max` | 0.633 / reference image |
-### `vertexai/gemini-3-pro-image`
+#### Krea 2 generation rates (credits / generation)
-| Provider | Call it as | Pricing |
-| --- | --- | --- |
-| Vertex AI (default) | `vertexai/gemini-3-pro-image` | Metered — per token, across input text, image, audio and video plus output text, image and reasoning. Varies per request. |
-| fal | `fal/fal-nano-banana-pro` | **$0.15** flat per generation |
-| Wavespeed | `wavespeed/wavespeed-nano-banana-pro` | **$0.14** per generation at 1K and 2K, **$0.24** at 4K |
-| Runware | `runware/runware-nano-banana-pro` | Metered — varies with what the request consumed |
+| Name | Model ID | Moodboards Credits | Style references Credits | Text only Credits |
+| --- | --- | ---: | ---: | ---: |
+| [Krea 2](/development/comfy-router/models/krea/krea-2/code) | `krea/krea-2` | 8.44 | 7.385 | 6.33 |
+| [Krea 2 Large](/development/comfy-router/models/krea/krea-2-large/code) | `krea/krea-2-large` | 14.77 | 13.715 | 12.66 |
+| [Krea 2 Medium](/development/comfy-router/models/krea/krea-2-medium/code) | `krea/krea-2-medium` | 8.44 | 7.385 | 6.33 |
+| [Krea 2 Medium Turbo](/development/comfy-router/models/krea/krea-2-medium-turbo/code) | `krea/krea-2-medium-turbo` | 4.22 | 3.6925 | 3.165 |
-### `vertexai/gemini-3.1-flash-image`
+#### Qwen Image 3.0 output image rates
-| Provider | Call it as | Pricing |
-| --- | --- | --- |
-| Vertex AI (default) | `vertexai/gemini-3.1-flash-image` | Metered — per token, on the same basis as above. Varies per request. |
-| fal | `fal/fal-nano-banana-2` | **$0.08** flat per generation |
-| Wavespeed | `wavespeed/wavespeed-nano-banana-2` | **$0.07** flat per generation |
-| Runware | `runware/runware-nano-banana-2` | Metered — varies with what the request consumed |
+| Name | Model ID | 1K Credits / generation | 2K Credits / generation |
+| --- | --- | ---: | ---: |
+| [Qwen Image 3.0](/development/comfy-router/models/qwen/qwen-image-3-0/code) | `qwen/qwen-image-3.0` | 9.0519 | 9.0519 |
+| [Qwen Image 3.0 Pro](/development/comfy-router/models/qwen/qwen-image-3-0-pro/code) | `qwen/qwen-image-3.0-pro` | 12.0692 | 22.6297 |
-The default provider for both is metered, and the alternates differ in how predictable they are: fal is a single flat price, Wavespeed is flat within a resolution band, and Runware varies per request. If you need a per-image price you can forecast, fal and Wavespeed give you one. If your requests are small, a metered provider can still come out lower — the only way to know for your own traffic is to measure it with the header.
+#### Seedream 5.0 Pro output image rates
-### `openai/gpt-image-2` on Wavespeed
+| Name | Model ID | Layer separation · Standard Credits / generation | Layer separation · Large Credits / generation | Output image · Standard Credits / generation | Output image · Large Credits / generation |
+| --- | --- | ---: | ---: | ---: | ---: |
+| [Seedream 5.0 Pro](/development/comfy-router/models/byteplus/seedream-5-0-pro-260628/code) | `byteplus/seedream-5-0-pro-260628` | 6.7889 | 13.5778 | 9.495 | 18.99 |
-Not every alternate is a single number. Wavespeed prices the GPT image models as a grid of quality against output resolution — flat within each cell, so it is predictable once you know which cell your request lands in:
+#### Image generation and then image edit
-**Text to image** (`wavespeed/wavespeed-gpt-image-2`)
+| Name | Model ID | Image generation Credits / generation | Image edit Credits / generation |
+| --- | --- | ---: | ---: |
+| [Uni 1](/development/comfy-router/models/luma_2/uni-1/code) | `luma_2/uni-1` | 8.5244 | 9.1574 |
+| [Uni 1 Max](/development/comfy-router/models/luma_2/uni-1-max/code) | `luma_2/uni-1-max` | 21.1 | 21.733 |
-| Quality | 1K | 2K | 4K |
-| --- | --- | --- | --- |
-| `low` | $0.01 | $0.02 | $0.03 |
-| `medium` | $0.06 | $0.10 | $0.18 |
-| `high` | $0.22 | $0.40 | $0.72 |
+#### Ideogram image rates
-**Edit**
+| Name | Model ID | Standard Credits / generation | Quality Credits / generation | Turbo Credits / generation |
+| --- | --- | ---: | ---: | ---: |
+| [Ideogram 4.0](/development/comfy-router/models/ideogram/ideogram-v4/code) | `ideogram/ideogram-v4` | 18.1038 | 30.173 | 9.0519 |
+| [Ideogram V3](/development/comfy-router/models/ideogram/ideogram-v3/code) | `ideogram/ideogram-v3` | 18.1038 | 27.1557 | 9.0519 |
+
-| Quality | 1K | 2K | 4K |
-| --- | --- | --- | --- |
-| `low` | $0.02 | $0.03 | $0.04 |
-| `medium` | $0.07 | $0.11 | $0.19 |
-| `high` | $0.23 | $0.41 | $0.73 |
+
-OpenAI's own leg for the same model is token-metered instead, so the two are not comparable cell by cell — pick the row and column your traffic actually uses and compare that against a measured run.
+#### Not published
-The `gpt-image-2.5` models are priced on the same grid shape with two further quality steps (`xhigh` and `max`). Seedance, Kling and Wan video are metered or multi-tier on every provider that serves them. For all of these, compare measured runs on a request shaped like your real traffic rather than against a single published figure — with `X-Comfy-Credits-Used` where the model reports it, and against your workspace's usage records where it does not.
+| Name | Model ID | Credits |
+| --- | --- | ---: |
+| [Crystal Upscaler](/development/comfy-router/models/fal/crystal-upscaler/code) | `fal/crystal-upscaler` | Not published |
-## Choosing a provider on a request
+#### Usage-based rates
-Send the model's own id and Router uses its default provider. To run the same model on an alternate, either call the alternate's own id directly, or keep the model id and add `model_provider`:
+| Name | Model ID | Credits |
+| --- | --- | ---: |
+| [GPT Image 2](/development/comfy-router/models/openai/gpt-image-2/code) | `openai/gpt-image-2` | Usage-based
request usage |
+| [GPT Image 2.5 Flare](/development/comfy-router/models/openai/gpt-image-2-5-flare/code) | `openai/gpt-image-2.5-flare` | Usage-based
request usage |
+| [GPT Image 2.5 Sunburst](/development/comfy-router/models/openai/gpt-image-2-5-sunburst/code) | `openai/gpt-image-2.5-sunburst` | Usage-based
request usage |
-```bash
-curl -X POST \
- -H "Authorization: Bearer $COMFY_API_KEY" \
- -H "Content-Type: application/json" \
- -d '{"contents":[{"role":"user","parts":[{"text":"a red bicycle"}]}]}' \
- "https://api.comfy.org/v2/models/vertexai/gemini-3-pro-image?model_provider=wavespeed&fallback_provider=false"
-```
+#### Image generation and edit rates
-With `model_provider`, Router translates the response back into the model's native shape where it can. Translation is best-effort: if it cannot be applied, the successful response is returned in the alternate provider's own shape rather than withheld. Calling an alternate's own id directly uses that id's own request and response contract. See [Providers](/development/comfy-router/providers) for the full list and for `strict_mode`.
+| Name | Model ID | Credits / generation |
+| --- | --- | ---: |
+| [Nano Banana 2](/development/comfy-router/models/google/nano-banana-2/code) | `vertexai/gemini-3.1-flash-image` | 16.88 |
+| [Nano Banana Pro](/development/comfy-router/models/google/nano-banana-pro/code) | `vertexai/gemini-3-pro-image` | 31.65 |
+| [Patina](/development/comfy-router/models/fal/patina/code) | `fal/patina` | 3.0173 |
+
-### Provider fallback can change the price
+
-Automatic provider fallback is **on by default**: if the first provider fails for a reason that is not the request's own, Router can retry once on another of the model's providers, and that provider's price applies. The response then carries `X-Comfy-Router-Fallback-Provider`, naming the provider that served it. When you pick a provider for its price, add `fallback_provider=false`, as above, so a failure is returned to you rather than served — and charged — elsewhere.
+| Name | Model ID | Credits |
+| --- | --- | ---: |
+| [GPT Image 2](/development/comfy-router/models/openai/gpt-image-2/code) | `openai/gpt-image-2` | Usage-based
request usage |
+| [GPT Image 2.5 Flare](/development/comfy-router/models/openai/gpt-image-2-5-flare/code) | `openai/gpt-image-2.5-flare` | Usage-based
request usage |
+| [GPT Image 2.5 Sunburst](/development/comfy-router/models/openai/gpt-image-2-5-sunburst/code) | `openai/gpt-image-2.5-sunburst` | Usage-based
request usage |
+| [Nano Banana 2](/development/comfy-router/models/google/nano-banana-2/code) | `vertexai/gemini-3.1-flash-image` | Usage-based
request usage |
+| [Nano Banana Pro](/development/comfy-router/models/google/nano-banana-pro/code) | `vertexai/gemini-3-pro-image` | Usage-based
request usage |
+
-## What you are charged for
+
-| Outcome | Charged |
-| --- | --- |
-| Successful generation | Yes |
-| Replayed response for a reused `Idempotency-Key` | Not again — a replay never adds a charge to whatever the original call was charged |
-| Refused by Router before dispatch — `X-Comfy-Error-Type` of `invalid_input`, `insufficient_credits`, `rate_limited`, `concurrency_limit_exceeded`, `unauthorized`, `forbidden` or `not_enabled` | No |
-| Provider refused the prompt on content policy (`content_policy_violation`) | Sometimes — it depends on the model. The error response carries no `X-Comfy-Credits-Used`, so check your workspace's usage records. |
-| Other provider-side failures (`provider_error`, `provider_timeout`, `model_not_found`) | Can be — the request may have reached the provider |
-| Timeout or lost connection | Not determined by the disconnect. Accepted provider work is not cancelled, and may be charged. See [timeouts and collection](/development/comfy-router/errors#timeouts-and-collection). |
+#### Image generation and edit rates
-## Bring your own provider key
+| Name | Model ID | Text to image Credits / generation | Image edit Credits / generation | Image edit · 1K / 2K Credits / generation | Image edit · 4K Credits / generation |
+| --- | --- | ---: | ---: | ---: | ---: |
+| [GPT Image 2](/development/comfy-router/models/openai/gpt-image-2/code) | `openai/gpt-image-2` | 12.66 | 14.77 | - | - |
+| [GPT Image 2.5 Flare](/development/comfy-router/models/openai/gpt-image-2-5-flare/code) | `openai/gpt-image-2.5-flare` | 5.064 | 8.229 | - | - |
+| [GPT Image 2.5 Sunburst](/development/comfy-router/models/openai/gpt-image-2-5-sunburst/code) | `openai/gpt-image-2.5-sunburst` | 5.064 | 8.229 | - | - |
+| [Nano Banana 2](/development/comfy-router/models/google/nano-banana-2/code) | `vertexai/gemini-3.1-flash-image` | 14.77 | 14.77 | - | - |
+| [Nano Banana Pro](/development/comfy-router/models/google/nano-banana-pro/code) | `vertexai/gemini-3-pro-image` | 29.54 | - | 29.54 | 50.64 |
-Saving your own Higgsfield key bills those generations to your Higgsfield account rather than your Comfy credits, through the same endpoint. Those runs carry no `X-Comfy-Credits-Used` header. See [Higgsfield with your own key](/development/comfy-router/higgsfield-byok).
+| Name | Model ID | Credits / generation |
+| --- | --- | ---: |
+| [Seedvr 2](/development/comfy-router/models/wavespeed/seedvr2/code) | `wavespeed/seedvr2` | 2.11 |
+| [Ultimate Image Upscaler](/development/comfy-router/models/wavespeed/ultimate-image-upscaler/code) | `wavespeed/ultimate-image-upscaler` | 12.66 |
+
+#### Image quality and resolution
+
+| Name | Model ID | Operation | Quality | 1K Credits / generation | 2K Credits / generation | 4K Credits / generation |
+| --- | --- | --- | --- | ---: | ---: | ---: |
+| [GPT Image 2](/development/comfy-router/models/openai/gpt-image-2/code) | `openai/gpt-image-2` | Text to image | Low | 2.11 | 4.22 | 6.33 |
+| | | | Medium | 12.66 | 21.1 | 37.98 |
+| | | | High | 46.42 | 84.4 | 151.92 |
+| | | Image edit | Low | 4.22 | 6.33 | 8.44 |
+| | | | Medium | 14.77 | 23.21 | 40.09 |
+| | | | High | 48.53 | 86.51 | 154.03 |
+| [GPT Image 2.5 Flare](/development/comfy-router/models/openai/gpt-image-2-5-flare/code) | `openai/gpt-image-2.5-flare` | Text to image | Low | 2.11 | 4.22 | 6.33 |
+| | | | Medium | 5.064 | 8.44 | 14.77 |
+| | | | High | 18.99 | 31.65 | 56.97 |
+| | | | Extra high | 33.76 | 56.97 | 101.28 |
+| | | | Max | 75.96 | 126.6 | 211 |
+| | | Image edit | Low | 5.275 | 7.385 | 9.495 |
+| | | | Medium | 8.229 | 11.605 | 17.935 |
+| | | | High | 22.155 | 34.815 | 60.135 |
+| | | | Extra high | 36.925 | 60.135 | 104.445 |
+| | | | Max | 79.125 | 129.765 | 214.165 |
+| [GPT Image 2.5 Sunburst](/development/comfy-router/models/openai/gpt-image-2-5-sunburst/code) | `openai/gpt-image-2.5-sunburst` | Text to image | Low | 2.11 | 4.22 | 6.33 |
+| | | | Medium | 5.064 | 8.44 | 14.77 |
+| | | | High | 18.99 | 31.65 | 56.97 |
+| | | | Extra high | 33.76 | 56.97 | 101.28 |
+| | | | Max | 75.96 | 126.6 | 211 |
+| | | Image edit | Low | 5.275 | 7.385 | 9.495 |
+| | | | Medium | 8.229 | 11.605 | 17.935 |
+| | | | High | 22.155 | 34.815 | 60.135 |
+| | | | Extra high | 36.925 | 60.135 | 104.445 |
+| | | | Max | 79.125 | 129.765 | 214.165 |
+
+
+
+
+| Name | Model ID | Credits |
+| --- | --- | ---: |
+| [GPT Image 2](/development/comfy-router/models/openai/gpt-image-2/code) | `openai/gpt-image-2` | Not published |
+| [Nano Banana 2](/development/comfy-router/models/google/nano-banana-2/code) | `vertexai/gemini-3.1-flash-image` | Not published |
+| [Nano Banana Pro](/development/comfy-router/models/google/nano-banana-pro/code) | `vertexai/gemini-3-pro-image` | Not published |
+
+
+
+
+
+
+
+
+
+
+
+
+Video rates apply to output plus reference-video duration.
+
+#### Per 1M tokens by resolution
+
+| Name | Model ID | Input type | 480p Credits | 720p Credits | 1080p Credits | 4K Credits |
+| --- | --- | --- | ---: | ---: | ---: | ---: |
+| [Dreamina Seedance 2.0](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | `byteplus/dreamina-seedance-2-0-260128` | Image to video | 2112.11 / 1M tokens | 2112.11 / 1M tokens | 2323.321 / 1M tokens | 1206.92 / 1M tokens |
+| | | Text to video | 2112.11 / 1M tokens | 2112.11 / 1M tokens | 2323.321 / 1M tokens | 1206.92 / 1M tokens |
+| | | Video to video | 1297.439 / 1M tokens | 1297.439 / 1M tokens | 1418.131 / 1M tokens | 724.152 / 1M tokens |
+| [Dreamina Seedance 2.5](/development/comfy-router/models/byteplus/dreamina-seedance-2-5-260628/code) | `byteplus/dreamina-seedance-2-5-260628` | Image to video | 3228.511 / 1M tokens | 3228.511 / 1M tokens | 3530.241 / 1M tokens | - |
+| | | Text to video | 3228.511 / 1M tokens | 3228.511 / 1M tokens | 3530.241 / 1M tokens | - |
+| | | Video to video | 1931.072 / 1M tokens | 1931.072 / 1M tokens | 2112.11 / 1M tokens | - |
+
+#### Per second by resolution
+
+| Name | Model ID | 480p Credits | 720p Credits | 1080p Credits | 1440p Credits | 4K Credits |
+| --- | --- | ---: | ---: | ---: | ---: | ---: |
+| [HappyHorse 1.0 I2V](/development/comfy-router/models/wan/happyhorse-1-0-i2v/code) | `wan/happyhorse-1.0-i2v` | - | 29.54 / s | 50.64 / s | - | - |
+| [HappyHorse 1.0 R2V](/development/comfy-router/models/wan/happyhorse-1-0-r2v/code) | `wan/happyhorse-1.0-r2v` | - | 29.54 / s | 50.64 / s | - | - |
+| [HappyHorse 1.0 T2V](/development/comfy-router/models/wan/happyhorse-1-0-t2v/code) | `wan/happyhorse-1.0-t2v` | - | 29.54 / s | 50.64 / s | - | - |
+| [HappyHorse 1.0 Video Edit](/development/comfy-router/models/wan/happyhorse-1-0-video-edit/code) | `wan/happyhorse-1.0-video-edit` | - | 29.54 / s | 50.64 / s | - | - |
+| [HappyHorse 1.1 I2V](/development/comfy-router/models/wan/happyhorse-1-1-i2v/code) | `wan/happyhorse-1.1-i2v` | - | 42.2422 / s | 54.3114 / s | - | - |
+| [HappyHorse 1.1 R2V](/development/comfy-router/models/wan/happyhorse-1-1-r2v/code) | `wan/happyhorse-1.1-r2v` | - | 42.2422 / s | 54.3114 / s | - | - |
+| [HappyHorse 1.1 T2V](/development/comfy-router/models/wan/happyhorse-1-1-t2v/code) | `wan/happyhorse-1.1-t2v` | - | 42.2422 / s | 54.3114 / s | - | - |
+| [LTX 2.5 Fast](/development/comfy-router/models/ltx/ltx-2-5-fast/code) | `ltx/ltx-2-5-fast` | - | 27.1557 / s | 39.2249 / s | 57.3287 / s | 90.519 / s |
+| [LTX 2.5 Pro](/development/comfy-router/models/ltx/ltx-2-5-pro/code) | `ltx/ltx-2-5-pro` | - | 36.2076 / s | 51.2941 / s | - | - |
+| [P Video 2 Standard](/development/comfy-router/models/pruna/p-video-2/code) | `pruna/p-video-2` | - | 7.5432 / s | 15.0865 / s | - | - |
+| [P Video 2 Draft](/development/comfy-router/models/pruna/p-video-2/code) | `pruna/p-video-2` | - | 4.5259 / s | 9.0519 / s | - | - |
+| [Wan 2.5 I2V Preview](/development/comfy-router/models/wan/wan2-5-i2v-preview/code) | `wan/wan2.5-i2v-preview` | 10.55 / s | 21.1 / s | 31.65 / s | - | - |
+| [Wan 2.5 T2V Preview](/development/comfy-router/models/wan/wan2-5-t2v-preview/code) | `wan/wan2.5-t2v-preview` | 10.55 / s | 21.1 / s | 31.65 / s | - | - |
+| [Wan 2.6 I2V](/development/comfy-router/models/wan/wan2-6-i2v/code) | `wan/wan2.6-i2v` | - | 21.1 / s | 31.65 / s | - | - |
+| [Wan 2.6 R2V](/development/comfy-router/models/wan/wan2-6-r2v/code) | `wan/wan2.6-r2v` | - | 21.1 / s | 31.65 / s | - | - |
+| [Wan 2.6 T2V](/development/comfy-router/models/wan/wan2-6-t2v/code) | `wan/wan2.6-t2v` | - | 21.1 / s | 31.65 / s | - | - |
+| [Wan 2.7 I2V](/development/comfy-router/models/wan/wan2-7-i2v/code) | `wan/wan2.7-i2v` | - | 21.1 / s | 31.65 / s | - | - |
+| [Wan 2.7 R2V](/development/comfy-router/models/wan/wan2-7-r2v/code) | `wan/wan2.7-r2v` | - | 21.1 / s | 31.65 / s | - | - |
+| [Wan 2.7 T2V](/development/comfy-router/models/wan/wan2-7-t2v/code) | `wan/wan2.7-t2v` | - | 21.1 / s | 31.65 / s | - | - |
+| [Wan 2.7 Video Edit](/development/comfy-router/models/wan/wan2-7-videoedit/code) | `wan/wan2.7-videoedit` | - | 21.1 / s | 31.65 / s | - | - |
+| [Wan 3.0 Video](/development/comfy-router/models/wan/wan3-0-video/code) | `wan/wan3.0-video` | 10.55 / s | 21.1 / s | 42.2 / s | - | - |
+| [Wan 3.0 Video Prime](/development/comfy-router/models/wan/wan3-0-video-prime/code) | `wan/wan3.0-video-prime` | 14.348 / s | 29.54 / s | 59.08 / s | - | - |
+
+| Name | Model ID | 768p Credits | 2K Credits |
+| --- | --- | ---: | ---: |
+| [MiniMax H3](/development/comfy-router/models/minimax/minimax-h3/code) | `minimax/minimax-h3` | 27.1557 / s | 39.2249 / s |
+
+| Name | Model ID | Audio | 720p Credits | 1080p Credits | 4K Credits |
+| --- | --- | --- | ---: | ---: | ---: |
+| [Veo 3.1 Fast Generate 001](/development/comfy-router/models/veo/veo-3-1-fast-generate-001/code) | `veo/veo-3.1-fast-generate-001` | No audio | 16.88 / s | 21.1 / s | 52.75 / s |
+| | | Audio | 21.1 / s | 25.32 / s | 63.3 / s |
+| [Veo 3.1 Generate 001](/development/comfy-router/models/veo/veo-3-1-generate-001/code) | `veo/veo-3.1-generate-001` | No audio | 42.2 / s | 42.2 / s | 84.4 / s |
+| | | Audio | 84.4 / s | 84.4 / s | 126.6 / s |
+| [Veo 3.1 Lite Generate 001](/development/comfy-router/models/veo/veo-3-1-lite-generate-001/code) | `veo/veo-3.1-lite-generate-001` | No audio | 6.33 / s | 10.55 / s | - |
+| | | Audio | 10.55 / s | 16.88 / s | - |
+
+#### SwitchX image and video rates
+
+Images are billed per output image. Videos are billed per 30 output frames, rounded up.
+
+| Name | Model ID | 720p Credits | 1080p Credits |
+| --- | --- | ---: | ---: |
+| [SwitchX](/development/comfy-router/models/beeble/switchx/code) | `beeble/switchx` | Image: 30.173
Video: 30.173 | Image: 90.519
Video: 90.519 |
+
+#### Rates per second
+
+| Name | Model ID | Credits |
+| --- | --- | ---: |
+| [Aleph 2](/development/comfy-router/models/runway/aleph2/code) | `runway/aleph2` | 84.4844 / s |
+| [Gen 4 Turbo](/development/comfy-router/models/runway/gen4-turbo/code) | `runway/gen4_turbo` | 15.0865 / s |
+| [Sync 3](/development/comfy-router/models/synclabs/sync-3/code) | `synclabs/sync-3` | 40.1301 / s |
+| [Erase](/development/comfy-router/models/bria/video-edit-erase/code) | `bria/video-edit-erase` | 13.5778 / s |
+| [Green Screen](/development/comfy-router/models/bria/video-edit-green-screen/code) | `bria/video-edit-green-screen` | 10.55 / s |
+| [Remove Background](/development/comfy-router/models/bria/video-edit-remove-background/code) | `bria/video-edit-remove-background` | 10.55 / s |
+| [Replace Background](/development/comfy-router/models/bria/video-edit-replace-background/code) | `bria/video-edit-replace-background` | 10.55 / s |
+
+#### Usage-based rates
+
+Comfy usage-based rates are billed per active GPU-second; total cost depends on runtime.
+
+| Name | Model ID | Credits |
+| --- | --- | ---: |
+| [FLUX 3 Video](/development/comfy-router/models/black-forest-labs/flux-3-video/code) | `bfl/flux-3-video` | 0.266 / GPU-second |
+| [FLUX Video Upscale](/development/comfy-router/models/black-forest-labs/flux-video-upscale/code) | `bfl/video-upscale-v1` | 0.266 / GPU-second |
+| [Grok Imagine Video](/development/comfy-router/models/xai/grok-imagine-video/code) | `xai/grok-imagine-video` | 0.266 / GPU-second |
+| [Grok Imagine Video 1.5](/development/comfy-router/models/xai/grok-imagine-video-1-5/code) | `xai/grok-imagine-video-1.5` | 0.266 / GPU-second |
+| [Grok Imagine Video 1.5 Preview](/development/comfy-router/models/xai/grok-imagine-video-1-5-preview/code) | `xai/grok-imagine-video-1.5-preview` | 0.266 / GPU-second |
+| [Ray 2](/development/comfy-router/models/luma/ray-2/code) | `luma/ray-2` | 0.266 / GPU-second |
+| [Ray Flash 2](/development/comfy-router/models/luma/ray-flash-2/code) | `luma/ray-flash-2` | 0.266 / GPU-second |
+| [Video Edit V1](/development/comfy-router/models/black-forest-labs/video-edit-v1/code) | `bfl/video-edit-v1` | 0.266 / GPU-second |
+
+#### Not published
+
+| Name | Model ID | Credits |
+| --- | --- | ---: |
+| [Grok Imagine Video 1.5 Lite](/development/comfy-router/models/xai/grok-imagine-video-1-5-lite/code) | `xai/grok-imagine-video-1.5-lite` | Not published |
+| [Pika 2.5 Image To Video](/development/comfy-router/models/pika/pika-2-5-image-to-video/code) | `pika/pika-2.5-image-to-video` | Not published |
+| [Pika 2.5 Text To Video](/development/comfy-router/models/pika/pika-2-5-text-to-video/code) | `pika/pika-2.5-text-to-video` | Not published |
+| [Video To Video Resize](/development/comfy-router/models/moonvalley/video-to-video-resize/code) | `moonvalley/video-to-video-resize` | Not published |
+
+#### Rates per Kling credit
+
+| Name | Model ID | Credits |
+| --- | --- | ---: |
+| [Kling 3.0 Turbo](/development/comfy-router/models/kling/kling-3-0-turbo/code) | `kling/kling-3.0-turbo` | 29.54 / Kling credit |
+| [Kling V2.5 Turbo](/development/comfy-router/models/kling/kling-v2-5-turbo/code) | `kling/kling-v2-5-turbo` | 29.54 / Kling credit |
+| [Kling V2.6](/development/comfy-router/models/kling/kling-v2-6/code) | `kling/kling-v2-6` | 29.54 / Kling credit |
+| [Kling V3](/development/comfy-router/models/kling/kling-v3/code) | `kling/kling-v3` | 29.54 / Kling credit |
+| [Kling V3 Omni](/development/comfy-router/models/kling/kling-v3-omni/code) | `kling/kling-v3-omni` | 29.54 / Kling credit |
+| [Kling Video O1](/development/comfy-router/models/kling/kling-video-o1/code) | `kling/kling-video-o1` | 29.54 / Kling credit |
+| [Videos Avatar Image 2video](/development/comfy-router/models/kling/videos-avatar-image2video/code) | `kling/videos-avatar-image2video` | 29.54 / Kling credit |
+| [Videos Lip Sync](/development/comfy-router/models/kling/videos-lip-sync/code) | `kling/videos-lip-sync` | 29.54 / Kling credit |
+| [Videos Video Extend](/development/comfy-router/models/kling/videos-video-extend/code) | `kling/videos-video-extend` | 29.54 / Kling credit |
+
+#### Rates per extra reference image
+
+| Name | Model ID | Option | Credits |
+| --- | --- | --- | ---: |
+| [MiniMax H3](/development/comfy-router/models/minimax/minimax-h3/code) | `minimax/minimax-h3` | References beyond 5 | 12.0692 / extra reference image |
+
+#### Rates per request
+
+| Name | Model ID | 5s Credits / request | 10s Credits / request |
+| --- | --- | ---: | ---: |
+| [Image To Video](/development/comfy-router/models/moonvalley/image-to-video/code) | `moonvalley/image-to-video` | 316.5 | 633 |
+| [Text To Video](/development/comfy-router/models/moonvalley/text-to-video/code) | `moonvalley/text-to-video` | 316.5 | 633 |
+| [Video To Video](/development/comfy-router/models/moonvalley/video-to-video/code) | `moonvalley/video-to-video` | 474.75 | 844 |
+
+#### Seedance video token rates
+
+| Name | Model ID | Image to video / Text to video Credits / 1M tokens | Video to video Credits / 1M tokens |
+| --- | --- | ---: | ---: |
+| [Dreamina Seedance 2.0 Fast](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-fast-260128/code) | `byteplus/dreamina-seedance-2-0-fast-260128` | 1689.688 | 995.709 |
+| [Dreamina Seedance 2.0 Mini](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-mini/code) | `byteplus/dreamina-seedance-2-0-mini` | 1056.055 | 633.633 |
+| [Seedance 1.0 Pro](/development/comfy-router/models/byteplus/seedance-1-0-pro-250528/code) | `byteplus/seedance-1-0-pro-250528` | 527.5 | - |
+| [Seedance 1.0 Pro Fast](/development/comfy-router/models/byteplus/seedance-1-0-pro-fast-251015/code) | `byteplus/seedance-1-0-pro-fast-251015` | 211 | - |
+
+#### Seedance 1.5 Pro audio rates
+
+| Name | Model ID | No audio Credits / 1M tokens | Audio Credits / 1M tokens |
+| --- | --- | ---: | ---: |
+| [Seedance 1.5 Pro](/development/comfy-router/models/byteplus/seedance-1-5-pro-251215/code) | `byteplus/seedance-1-5-pro-251215` | 253.2 | 506.4 |
+
+
+
+
+#### Per 1K video tokens by resolution
+
+| Name | Model ID | 480p Credits | 720p Credits | 1080p Credits | 4K Credits |
+| --- | --- | ---: | ---: | ---: | ---: |
+| [Dreamina Seedance 2.0](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | `byteplus/dreamina-seedance-2-0-260128` | 2.954 | 2.954 | 2.954 | 1.688 |
+| [Dreamina Seedance 2.5](/development/comfy-router/models/byteplus/dreamina-seedance-2-5-260628/code) | `byteplus/dreamina-seedance-2-5-260628` | 4.5154 | 4.5154 | 4.9374 | - |
+
+#### Rates per generation
+
+| Name | Model ID | Credits / generation |
+| --- | --- | ---: |
+| [H3 Max](/development/comfy-router/models/fal/h3-max/code) | `fal/h3-max` | 15.0865 |
+| [H3 Max Turbo](/development/comfy-router/models/fal/h3-max-turbo/code) | `fal/h3-max-turbo` | 7.5432 |
+
+
+
+
+#### Per 1K video tokens by resolution
+
+| Name | Model ID | 480p Credits | 720p Credits |
+| --- | --- | ---: | ---: |
+| [Dreamina Seedance 2.0](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | `byteplus/dreamina-seedance-2-0-260128` | 2.954 | 2.954 |
+| [Dreamina Seedance 2.5](/development/comfy-router/models/byteplus/dreamina-seedance-2-5-260628/code) | `byteplus/dreamina-seedance-2-5-260628` | 4.5154 | 4.5154 |
+
+#### Per second by resolution
+
+| Name | Model ID | 480p Credits | 720p Credits | 1080p Credits | 4K Credits |
+| --- | --- | ---: | ---: | ---: | ---: |
+| [Kling 3.0 Turbo](/development/comfy-router/models/kling/kling-3-0-turbo/code) | `kling/kling-3.0-turbo` | - | 23.632 / s | 29.54 / s | - |
+| [Wan 3.0 Video](/development/comfy-router/models/wan/wan3-0-video/code) | `wan/wan3.0-video` | 10.55 / s | 21.1 / s | 42.2 / s | - |
+| [Higgsfield Kling 3.4k](/development/comfy-router/models/higgsfield/higgsfield-kling-3-4k/code) | `higgsfield/higgsfield-kling-3-4k` | - | - | - | 88.62 / s |
+
+#### Rates per second
+
+| Name | Model ID | Credits |
+| --- | --- | ---: |
+| [Higgsfield Kling 3 Pro](/development/comfy-router/models/higgsfield/higgsfield-kling-3-pro/code) | `higgsfield/higgsfield-kling-3-pro` | 35.448 / s |
+| [Kling V3 Standard](/development/comfy-router/models/kling/kling-v3/code) | `kling/kling-v3` | 26.6282 / s |
+
+
+
+
+| Name | Model ID | Credits |
+| --- | --- | ---: |
+| [Dreamina Seedance 2.0](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | `byteplus/dreamina-seedance-2-0-260128` | Usage-based
request usage |
+| [Dreamina Seedance 2.5](/development/comfy-router/models/byteplus/dreamina-seedance-2-5-260628/code) | `byteplus/dreamina-seedance-2-5-260628` | Usage-based
request usage |
+
+
+
+
+#### Rates per request
+
+| Name | Model ID | Credits / request |
+| --- | --- | ---: |
+| [Dreamina Seedance 2.0](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | `byteplus/dreamina-seedance-2-0-260128` | 126.6 |
+| [Dreamina Seedance 2.5](/development/comfy-router/models/byteplus/dreamina-seedance-2-5-260628/code) | `byteplus/dreamina-seedance-2-5-260628` | 189.9 |
+
+#### Per 5 s by resolution
+
+| Name | Model ID | 720p Credits | 1080p Credits | 2K Credits | 4K Credits |
+| --- | --- | ---: | ---: | ---: | ---: |
+| [Flashvsr](/development/comfy-router/models/wavespeed/flashvsr/code) | `wavespeed/flashvsr` | 12.66 / 5 s | 18.99 / 5 s | 25.32 / 5 s | 33.76 / 5 s |
+
+
+
+
+| Name | Model ID | Credits |
+| --- | --- | ---: |
+| [Dreamina Seedance 2.0](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | `byteplus/dreamina-seedance-2-0-260128` | Not published |
+
+
+
+
+
+
+
+
+
+
+
+
+#### Token rates
+
+| Name | Model ID | Cached input credits / 1M tokens | Input credits / 1M tokens | Output credits / 1M tokens |
+| --- | --- | ---: | ---: | ---: |
+| [Claude Fable 5.1](/development/comfy-router/models/anthropic/claude-fable-5-1/code) | `anthropic/claude-fable-5-1` | Read: 75.4325
Write (1h): 6034.6
Write (5m): 3771.625 | Text: 3017.3 | Text: 15086.5 |
+| [Claude Fable 5](/development/comfy-router/models/anthropic/claude-fable-5/code) | `anthropic/claude-fable-5` | Read: 301.73
Write (1h): 6034.6
Write (5m): 3771.625 | Text: 3017.3 | Text: 15086.5 |
+| [Claude Haiku 4.5](/development/comfy-router/models/anthropic/claude-haiku-4-5-20251001/code) | `anthropic/claude-haiku-4-5-20251001` | Read: 21.1
Write (1h): 422
Write (5m): 263.75 | Text: 211 | Text: 1055 |
+| [Claude Opus 5.5](/development/comfy-router/models/anthropic/claude-opus-5-5/code) | `anthropic/claude-opus-5-5` | Read: 60.346
Write (1h): 2413.84
Write (5m): 1508.65 | Text: 1206.92 | Text: 6034.6 |
+| [Claude Opus 5](/development/comfy-router/models/anthropic/claude-opus-5/code) | `anthropic/claude-opus-5` | Read: 150.865
Write (1h): 3017.3
Write (5m): 1885.8125 | Text: 1508.65 | Text: 7543.25 |
+| [Claude Opus 4.8](/development/comfy-router/models/anthropic/claude-opus-4-8/code) | `anthropic/claude-opus-4-8` | Read: 150.865
Write (1h): 3017.3
Write (5m): 1885.8125 | Text: 1508.65 | Text: 7543.25 |
+| [Claude Opus 4.7](/development/comfy-router/models/anthropic/claude-opus-4-7/code) | `anthropic/claude-opus-4-7` | Read: 105.5
Write (1h): 2110
Write (5m): 1318.75 | Text: 1055 | Text: 5275 |
+| [Claude Opus 4.6](/development/comfy-router/models/anthropic/claude-opus-4-6/code) | `anthropic/claude-opus-4-6` | Read: 105.5
Write (1h): 2110
Write (5m): 1318.75 | Text: 1055 | Text: 5275 |
+| [Claude Sonnet 5.5](/development/comfy-router/models/anthropic/claude-sonnet-5-5/code) | `anthropic/claude-sonnet-5-5` | Read: 60.346
Write (1h): 1206.92
Write (5m): 754.325 | Text: 603.46 | Text: 3017.3 |
+| [Claude Sonnet 5](/development/comfy-router/models/anthropic/claude-sonnet-5/code) | `anthropic/claude-sonnet-5` | Read: 60.346
Write (1h): 1206.92
Write (5m): 754.325 | Text: 603.46 | Text: 3017.3 |
+| [Claude Sonnet 4.6](/development/comfy-router/models/anthropic/claude-sonnet-4-6/code) | `anthropic/claude-sonnet-4-6` | Read: 63.3
Write (1h): 1266
Write (5m): 791.25 | Text: 633 | Text: 3165 |
+| [Claude Sonnet 4.5](/development/comfy-router/models/anthropic/claude-sonnet-4-5-20250929/code) | `anthropic/claude-sonnet-4-5-20250929` | Read: 63.3
Write (1h): 1266
Write (5m): 791.25 | Text: 633 | Text: 3165 |
+| [Gemini 3.8 Flash](/development/comfy-router/models/google/gemini-3-8-flash/code) | `vertexai/gemini-3.8-flash` | - | Audio / Image / Text / Video: 452.595 | Text / Reasoning: 2262.975 |
+| [Gemini 3.7 Flash](/development/comfy-router/models/google/gemini-3-7-flash/code) | `vertexai/gemini-3.7-flash` | - | Audio / Image / Text / Video: 452.595 | Text / Reasoning: 2262.975 |
+| [Gemini 3.1 Flash Lite](/development/comfy-router/models/google/gemini-3-1-flash-lite/code) | `vertexai/gemini-3.1-flash-lite` | - | Image / Text / Video: 52.75
Audio: 105.5 | Text / Reasoning: 316.5 |
+| [Gemini Omni 1.1 Flash](/development/comfy-router/models/gemini-interactions/gemini-omni-1-1-flash/code) | `gemini-interactions/gemini-omni-1.1-flash` | - | Audio / Image / Text / Video: 452.595 | Text / Reasoning: 2715.57
Video: 5280.275 |
+| [Gemini Omni Flash Preview](/development/comfy-router/models/gemini-interactions/gemini-omni-flash-preview/code) | `gemini-interactions/gemini-omni-flash-preview` | - | Audio / Image / Text / Video: 452.595 | Text / Reasoning: 2715.57
Video: 5280.275 |
+| [Google Gemini](/development/comfy-router/models/google/gemini/code) | `vertexai/gemini-2.5-flash` | - | Image / Text / Video: 63.3
Audio: 211 | Text: 527.5 |
+| [Google Gemini](/development/comfy-router/models/google/gemini/code) | `vertexai/gemini-2.5-pro` | - | Audio / Image / Text / Video: 263.75 | Text: 2110 |
+| [Google Gemini](/development/comfy-router/models/google/gemini/code) | `vertexai/gemini-3.1-pro-preview` | - | Audio / Image / Text / Video: 422 | Text / Reasoning: 2532
Image: 25320 |
+| [Google Gemini](/development/comfy-router/models/google/gemini/code) | `vertexai/gemini-3.5-flash` | - | Audio / Image / Text / Video: 316.5 | Text / Reasoning: 1899 |
+| [GPT 6 Astra](/development/comfy-router/models/openai/gpt-6-astra/code) | `openai/gpt-6-astra` | Read: 301.73
Write: 3771.625 | Text: 3017.3 | Text: 15086.5 |
+| [GPT 6 Luna](/development/comfy-router/models/openai/gpt-6-luna/code) | `openai/gpt-6-luna` | Read: 3.0173
Write: 37.7162 | Text: 30.173 | Text: 150.865 |
+| [GPT 6 Sol](/development/comfy-router/models/openai/gpt-6-sol/code) | `openai/gpt-6-sol` | Read: 60.346
Write: 754.325 | Text: 603.46 | Text: 3017.3 |
+| [GPT 5.6 Luna](/development/comfy-router/models/openai/gpt-5-6-luna/code) | `openai/gpt-5.6-luna` | Read: 21.1
Write: 263.75 | Text: 211 | Text: 1266 |
+| [GPT 5.6 Sol](/development/comfy-router/models/openai/gpt-5-6-sol/code) | `openai/gpt-5.6-sol` | Read: 105.5
Write: 1318.75 | Text: 1055 | Text: 6330 |
+| [GPT 5.6 Terra](/development/comfy-router/models/openai/gpt-5-6-terra/code) | `openai/gpt-5.6-terra` | Read: 52.75
Write: 659.375 | Text: 527.5 | Text: 3165 |
+| [GPT 5.5](/development/comfy-router/models/openai/gpt-5-5/code) | `openai/gpt-5.5` | 105.5 | Text: 1055 | Text: 6330 |
+| [GPT 5.5 Pro](/development/comfy-router/models/openai/gpt-5-5-pro/code) | `openai/gpt-5.5-pro` | - | Text: 6330 | Text: 37980 |
+| [GPT 5](/development/comfy-router/models/openai/gpt-5/code) | `openai/gpt-5` | 26.375 | Text: 263.75 | Text: 2110 |
+| [GPT 5 Mini](/development/comfy-router/models/openai/gpt-5-mini/code) | `openai/gpt-5-mini` | 5.275 | Text: 52.75 | Text: 422 |
+| [GPT 5 Nano](/development/comfy-router/models/openai/gpt-5-nano/code) | `openai/gpt-5-nano` | 1.055 | Text: 10.55 | Text: 84.4 |
+| [GPT 4.1](/development/comfy-router/models/openai/gpt-4-1/code) | `openai/gpt-4.1` | 105.5 | Text: 422 | Text: 1688 |
+| [GPT 4.1 Mini](/development/comfy-router/models/openai/gpt-4-1-mini/code) | `openai/gpt-4.1-mini` | 21.1 | Text: 84.4 | Text: 337.6 |
+| [GPT 4.1 Nano](/development/comfy-router/models/openai/gpt-4-1-nano/code) | `openai/gpt-4.1-nano` | 5.275 | Text: 21.1 | Text: 84.4 |
+| [GPT 4o](/development/comfy-router/models/openai/gpt-4o/code) | `openai/gpt-4o` | 263.75 | Text: 527.5 | Text: 2110 |
+| [o4-mini](/development/comfy-router/models/openai/o4-mini/code) | `openai/o4-mini` | 58.025 | Text: 232.1 | Text: 928.4 |
+| [o3](/development/comfy-router/models/openai/o3/code) | `openai/o3` | 105.5 | Text: 422 | Text: 1688 |
+| [o1](/development/comfy-router/models/openai/o1/code) | `openai/o1` | 1582.5 | Text: 3165 | Text: 12660 |
+| [o1-pro](/development/comfy-router/models/openai/o1-pro/code) | `openai/o1-pro` | - | Text: 31650 | Text: 126600 |
+| [Seed 2.0 Lite](/development/comfy-router/models/byteplus/seed-2-0-lite-260228/code) | `byteplus/seed-2-0-lite-260228` | 10.55 | Text: 52.75 | Text: 422 |
+| [Seed 2.0 Mini](/development/comfy-router/models/byteplus/seed-2-0-mini-260215/code) | `byteplus/seed-2-0-mini-260215` | 4.22 | Text: 21.1 | Text: 84.4 |
+| [Seed 2.0 Pro](/development/comfy-router/models/byteplus/seed-2-0-pro-260328/code) | `byteplus/seed-2-0-pro-260328` | 21.1 | Text: 105.5 | Text: 633 |
+
+#### Usage-based rates
+
+Comfy usage-based rates are billed per active GPU-second; total cost depends on runtime.
+
+| Name | Model ID | Credits |
+| --- | --- | ---: |
+| [Chat Completions](/development/comfy-router/models/openrouter/chat-completions/code) | `openrouter/chat-completions` | 0.266 / GPU-second |
+
+#### Not published
+
+| Name | Model ID | Credits |
+| --- | --- | ---: |
+| [Structured Instruction](/development/comfy-router/models/bria/structured-instruction/code) | `bria/structured-instruction` | Not published |
+
+
+
+
+
+
+
+
+
+
+
+
+#### Rates by duration
+
+| Name | Model ID | Credits |
+| --- | --- | ---: |
+| [Eleven Sfx V2](/development/comfy-router/models/elevenlabs/eleven-sfx-v2/code) | `elevenlabs/eleven_sfx_v2` | 29.54 / minute |
+| [Seed Audio 1.0](/development/comfy-router/models/byteplus/seed-audio-1-0/code) | `byteplus/seed-audio-1.0` | 0.7543 / s |
+| [Seed Audio 1.0 Multilingual](/development/comfy-router/models/byteplus/seed-audio-1-0-multilingual/code) | `byteplus/seed-audio-1.0-multilingual` | 0.7543 / s |
+| [Starfish](/development/comfy-router/models/heygen/starfish/code) | `heygen/starfish` | 0.2013 / s |
+
+#### Rates per 1K characters
+
+| Name | Model ID | Option | Credits |
+| --- | --- | --- | ---: |
+| [Eleven V3](/development/comfy-router/models/elevenlabs/eleven-v3/code) | `elevenlabs/eleven_v3` | Dialogue / Text to speech | 21.1 / 1K characters |
+
+#### Not published
+
+| Name | Model ID | Credits |
+| --- | --- | ---: |
+| [Eleven V4](/development/comfy-router/models/elevenlabs/eleven-v4/code) | `elevenlabs/eleven_v4` | Not published |
+| [Eleven V4 Turbo](/development/comfy-router/models/elevenlabs/eleven-v4-turbo/code) | `elevenlabs/eleven_v4_turbo` | Not published |
+
+
+
+
+
+
+
+
+
+
+
+
+#### Rates per Meshy credit
+
+| Name | Model ID | Credits |
+| --- | --- | ---: |
+| [Animations](/development/comfy-router/models/meshy/animations/code) | `meshy/animations` | 12.0692 / Meshy credit |
+| [Meshy 5](/development/comfy-router/models/meshy/meshy-5/code) | `meshy/meshy-5` | 12.0692 / Meshy credit |
+| [Meshy 6](/development/comfy-router/models/meshy/meshy-6/code) | `meshy/meshy-6` | 12.0692 / Meshy credit |
+| [Meshy 7](/development/comfy-router/models/meshy/meshy-7/code) | `meshy/meshy-7` | 12.0692 / Meshy credit |
+| [Meshy 7.1](/development/comfy-router/models/meshy/meshy-7-1/code) | `meshy/meshy-7.1` | 12.0692 / Meshy credit |
+| [Remesh](/development/comfy-router/models/meshy/remesh/code) | `meshy/remesh` | 12.0692 / Meshy credit |
+| [Rigging](/development/comfy-router/models/meshy/rigging/code) | `meshy/rigging` | 12.0692 / Meshy credit |
+
+#### Rates per operation
+
+| Name | Model ID | Credits |
+| --- | --- | ---: |
+| [Hunyuan 3D Part](/development/comfy-router/models/tencent/hunyuan-3d-part/code) | `tencent/hunyuan-3d-part` | 126.6 / operation |
+| [Hunyuan 3D Texture Edit](/development/comfy-router/models/tencent/hunyuan-3d-texture-edit/code) | `tencent/hunyuan-3d-texture-edit` | 126.6 / operation |
+| [Hunyuan 3D Uv](/development/comfy-router/models/tencent/hunyuan-3d-uv/code) | `tencent/hunyuan-3d-uv` | 42.2 / operation |
+
+#### Usage-based rates
+
+Comfy usage-based rates are billed per active GPU-second; total cost depends on runtime.
+
+| Name | Model ID | Credits |
+| --- | --- | ---: |
+| [Hunyuan 3D Smart Topology](/development/comfy-router/models/tencent/hunyuan-3d-smart-topology/code) | `tencent/hunyuan-3d-smart-topology` | 0.266 / GPU-second |
+
+
+
+
+
+
+
+
+
+
+
+
+
+#### Token rates
+
+| Name | Model ID | Input USD / 1M tokens | Output USD / 1M tokens |
+| --- | --- | ---: | ---: |
+| [Gemini 2.5 Flash Image](/development/comfy-router/models/google/gemini-2-5-flash-image/code) | `vertexai/gemini-2.5-flash-image` | Image / Text / Video: $0.30
Audio: $1.00 | Text: $2.50
Image: $30.00 |
+| [Gemini Nano Banana 2.1](/development/comfy-router/models/google/gemini-nano-banana-2-1/code) | `vertexai/gemini-nano-banana-2.1` | Image / Text / Video: $2.145 | Text / Reasoning: $10.725
Image: $42.90 |
+| [GPT Image 2.5 Flare](/development/comfy-router/models/openai/gpt-image-2-5-flare/code) | `openai/gpt-image-2.5-flare` | Text: $7.15
Image: $11.44 | Text: $14.30
Image: $42.90 |
+| [GPT Image 2.5 Sunburst](/development/comfy-router/models/openai/gpt-image-2-5-sunburst/code) | `openai/gpt-image-2.5-sunburst` | Text: $7.15
Image: $11.44 | Text: $14.30
Image: $42.90 |
+| [GPT Image 1.5](/development/comfy-router/models/openai/gpt-image-1-5/code) | `openai/gpt-image-1.5` | Text: $5.00
Image: $8.00 | Text: $10.00
Image: $32.00 |
+| [GPT Image 1](/development/comfy-router/models/openai/gpt-image-1/code) | `openai/gpt-image-1` | Text: $5.00
Image: $10.00 | Image: $40.00 |
+| [Nano Banana 2](/development/comfy-router/models/google/nano-banana-2/code) | `vertexai/gemini-3.1-flash-image` | Image / Text / Video: $0.60 | Text / Reasoning: $3.60
Image: $72.00 |
+| [Nano Banana 2 Lite](/development/comfy-router/models/google/nano-banana-2-lite/code) | `vertexai/gemini-3.1-flash-lite-image` | Image / Text / Video: $0.30 | Text / Reasoning: $1.80
Image: $36.00 |
+| [Nano Banana Pro](/development/comfy-router/models/google/nano-banana-pro/code) | `vertexai/gemini-3-pro-image` | Image / Text: $2.40 | Text / Reasoning: $14.40
Image: $144.00 |
+
+#### Usage-based rates
+
+Comfy usage-based rates are billed per active GPU-second; total cost depends on runtime.
+
+| Name | Model ID | USD |
+| --- | --- | ---: |
+| [AI Image Upscaler Precision V2](/development/comfy-router/models/freepik/ai-image-upscaler-precision-v2/code) | `freepik/ai-image-upscaler-precision-v2` | $0.00126066 / GPU-second |
+| [Erase V1](/development/comfy-router/models/black-forest-labs/erase-v1/code) | `bfl/erase-v1` | $0.00126066 / GPU-second |
+| [FLUX 2 Max](/development/comfy-router/models/black-forest-labs/flux-2-max/code) | `bfl/flux-2-max` | $0.00126066 / GPU-second |
+| [FLUX 2 Pro](/development/comfy-router/models/black-forest-labs/flux-2-pro/code) | `bfl/flux-2-pro` | $0.00126066 / GPU-second |
+| [FLUX 3 Image](/development/comfy-router/models/black-forest-labs/flux-3-image/code) | `bfl/flux-3-image` | $0.00126066 / GPU-second |
+| [GPT Image 2](/development/comfy-router/models/openai/gpt-image-2/code) | `openai/gpt-image-2` | $0.00126066 / GPU-second |
+| [Grok Imagine Image](/development/comfy-router/models/xai/grok-imagine-image/code) | `xai/grok-imagine-image` | $0.00126066 / GPU-second |
+| [Grok Imagine Image 2.0](/development/comfy-router/models/xai/grok-imagine-image-2-0/code) | `xai/grok-imagine-image-2.0` | $0.00126066 / GPU-second |
+| [Grok Imagine Image Pro](/development/comfy-router/models/xai/grok-imagine-image-pro/code) | `xai/grok-imagine-image-pro` | $0.00126066 / GPU-second |
+| [Grok Imagine Image Quality](/development/comfy-router/models/xai/grok-imagine-image-quality/code) | `xai/grok-imagine-image-quality` | $0.00126066 / GPU-second |
+| [VTO V1](/development/comfy-router/models/black-forest-labs/vto-v1/code) | `bfl/vto-v1` | $0.00126066 / GPU-second |
+
+#### Image generation and edit rates
+
+| Name | Model ID | USD / generation |
+| --- | --- | ---: |
+| [AI Skin Enhancer Creative](/development/comfy-router/models/freepik/ai-skin-enhancer-creative/code) | `freepik/ai-skin-enhancer-creative` | $0.29 |
+| [AI Skin Enhancer Faithful](/development/comfy-router/models/freepik/ai-skin-enhancer-faithful/code) | `freepik/ai-skin-enhancer-faithful` | $0.37 |
+| [AI Skin Enhancer Flexible](/development/comfy-router/models/freepik/ai-skin-enhancer-flexible/code) | `freepik/ai-skin-enhancer-flexible` | $0.45 |
+| [FLUX 1.1 Pro](/development/comfy-router/models/black-forest-labs/flux-1-1-pro-ultra-image/code) | `bfl/flux-pro-1.1` | $0.04 |
+| [FLUX 1.1 Pro Ultra](/development/comfy-router/models/black-forest-labs/flux-1-1-pro-ultra-image/code) | `bfl/flux-pro-1.1-ultra` | $0.06 |
+| [FLUX Pro 1.0 Expand](/development/comfy-router/models/black-forest-labs/flux-pro-1-0-expand/code) | `bfl/flux-pro-1.0-expand` | $0.05 |
+| [FLUX Pro 1.0 Fill](/development/comfy-router/models/black-forest-labs/flux-pro-1-0-fill/code) | `bfl/flux-pro-1.0-fill` | $0.05 |
+| [FLUX.1 Kontext Max](/development/comfy-router/models/black-forest-labs/flux-1-kontext/code) | `bfl/flux-kontext-max` | $0.08 |
+| [FLUX.1 Kontext Pro](/development/comfy-router/models/black-forest-labs/flux-1-kontext/code) | `bfl/flux-kontext-pro` | $0.04 |
+| [Gen 4 Image](/development/comfy-router/models/runway/gen4-image/code) | `runway/gen4_image` | $0.1144 |
+| [Photon 1](/development/comfy-router/models/luma/photon-1/code) | `luma/photon-1` | $0.010439 |
+| [Photon Flash 1](/development/comfy-router/models/luma/photon-flash-1/code) | `luma/photon-flash-1` | $0.002717 |
+| [Seedream 4.0](/development/comfy-router/models/byteplus/seedream-4-0-250828/code) | `byteplus/seedream-4-0-250828` | $0.03 |
+| [Seedream 4.5](/development/comfy-router/models/byteplus/seedream-4-5-251128/code) | `byteplus/seedream-4-5-251128` | $0.04 |
+| [Seedream 5.0](/development/comfy-router/models/byteplus/seedream-5-0-260128/code) | `byteplus/seedream-5-0-260128` | $0.035 |
+| [Seedream 5.0 Flash](/development/comfy-router/models/byteplus/seedream-5-0-flash-260915/code) | `byteplus/seedream-5-0-flash-260915` | $0.02574 |
+| [Fibo](/development/comfy-router/models/bria/fibo/code) | `bria/fibo` | $0.04 |
+| [Add Object By Text](/development/comfy-router/models/bria/image-edit-add-object-by-text/code) | `bria/image-edit-add-object-by-text` | $0.0572 |
+| [Erase](/development/comfy-router/models/bria/image-edit-erase/code) | `bria/image-edit-erase` | $0.0286 |
+| [Erase By Text](/development/comfy-router/models/bria/image-edit-erase-by-text/code) | `bria/image-edit-erase-by-text` | $0.0572 |
+| [Erase Foreground](/development/comfy-router/models/bria/image-edit-erase-foreground/code) | `bria/image-edit-erase-foreground` | $0.0572 |
+| [Expand](/development/comfy-router/models/bria/image-edit-expand/code) | `bria/image-edit-expand` | $0.0286 |
+| [Generative Fill](/development/comfy-router/models/bria/image-edit-gen-fill/code) | `bria/image-edit-gen-fill` | $0.0429 |
+| [Increase Resolution](/development/comfy-router/models/bria/image-edit-increase-resolution/code) | `bria/image-edit-increase-resolution` | $0.0286 |
+| [Relight](/development/comfy-router/models/bria/image-edit-relight/code) | `bria/image-edit-relight` | $0.0572 |
+| [Remove Background](/development/comfy-router/models/bria/image-edit-remove-background/code) | `bria/image-edit-remove-background` | $0.018 |
+| [Replace Background](/development/comfy-router/models/bria/image-edit-replace-background/code) | `bria/image-edit-replace-background` | $0.0572 |
+| [Replace Object By Text](/development/comfy-router/models/bria/image-edit-replace-object-by-text/code) | `bria/image-edit-replace-object-by-text` | $0.0572 |
+| [Reseason](/development/comfy-router/models/bria/image-edit-reseason/code) | `bria/image-edit-reseason` | $0.0572 |
+| [Restore](/development/comfy-router/models/bria/image-edit-restore/code) | `bria/image-edit-restore` | $0.0572 |
+| [Kling Image O1](/development/comfy-router/models/kling/kling-image-o1/code) | `kling/kling-image-o1` | $0.028 |
+| [Wan 2.5 I2I Preview](/development/comfy-router/models/wan/wan2-5-i2i-preview/code) | `wan/wan2.5-i2i-preview` | $0.03 |
+| [Wan 2.5 T2I Preview](/development/comfy-router/models/wan/wan2-5-t2i-preview/code) | `wan/wan2.5-t2i-preview` | $0.03 |
+
+#### Not published
+
+| Name | Model ID | USD |
+| --- | --- | ---: |
+| [Ideogram 4.5](/development/comfy-router/models/ideogram/ideogram-4-5/code) | `ideogram/ideogram-4-5` | Not published |
+| [P Image Ideogram](/development/comfy-router/models/ideogram/p-image-ideogram/code) | `ideogram/p-image-ideogram` | Not published |
+| [Text To Image](/development/comfy-router/models/moonvalley/text-to-image/code) | `moonvalley/text-to-image` | Not published |
+
+#### Rates per input image
+
+| Name | Model ID | Option | USD |
+| --- | --- | --- | ---: |
+| [Qwen Image 3.0](/development/comfy-router/models/qwen/qwen-image-3-0/code) | `qwen/qwen-image-3.0` | Input image · 1K / 2K | $0.00429 / input image |
+| [Qwen Image 3.0 Pro](/development/comfy-router/models/qwen/qwen-image-3-0-pro/code) | `qwen/qwen-image-3.0-pro` | Input image · 1K / 2K | $0.00429 / input image |
+| [Seedream 5.0 Pro](/development/comfy-router/models/byteplus/seedream-5-0-pro-260628/code) | `byteplus/seedream-5-0-pro-260628` | Input images after the first | $0.003 / input image |
+
+#### Rates per Recraft credit
+
+| Name | Model ID | USD |
+| --- | --- | ---: |
+| [Recraft V2](/development/comfy-router/models/recraft/recraftv2/code) | `recraft/recraftv2` | $0.00143 / Recraft credit |
+| [Recraft V3](/development/comfy-router/models/recraft/recraftv3/code) | `recraft/recraftv3` | $0.00143 / Recraft credit |
+| [Recraft V4](/development/comfy-router/models/recraft/recraftv4/code) | `recraft/recraftv4` | $0.00143 / Recraft credit |
+| [Recraft V4 Pro](/development/comfy-router/models/recraft/recraftv4-pro/code) | `recraft/recraftv4_pro` | $0.00143 / Recraft credit |
+| [Recraft V4 Styles](/development/comfy-router/models/recraft/recraftv4-styles/code) | `recraft/recraftv4_styles` | $0.00143 / Recraft credit |
+| [Recraft V4 Styles Pro](/development/comfy-router/models/recraft/recraftv4-styles-pro/code) | `recraft/recraftv4_styles_pro` | $0.00143 / Recraft credit |
+| [Recraft V4 Styles Pro Vector](/development/comfy-router/models/recraft/recraftv4-styles-pro-vector/code) | `recraft/recraftv4_styles_pro_vector` | $0.00143 / Recraft credit |
+| [Recraft V4 Styles Vector](/development/comfy-router/models/recraft/recraftv4-styles-vector/code) | `recraft/recraftv4_styles_vector` | $0.00143 / Recraft credit |
+| [Recraft V4.1](/development/comfy-router/models/recraft/recraftv4-1/code) | `recraft/recraftv4_1` | $0.00143 / Recraft credit |
+| [Recraft V4.1 Pro](/development/comfy-router/models/recraft/recraftv4-1-pro/code) | `recraft/recraftv4_1_pro` | $0.00143 / Recraft credit |
+| [Recraft V4.1 Pro Vector](/development/comfy-router/models/recraft/recraftv4-1-pro-vector/code) | `recraft/recraftv4_1_pro_vector` | $0.00143 / Recraft credit |
+| [Recraft V4.1 Utility](/development/comfy-router/models/recraft/recraftv4-1-utility/code) | `recraft/recraftv4_1_utility` | $0.00143 / Recraft credit |
+| [Recraft V4.1 Utility Pro](/development/comfy-router/models/recraft/recraftv4-1-utility-pro/code) | `recraft/recraftv4_1_utility_pro` | $0.00143 / Recraft credit |
+| [Recraft V4.1 Utility Pro Vector](/development/comfy-router/models/recraft/recraftv4-1-utility-pro-vector/code) | `recraft/recraftv4_1_utility_pro_vector` | $0.00143 / Recraft credit |
+| [Recraft V4.1 Utility Vector](/development/comfy-router/models/recraft/recraftv4-1-utility-vector/code) | `recraft/recraftv4_1_utility_vector` | $0.00143 / Recraft credit |
+| [Recraft V4.1 Vector](/development/comfy-router/models/recraft/recraftv4-1-vector/code) | `recraft/recraftv4_1_vector` | $0.00143 / Recraft credit |
+
+#### Rates per reference image
+
+| Name | Model ID | USD |
+| --- | --- | ---: |
+| [Uni 1](/development/comfy-router/models/luma_2/uni-1/code) | `luma_2/uni-1` | $0.003 / reference image |
+| [Uni 1 Max](/development/comfy-router/models/luma_2/uni-1-max/code) | `luma_2/uni-1-max` | $0.003 / reference image |
+
+#### Krea 2 generation rates (USD / generation)
+
+| Name | Model ID | Moodboards USD | Style references USD | Text only USD |
+| --- | --- | ---: | ---: | ---: |
+| [Krea 2](/development/comfy-router/models/krea/krea-2/code) | `krea/krea-2` | $0.04 | $0.035 | $0.03 |
+| [Krea 2 Large](/development/comfy-router/models/krea/krea-2-large/code) | `krea/krea-2-large` | $0.07 | $0.065 | $0.06 |
+| [Krea 2 Medium](/development/comfy-router/models/krea/krea-2-medium/code) | `krea/krea-2-medium` | $0.04 | $0.035 | $0.03 |
+| [Krea 2 Medium Turbo](/development/comfy-router/models/krea/krea-2-medium-turbo/code) | `krea/krea-2-medium-turbo` | $0.02 | $0.0175 | $0.015 |
+
+#### Qwen Image 3.0 output image rates
+
+| Name | Model ID | 1K USD / generation | 2K USD / generation |
+| --- | --- | ---: | ---: |
+| [Qwen Image 3.0](/development/comfy-router/models/qwen/qwen-image-3-0/code) | `qwen/qwen-image-3.0` | $0.0429 | $0.0429 |
+| [Qwen Image 3.0 Pro](/development/comfy-router/models/qwen/qwen-image-3-0-pro/code) | `qwen/qwen-image-3.0-pro` | $0.0572 | $0.10725 |
+
+#### Seedream 5.0 Pro output image rates
+
+| Name | Model ID | Layer separation · Standard USD / generation | Layer separation · Large USD / generation | Output image · Standard USD / generation | Output image · Large USD / generation |
+| --- | --- | ---: | ---: | ---: | ---: |
+| [Seedream 5.0 Pro](/development/comfy-router/models/byteplus/seedream-5-0-pro-260628/code) | `byteplus/seedream-5-0-pro-260628` | $0.032175 | $0.06435 | $0.045 | $0.09 |
+
+#### Image generation and then image edit
+
+| Name | Model ID | Image generation USD / generation | Image edit USD / generation |
+| --- | --- | ---: | ---: |
+| [Uni 1](/development/comfy-router/models/luma_2/uni-1/code) | `luma_2/uni-1` | $0.0404 | $0.0434 |
+| [Uni 1 Max](/development/comfy-router/models/luma_2/uni-1-max/code) | `luma_2/uni-1-max` | $0.10 | $0.103 |
+
+#### Ideogram image rates
+
+| Name | Model ID | Standard USD / generation | Quality USD / generation | Turbo USD / generation |
+| --- | --- | ---: | ---: | ---: |
+| [Ideogram 4.0](/development/comfy-router/models/ideogram/ideogram-v4/code) | `ideogram/ideogram-v4` | $0.0858 | $0.143 | $0.0429 |
+| [Ideogram V3](/development/comfy-router/models/ideogram/ideogram-v3/code) | `ideogram/ideogram-v3` | $0.0858 | $0.1287 | $0.0429 |
+
+
+
+
+#### Not published
+
+| Name | Model ID | USD |
+| --- | --- | ---: |
+| [Crystal Upscaler](/development/comfy-router/models/fal/crystal-upscaler/code) | `fal/crystal-upscaler` | Not published |
+
+#### Usage-based rates
+
+| Name | Model ID | USD |
+| --- | --- | ---: |
+| [GPT Image 2](/development/comfy-router/models/openai/gpt-image-2/code) | `openai/gpt-image-2` | Usage-based
request usage |
+| [GPT Image 2.5 Flare](/development/comfy-router/models/openai/gpt-image-2-5-flare/code) | `openai/gpt-image-2.5-flare` | Usage-based
request usage |
+| [GPT Image 2.5 Sunburst](/development/comfy-router/models/openai/gpt-image-2-5-sunburst/code) | `openai/gpt-image-2.5-sunburst` | Usage-based
request usage |
+
+#### Image generation and edit rates
+
+| Name | Model ID | USD / generation |
+| --- | --- | ---: |
+| [Nano Banana 2](/development/comfy-router/models/google/nano-banana-2/code) | `vertexai/gemini-3.1-flash-image` | $0.08 |
+| [Nano Banana Pro](/development/comfy-router/models/google/nano-banana-pro/code) | `vertexai/gemini-3-pro-image` | $0.15 |
+| [Patina](/development/comfy-router/models/fal/patina/code) | `fal/patina` | $0.0143 |
+
+
+
+
+| Name | Model ID | USD |
+| --- | --- | ---: |
+| [GPT Image 2](/development/comfy-router/models/openai/gpt-image-2/code) | `openai/gpt-image-2` | Usage-based
request usage |
+| [GPT Image 2.5 Flare](/development/comfy-router/models/openai/gpt-image-2-5-flare/code) | `openai/gpt-image-2.5-flare` | Usage-based
request usage |
+| [GPT Image 2.5 Sunburst](/development/comfy-router/models/openai/gpt-image-2-5-sunburst/code) | `openai/gpt-image-2.5-sunburst` | Usage-based
request usage |
+| [Nano Banana 2](/development/comfy-router/models/google/nano-banana-2/code) | `vertexai/gemini-3.1-flash-image` | Usage-based
request usage |
+| [Nano Banana Pro](/development/comfy-router/models/google/nano-banana-pro/code) | `vertexai/gemini-3-pro-image` | Usage-based
request usage |
+
+
+
+
+#### Image generation and edit rates
+
+| Name | Model ID | Text to image USD / generation | Image edit USD / generation | Image edit · 1K / 2K USD / generation | Image edit · 4K USD / generation |
+| --- | --- | ---: | ---: | ---: | ---: |
+| [GPT Image 2](/development/comfy-router/models/openai/gpt-image-2/code) | `openai/gpt-image-2` | $0.06 | $0.07 | - | - |
+| [GPT Image 2.5 Flare](/development/comfy-router/models/openai/gpt-image-2-5-flare/code) | `openai/gpt-image-2.5-flare` | $0.024 | $0.039 | - | - |
+| [GPT Image 2.5 Sunburst](/development/comfy-router/models/openai/gpt-image-2-5-sunburst/code) | `openai/gpt-image-2.5-sunburst` | $0.024 | $0.039 | - | - |
+| [Nano Banana 2](/development/comfy-router/models/google/nano-banana-2/code) | `vertexai/gemini-3.1-flash-image` | $0.07 | $0.07 | - | - |
+| [Nano Banana Pro](/development/comfy-router/models/google/nano-banana-pro/code) | `vertexai/gemini-3-pro-image` | $0.14 | - | $0.14 | $0.24 |
+
+| Name | Model ID | USD / generation |
+| --- | --- | ---: |
+| [Seedvr 2](/development/comfy-router/models/wavespeed/seedvr2/code) | `wavespeed/seedvr2` | $0.01 |
+| [Ultimate Image Upscaler](/development/comfy-router/models/wavespeed/ultimate-image-upscaler/code) | `wavespeed/ultimate-image-upscaler` | $0.06 |
+
+#### Image quality and resolution
+
+| Name | Model ID | Operation | Quality | 1K USD / generation | 2K USD / generation | 4K USD / generation |
+| --- | --- | --- | --- | ---: | ---: | ---: |
+| [GPT Image 2](/development/comfy-router/models/openai/gpt-image-2/code) | `openai/gpt-image-2` | Text to image | Low | $0.01 | $0.02 | $0.03 |
+| | | | Medium | $0.06 | $0.10 | $0.18 |
+| | | | High | $0.22 | $0.40 | $0.72 |
+| | | Image edit | Low | $0.02 | $0.03 | $0.04 |
+| | | | Medium | $0.07 | $0.11 | $0.19 |
+| | | | High | $0.23 | $0.41 | $0.73 |
+| [GPT Image 2.5 Flare](/development/comfy-router/models/openai/gpt-image-2-5-flare/code) | `openai/gpt-image-2.5-flare` | Text to image | Low | $0.01 | $0.02 | $0.03 |
+| | | | Medium | $0.024 | $0.04 | $0.07 |
+| | | | High | $0.09 | $0.15 | $0.27 |
+| | | | Extra high | $0.16 | $0.27 | $0.48 |
+| | | | Max | $0.36 | $0.60 | $1.00 |
+| | | Image edit | Low | $0.025 | $0.035 | $0.045 |
+| | | | Medium | $0.039 | $0.055 | $0.085 |
+| | | | High | $0.105 | $0.165 | $0.285 |
+| | | | Extra high | $0.175 | $0.285 | $0.495 |
+| | | | Max | $0.375 | $0.615 | $1.015 |
+| [GPT Image 2.5 Sunburst](/development/comfy-router/models/openai/gpt-image-2-5-sunburst/code) | `openai/gpt-image-2.5-sunburst` | Text to image | Low | $0.01 | $0.02 | $0.03 |
+| | | | Medium | $0.024 | $0.04 | $0.07 |
+| | | | High | $0.09 | $0.15 | $0.27 |
+| | | | Extra high | $0.16 | $0.27 | $0.48 |
+| | | | Max | $0.36 | $0.60 | $1.00 |
+| | | Image edit | Low | $0.025 | $0.035 | $0.045 |
+| | | | Medium | $0.039 | $0.055 | $0.085 |
+| | | | High | $0.105 | $0.165 | $0.285 |
+| | | | Extra high | $0.175 | $0.285 | $0.495 |
+| | | | Max | $0.375 | $0.615 | $1.015 |
+
+
+
+
+| Name | Model ID | USD |
+| --- | --- | ---: |
+| [GPT Image 2](/development/comfy-router/models/openai/gpt-image-2/code) | `openai/gpt-image-2` | Not published |
+| [Nano Banana 2](/development/comfy-router/models/google/nano-banana-2/code) | `vertexai/gemini-3.1-flash-image` | Not published |
+| [Nano Banana Pro](/development/comfy-router/models/google/nano-banana-pro/code) | `vertexai/gemini-3-pro-image` | Not published |
+
+
+
+
+
+
+
+
+
+
+
+
+Video rates apply to output plus reference-video duration.
+
+#### Per 1M tokens by resolution
+
+| Name | Model ID | Input type | 480p USD | 720p USD | 1080p USD | 4K USD |
+| --- | --- | --- | ---: | ---: | ---: | ---: |
+| [Dreamina Seedance 2.0](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | `byteplus/dreamina-seedance-2-0-260128` | Image to video | $10.01 / 1M tokens | $10.01 / 1M tokens | $11.011 / 1M tokens | $5.72 / 1M tokens |
+| | | Text to video | $10.01 / 1M tokens | $10.01 / 1M tokens | $11.011 / 1M tokens | $5.72 / 1M tokens |
+| | | Video to video | $6.149 / 1M tokens | $6.149 / 1M tokens | $6.721 / 1M tokens | $3.432 / 1M tokens |
+| [Dreamina Seedance 2.5](/development/comfy-router/models/byteplus/dreamina-seedance-2-5-260628/code) | `byteplus/dreamina-seedance-2-5-260628` | Image to video | $15.301 / 1M tokens | $15.301 / 1M tokens | $16.731 / 1M tokens | - |
+| | | Text to video | $15.301 / 1M tokens | $15.301 / 1M tokens | $16.731 / 1M tokens | - |
+| | | Video to video | $9.152 / 1M tokens | $9.152 / 1M tokens | $10.01 / 1M tokens | - |
+
+#### Per second by resolution
+
+| Name | Model ID | 480p USD | 720p USD | 1080p USD | 1440p USD | 4K USD |
+| --- | --- | ---: | ---: | ---: | ---: | ---: |
+| [HappyHorse 1.0 I2V](/development/comfy-router/models/wan/happyhorse-1-0-i2v/code) | `wan/happyhorse-1.0-i2v` | - | $0.14 / s | $0.24 / s | - | - |
+| [HappyHorse 1.0 R2V](/development/comfy-router/models/wan/happyhorse-1-0-r2v/code) | `wan/happyhorse-1.0-r2v` | - | $0.14 / s | $0.24 / s | - | - |
+| [HappyHorse 1.0 T2V](/development/comfy-router/models/wan/happyhorse-1-0-t2v/code) | `wan/happyhorse-1.0-t2v` | - | $0.14 / s | $0.24 / s | - | - |
+| [HappyHorse 1.0 Video Edit](/development/comfy-router/models/wan/happyhorse-1-0-video-edit/code) | `wan/happyhorse-1.0-video-edit` | - | $0.14 / s | $0.24 / s | - | - |
+| [HappyHorse 1.1 I2V](/development/comfy-router/models/wan/happyhorse-1-1-i2v/code) | `wan/happyhorse-1.1-i2v` | - | $0.2002 / s | $0.2574 / s | - | - |
+| [HappyHorse 1.1 R2V](/development/comfy-router/models/wan/happyhorse-1-1-r2v/code) | `wan/happyhorse-1.1-r2v` | - | $0.2002 / s | $0.2574 / s | - | - |
+| [HappyHorse 1.1 T2V](/development/comfy-router/models/wan/happyhorse-1-1-t2v/code) | `wan/happyhorse-1.1-t2v` | - | $0.2002 / s | $0.2574 / s | - | - |
+| [LTX 2.5 Fast](/development/comfy-router/models/ltx/ltx-2-5-fast/code) | `ltx/ltx-2-5-fast` | - | $0.1287 / s | $0.1859 / s | $0.2717 / s | $0.429 / s |
+| [LTX 2.5 Pro](/development/comfy-router/models/ltx/ltx-2-5-pro/code) | `ltx/ltx-2-5-pro` | - | $0.1716 / s | $0.2431 / s | - | - |
+| [P Video 2 Standard](/development/comfy-router/models/pruna/p-video-2/code) | `pruna/p-video-2` | - | $0.03575 / s | $0.0715 / s | - | - |
+| [P Video 2 Draft](/development/comfy-router/models/pruna/p-video-2/code) | `pruna/p-video-2` | - | $0.02145 / s | $0.0429 / s | - | - |
+| [Wan 2.5 I2V Preview](/development/comfy-router/models/wan/wan2-5-i2v-preview/code) | `wan/wan2.5-i2v-preview` | $0.05 / s | $0.10 / s | $0.15 / s | - | - |
+| [Wan 2.5 T2V Preview](/development/comfy-router/models/wan/wan2-5-t2v-preview/code) | `wan/wan2.5-t2v-preview` | $0.05 / s | $0.10 / s | $0.15 / s | - | - |
+| [Wan 2.6 I2V](/development/comfy-router/models/wan/wan2-6-i2v/code) | `wan/wan2.6-i2v` | - | $0.10 / s | $0.15 / s | - | - |
+| [Wan 2.6 R2V](/development/comfy-router/models/wan/wan2-6-r2v/code) | `wan/wan2.6-r2v` | - | $0.10 / s | $0.15 / s | - | - |
+| [Wan 2.6 T2V](/development/comfy-router/models/wan/wan2-6-t2v/code) | `wan/wan2.6-t2v` | - | $0.10 / s | $0.15 / s | - | - |
+| [Wan 2.7 I2V](/development/comfy-router/models/wan/wan2-7-i2v/code) | `wan/wan2.7-i2v` | - | $0.10 / s | $0.15 / s | - | - |
+| [Wan 2.7 R2V](/development/comfy-router/models/wan/wan2-7-r2v/code) | `wan/wan2.7-r2v` | - | $0.10 / s | $0.15 / s | - | - |
+| [Wan 2.7 T2V](/development/comfy-router/models/wan/wan2-7-t2v/code) | `wan/wan2.7-t2v` | - | $0.10 / s | $0.15 / s | - | - |
+| [Wan 2.7 Video Edit](/development/comfy-router/models/wan/wan2-7-videoedit/code) | `wan/wan2.7-videoedit` | - | $0.10 / s | $0.15 / s | - | - |
+| [Wan 3.0 Video](/development/comfy-router/models/wan/wan3-0-video/code) | `wan/wan3.0-video` | $0.05 / s | $0.10 / s | $0.20 / s | - | - |
+| [Wan 3.0 Video Prime](/development/comfy-router/models/wan/wan3-0-video-prime/code) | `wan/wan3.0-video-prime` | $0.068 / s | $0.14 / s | $0.28 / s | - | - |
+
+| Name | Model ID | 768p USD | 2K USD |
+| --- | --- | ---: | ---: |
+| [MiniMax H3](/development/comfy-router/models/minimax/minimax-h3/code) | `minimax/minimax-h3` | $0.1287 / s | $0.1859 / s |
+
+| Name | Model ID | Audio | 720p USD | 1080p USD | 4K USD |
+| --- | --- | --- | ---: | ---: | ---: |
+| [Veo 3.1 Fast Generate 001](/development/comfy-router/models/veo/veo-3-1-fast-generate-001/code) | `veo/veo-3.1-fast-generate-001` | No audio | $0.08 / s | $0.10 / s | $0.25 / s |
+| | | Audio | $0.10 / s | $0.12 / s | $0.30 / s |
+| [Veo 3.1 Generate 001](/development/comfy-router/models/veo/veo-3-1-generate-001/code) | `veo/veo-3.1-generate-001` | No audio | $0.20 / s | $0.20 / s | $0.40 / s |
+| | | Audio | $0.40 / s | $0.40 / s | $0.60 / s |
+| [Veo 3.1 Lite Generate 001](/development/comfy-router/models/veo/veo-3-1-lite-generate-001/code) | `veo/veo-3.1-lite-generate-001` | No audio | $0.03 / s | $0.05 / s | - |
+| | | Audio | $0.05 / s | $0.08 / s | - |
+
+#### SwitchX image and video rates
+
+Images are billed per output image. Videos are billed per 30 output frames, rounded up.
+
+| Name | Model ID | 720p USD | 1080p USD |
+| --- | --- | ---: | ---: |
+| [SwitchX](/development/comfy-router/models/beeble/switchx/code) | `beeble/switchx` | Image: $0.143
Video: $0.143 | Image: $0.429
Video: $0.429 |
+
+#### Rates per second
+
+| Name | Model ID | USD |
+| --- | --- | ---: |
+| [Aleph 2](/development/comfy-router/models/runway/aleph2/code) | `runway/aleph2` | $0.4004 / s |
+| [Gen 4 Turbo](/development/comfy-router/models/runway/gen4-turbo/code) | `runway/gen4_turbo` | $0.0715 / s |
+| [Sync 3](/development/comfy-router/models/synclabs/sync-3/code) | `synclabs/sync-3` | $0.19019 / s |
+| [Erase](/development/comfy-router/models/bria/video-edit-erase/code) | `bria/video-edit-erase` | $0.06435 / s |
+| [Green Screen](/development/comfy-router/models/bria/video-edit-green-screen/code) | `bria/video-edit-green-screen` | $0.05 / s |
+| [Remove Background](/development/comfy-router/models/bria/video-edit-remove-background/code) | `bria/video-edit-remove-background` | $0.05 / s |
+| [Replace Background](/development/comfy-router/models/bria/video-edit-replace-background/code) | `bria/video-edit-replace-background` | $0.05 / s |
+
+#### Usage-based rates
+
+Comfy usage-based rates are billed per active GPU-second; total cost depends on runtime.
+
+| Name | Model ID | USD |
+| --- | --- | ---: |
+| [FLUX 3 Video](/development/comfy-router/models/black-forest-labs/flux-3-video/code) | `bfl/flux-3-video` | $0.00126066 / GPU-second |
+| [FLUX Video Upscale](/development/comfy-router/models/black-forest-labs/flux-video-upscale/code) | `bfl/video-upscale-v1` | $0.00126066 / GPU-second |
+| [Grok Imagine Video](/development/comfy-router/models/xai/grok-imagine-video/code) | `xai/grok-imagine-video` | $0.00126066 / GPU-second |
+| [Grok Imagine Video 1.5](/development/comfy-router/models/xai/grok-imagine-video-1-5/code) | `xai/grok-imagine-video-1.5` | $0.00126066 / GPU-second |
+| [Grok Imagine Video 1.5 Preview](/development/comfy-router/models/xai/grok-imagine-video-1-5-preview/code) | `xai/grok-imagine-video-1.5-preview` | $0.00126066 / GPU-second |
+| [Ray 2](/development/comfy-router/models/luma/ray-2/code) | `luma/ray-2` | $0.00126066 / GPU-second |
+| [Ray Flash 2](/development/comfy-router/models/luma/ray-flash-2/code) | `luma/ray-flash-2` | $0.00126066 / GPU-second |
+| [Video Edit V1](/development/comfy-router/models/black-forest-labs/video-edit-v1/code) | `bfl/video-edit-v1` | $0.00126066 / GPU-second |
+
+#### Not published
+
+| Name | Model ID | USD |
+| --- | --- | ---: |
+| [Grok Imagine Video 1.5 Lite](/development/comfy-router/models/xai/grok-imagine-video-1-5-lite/code) | `xai/grok-imagine-video-1.5-lite` | Not published |
+| [Pika 2.5 Image To Video](/development/comfy-router/models/pika/pika-2-5-image-to-video/code) | `pika/pika-2.5-image-to-video` | Not published |
+| [Pika 2.5 Text To Video](/development/comfy-router/models/pika/pika-2-5-text-to-video/code) | `pika/pika-2.5-text-to-video` | Not published |
+| [Video To Video Resize](/development/comfy-router/models/moonvalley/video-to-video-resize/code) | `moonvalley/video-to-video-resize` | Not published |
+
+#### Rates per Kling credit
+
+| Name | Model ID | USD |
+| --- | --- | ---: |
+| [Kling 3.0 Turbo](/development/comfy-router/models/kling/kling-3-0-turbo/code) | `kling/kling-3.0-turbo` | $0.14 / Kling credit |
+| [Kling V2.5 Turbo](/development/comfy-router/models/kling/kling-v2-5-turbo/code) | `kling/kling-v2-5-turbo` | $0.14 / Kling credit |
+| [Kling V2.6](/development/comfy-router/models/kling/kling-v2-6/code) | `kling/kling-v2-6` | $0.14 / Kling credit |
+| [Kling V3](/development/comfy-router/models/kling/kling-v3/code) | `kling/kling-v3` | $0.14 / Kling credit |
+| [Kling V3 Omni](/development/comfy-router/models/kling/kling-v3-omni/code) | `kling/kling-v3-omni` | $0.14 / Kling credit |
+| [Kling Video O1](/development/comfy-router/models/kling/kling-video-o1/code) | `kling/kling-video-o1` | $0.14 / Kling credit |
+| [Videos Avatar Image 2video](/development/comfy-router/models/kling/videos-avatar-image2video/code) | `kling/videos-avatar-image2video` | $0.14 / Kling credit |
+| [Videos Lip Sync](/development/comfy-router/models/kling/videos-lip-sync/code) | `kling/videos-lip-sync` | $0.14 / Kling credit |
+| [Videos Video Extend](/development/comfy-router/models/kling/videos-video-extend/code) | `kling/videos-video-extend` | $0.14 / Kling credit |
+
+#### Rates per extra reference image
+
+| Name | Model ID | Option | USD |
+| --- | --- | --- | ---: |
+| [MiniMax H3](/development/comfy-router/models/minimax/minimax-h3/code) | `minimax/minimax-h3` | References beyond 5 | $0.0572 / extra reference image |
+
+#### Rates per request
+
+| Name | Model ID | 5s USD / request | 10s USD / request |
+| --- | --- | ---: | ---: |
+| [Image To Video](/development/comfy-router/models/moonvalley/image-to-video/code) | `moonvalley/image-to-video` | $1.50 | $3.00 |
+| [Text To Video](/development/comfy-router/models/moonvalley/text-to-video/code) | `moonvalley/text-to-video` | $1.50 | $3.00 |
+| [Video To Video](/development/comfy-router/models/moonvalley/video-to-video/code) | `moonvalley/video-to-video` | $2.25 | $4.00 |
+
+#### Seedance video token rates
+
+| Name | Model ID | Image to video / Text to video USD / 1M tokens | Video to video USD / 1M tokens |
+| --- | --- | ---: | ---: |
+| [Dreamina Seedance 2.0 Fast](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-fast-260128/code) | `byteplus/dreamina-seedance-2-0-fast-260128` | $8.008 | $4.719 |
+| [Dreamina Seedance 2.0 Mini](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-mini/code) | `byteplus/dreamina-seedance-2-0-mini` | $5.005 | $3.003 |
+| [Seedance 1.0 Pro](/development/comfy-router/models/byteplus/seedance-1-0-pro-250528/code) | `byteplus/seedance-1-0-pro-250528` | $2.50 | - |
+| [Seedance 1.0 Pro Fast](/development/comfy-router/models/byteplus/seedance-1-0-pro-fast-251015/code) | `byteplus/seedance-1-0-pro-fast-251015` | $1.00 | - |
+
+#### Seedance 1.5 Pro audio rates
+
+| Name | Model ID | No audio USD / 1M tokens | Audio USD / 1M tokens |
+| --- | --- | ---: | ---: |
+| [Seedance 1.5 Pro](/development/comfy-router/models/byteplus/seedance-1-5-pro-251215/code) | `byteplus/seedance-1-5-pro-251215` | $1.20 | $2.40 |
+
+
+
+
+#### Per 1K video tokens by resolution
+
+| Name | Model ID | 480p USD | 720p USD | 1080p USD | 4K USD |
+| --- | --- | ---: | ---: | ---: | ---: |
+| [Dreamina Seedance 2.0](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | `byteplus/dreamina-seedance-2-0-260128` | $0.014 | $0.014 | $0.014 | $0.008 |
+| [Dreamina Seedance 2.5](/development/comfy-router/models/byteplus/dreamina-seedance-2-5-260628/code) | `byteplus/dreamina-seedance-2-5-260628` | $0.0214 | $0.0214 | $0.0234 | - |
+
+#### Rates per generation
+
+| Name | Model ID | USD / generation |
+| --- | --- | ---: |
+| [H3 Max](/development/comfy-router/models/fal/h3-max/code) | `fal/h3-max` | $0.0715 |
+| [H3 Max Turbo](/development/comfy-router/models/fal/h3-max-turbo/code) | `fal/h3-max-turbo` | $0.03575 |
+
+
+
+
+#### Per 1K video tokens by resolution
+
+| Name | Model ID | 480p USD | 720p USD |
+| --- | --- | ---: | ---: |
+| [Dreamina Seedance 2.0](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | `byteplus/dreamina-seedance-2-0-260128` | $0.014 | $0.014 |
+| [Dreamina Seedance 2.5](/development/comfy-router/models/byteplus/dreamina-seedance-2-5-260628/code) | `byteplus/dreamina-seedance-2-5-260628` | $0.0214 | $0.0214 |
+
+#### Per second by resolution
+
+| Name | Model ID | 480p USD | 720p USD | 1080p USD | 4K USD |
+| --- | --- | ---: | ---: | ---: | ---: |
+| [Kling 3.0 Turbo](/development/comfy-router/models/kling/kling-3-0-turbo/code) | `kling/kling-3.0-turbo` | - | $0.112 / s | $0.14 / s | - |
+| [Wan 3.0 Video](/development/comfy-router/models/wan/wan3-0-video/code) | `wan/wan3.0-video` | $0.05 / s | $0.10 / s | $0.20 / s | - |
+| [Higgsfield Kling 3.4k](/development/comfy-router/models/higgsfield/higgsfield-kling-3-4k/code) | `higgsfield/higgsfield-kling-3-4k` | - | - | - | $0.42 / s |
+
+#### Rates per second
+
+| Name | Model ID | USD |
+| --- | --- | ---: |
+| [Higgsfield Kling 3 Pro](/development/comfy-router/models/higgsfield/higgsfield-kling-3-pro/code) | `higgsfield/higgsfield-kling-3-pro` | $0.168 / s |
+| [Kling V3 Standard](/development/comfy-router/models/kling/kling-v3/code) | `kling/kling-v3` | $0.1262 / s |
+
+
+
+
+| Name | Model ID | USD |
+| --- | --- | ---: |
+| [Dreamina Seedance 2.0](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | `byteplus/dreamina-seedance-2-0-260128` | Usage-based
request usage |
+| [Dreamina Seedance 2.5](/development/comfy-router/models/byteplus/dreamina-seedance-2-5-260628/code) | `byteplus/dreamina-seedance-2-5-260628` | Usage-based
request usage |
+
+
+
+
+#### Rates per request
+
+| Name | Model ID | USD / request |
+| --- | --- | ---: |
+| [Dreamina Seedance 2.0](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | `byteplus/dreamina-seedance-2-0-260128` | $0.60 |
+| [Dreamina Seedance 2.5](/development/comfy-router/models/byteplus/dreamina-seedance-2-5-260628/code) | `byteplus/dreamina-seedance-2-5-260628` | $0.90 |
+
+#### Per 5 s by resolution
+
+| Name | Model ID | 720p USD | 1080p USD | 2K USD | 4K USD |
+| --- | --- | ---: | ---: | ---: | ---: |
+| [Flashvsr](/development/comfy-router/models/wavespeed/flashvsr/code) | `wavespeed/flashvsr` | $0.06 / 5 s | $0.09 / 5 s | $0.12 / 5 s | $0.16 / 5 s |
+
+
+
+
+| Name | Model ID | USD |
+| --- | --- | ---: |
+| [Dreamina Seedance 2.0](/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | `byteplus/dreamina-seedance-2-0-260128` | Not published |
+
+
+
+
+
+
+
+
+
+
+
+
+#### Token rates
+
+| Name | Model ID | Cached input USD / 1M tokens | Input USD / 1M tokens | Output USD / 1M tokens |
+| --- | --- | ---: | ---: | ---: |
+| [Claude Fable 5.1](/development/comfy-router/models/anthropic/claude-fable-5-1/code) | `anthropic/claude-fable-5-1` | Read: $0.3575
Write (1h): $28.60
Write (5m): $17.875 | Text: $14.30 | Text: $71.50 |
+| [Claude Fable 5](/development/comfy-router/models/anthropic/claude-fable-5/code) | `anthropic/claude-fable-5` | Read: $1.43
Write (1h): $28.60
Write (5m): $17.875 | Text: $14.30 | Text: $71.50 |
+| [Claude Haiku 4.5](/development/comfy-router/models/anthropic/claude-haiku-4-5-20251001/code) | `anthropic/claude-haiku-4-5-20251001` | Read: $0.10
Write (1h): $2.00
Write (5m): $1.25 | Text: $1.00 | Text: $5.00 |
+| [Claude Opus 5.5](/development/comfy-router/models/anthropic/claude-opus-5-5/code) | `anthropic/claude-opus-5-5` | Read: $0.286
Write (1h): $11.44
Write (5m): $7.15 | Text: $5.72 | Text: $28.60 |
+| [Claude Opus 5](/development/comfy-router/models/anthropic/claude-opus-5/code) | `anthropic/claude-opus-5` | Read: $0.715
Write (1h): $14.30
Write (5m): $8.9375 | Text: $7.15 | Text: $35.75 |
+| [Claude Opus 4.8](/development/comfy-router/models/anthropic/claude-opus-4-8/code) | `anthropic/claude-opus-4-8` | Read: $0.715
Write (1h): $14.30
Write (5m): $8.9375 | Text: $7.15 | Text: $35.75 |
+| [Claude Opus 4.7](/development/comfy-router/models/anthropic/claude-opus-4-7/code) | `anthropic/claude-opus-4-7` | Read: $0.50
Write (1h): $10.00
Write (5m): $6.25 | Text: $5.00 | Text: $25.00 |
+| [Claude Opus 4.6](/development/comfy-router/models/anthropic/claude-opus-4-6/code) | `anthropic/claude-opus-4-6` | Read: $0.50
Write (1h): $10.00
Write (5m): $6.25 | Text: $5.00 | Text: $25.00 |
+| [Claude Sonnet 5.5](/development/comfy-router/models/anthropic/claude-sonnet-5-5/code) | `anthropic/claude-sonnet-5-5` | Read: $0.286
Write (1h): $5.72
Write (5m): $3.575 | Text: $2.86 | Text: $14.30 |
+| [Claude Sonnet 5](/development/comfy-router/models/anthropic/claude-sonnet-5/code) | `anthropic/claude-sonnet-5` | Read: $0.286
Write (1h): $5.72
Write (5m): $3.575 | Text: $2.86 | Text: $14.30 |
+| [Claude Sonnet 4.6](/development/comfy-router/models/anthropic/claude-sonnet-4-6/code) | `anthropic/claude-sonnet-4-6` | Read: $0.30
Write (1h): $6.00
Write (5m): $3.75 | Text: $3.00 | Text: $15.00 |
+| [Claude Sonnet 4.5](/development/comfy-router/models/anthropic/claude-sonnet-4-5-20250929/code) | `anthropic/claude-sonnet-4-5-20250929` | Read: $0.30
Write (1h): $6.00
Write (5m): $3.75 | Text: $3.00 | Text: $15.00 |
+| [Gemini 3.8 Flash](/development/comfy-router/models/google/gemini-3-8-flash/code) | `vertexai/gemini-3.8-flash` | - | Audio / Image / Text / Video: $2.145 | Text / Reasoning: $10.725 |
+| [Gemini 3.7 Flash](/development/comfy-router/models/google/gemini-3-7-flash/code) | `vertexai/gemini-3.7-flash` | - | Audio / Image / Text / Video: $2.145 | Text / Reasoning: $10.725 |
+| [Gemini 3.1 Flash Lite](/development/comfy-router/models/google/gemini-3-1-flash-lite/code) | `vertexai/gemini-3.1-flash-lite` | - | Image / Text / Video: $0.25
Audio: $0.50 | Text / Reasoning: $1.50 |
+| [Gemini Omni 1.1 Flash](/development/comfy-router/models/gemini-interactions/gemini-omni-1-1-flash/code) | `gemini-interactions/gemini-omni-1.1-flash` | - | Audio / Image / Text / Video: $2.145 | Text / Reasoning: $12.87
Video: $25.025 |
+| [Gemini Omni Flash Preview](/development/comfy-router/models/gemini-interactions/gemini-omni-flash-preview/code) | `gemini-interactions/gemini-omni-flash-preview` | - | Audio / Image / Text / Video: $2.145 | Text / Reasoning: $12.87
Video: $25.025 |
+| [Google Gemini](/development/comfy-router/models/google/gemini/code) | `vertexai/gemini-2.5-flash` | - | Image / Text / Video: $0.30
Audio: $1.00 | Text: $2.50 |
+| [Google Gemini](/development/comfy-router/models/google/gemini/code) | `vertexai/gemini-2.5-pro` | - | Audio / Image / Text / Video: $1.25 | Text: $10.00 |
+| [Google Gemini](/development/comfy-router/models/google/gemini/code) | `vertexai/gemini-3.1-pro-preview` | - | Audio / Image / Text / Video: $2.00 | Text / Reasoning: $12.00
Image: $120.00 |
+| [Google Gemini](/development/comfy-router/models/google/gemini/code) | `vertexai/gemini-3.5-flash` | - | Audio / Image / Text / Video: $1.50 | Text / Reasoning: $9.00 |
+| [GPT 6 Astra](/development/comfy-router/models/openai/gpt-6-astra/code) | `openai/gpt-6-astra` | Read: $1.43
Write: $17.875 | Text: $14.30 | Text: $71.50 |
+| [GPT 6 Luna](/development/comfy-router/models/openai/gpt-6-luna/code) | `openai/gpt-6-luna` | Read: $0.0143
Write: $0.17875 | Text: $0.143 | Text: $0.715 |
+| [GPT 6 Sol](/development/comfy-router/models/openai/gpt-6-sol/code) | `openai/gpt-6-sol` | Read: $0.286
Write: $3.575 | Text: $2.86 | Text: $14.30 |
+| [GPT 5.6 Luna](/development/comfy-router/models/openai/gpt-5-6-luna/code) | `openai/gpt-5.6-luna` | Read: $0.10
Write: $1.25 | Text: $1.00 | Text: $6.00 |
+| [GPT 5.6 Sol](/development/comfy-router/models/openai/gpt-5-6-sol/code) | `openai/gpt-5.6-sol` | Read: $0.50
Write: $6.25 | Text: $5.00 | Text: $30.00 |
+| [GPT 5.6 Terra](/development/comfy-router/models/openai/gpt-5-6-terra/code) | `openai/gpt-5.6-terra` | Read: $0.25
Write: $3.125 | Text: $2.50 | Text: $15.00 |
+| [GPT 5.5](/development/comfy-router/models/openai/gpt-5-5/code) | `openai/gpt-5.5` | $0.50 | Text: $5.00 | Text: $30.00 |
+| [GPT 5.5 Pro](/development/comfy-router/models/openai/gpt-5-5-pro/code) | `openai/gpt-5.5-pro` | - | Text: $30.00 | Text: $180.00 |
+| [GPT 5](/development/comfy-router/models/openai/gpt-5/code) | `openai/gpt-5` | $0.125 | Text: $1.25 | Text: $10.00 |
+| [GPT 5 Mini](/development/comfy-router/models/openai/gpt-5-mini/code) | `openai/gpt-5-mini` | $0.025 | Text: $0.25 | Text: $2.00 |
+| [GPT 5 Nano](/development/comfy-router/models/openai/gpt-5-nano/code) | `openai/gpt-5-nano` | $0.005 | Text: $0.05 | Text: $0.40 |
+| [GPT 4.1](/development/comfy-router/models/openai/gpt-4-1/code) | `openai/gpt-4.1` | $0.50 | Text: $2.00 | Text: $8.00 |
+| [GPT 4.1 Mini](/development/comfy-router/models/openai/gpt-4-1-mini/code) | `openai/gpt-4.1-mini` | $0.10 | Text: $0.40 | Text: $1.60 |
+| [GPT 4.1 Nano](/development/comfy-router/models/openai/gpt-4-1-nano/code) | `openai/gpt-4.1-nano` | $0.025 | Text: $0.10 | Text: $0.40 |
+| [GPT 4o](/development/comfy-router/models/openai/gpt-4o/code) | `openai/gpt-4o` | $1.25 | Text: $2.50 | Text: $10.00 |
+| [o4-mini](/development/comfy-router/models/openai/o4-mini/code) | `openai/o4-mini` | $0.275 | Text: $1.10 | Text: $4.40 |
+| [o3](/development/comfy-router/models/openai/o3/code) | `openai/o3` | $0.50 | Text: $2.00 | Text: $8.00 |
+| [o1](/development/comfy-router/models/openai/o1/code) | `openai/o1` | $7.50 | Text: $15.00 | Text: $60.00 |
+| [o1-pro](/development/comfy-router/models/openai/o1-pro/code) | `openai/o1-pro` | - | Text: $150.00 | Text: $600.00 |
+| [Seed 2.0 Lite](/development/comfy-router/models/byteplus/seed-2-0-lite-260228/code) | `byteplus/seed-2-0-lite-260228` | $0.05 | Text: $0.25 | Text: $2.00 |
+| [Seed 2.0 Mini](/development/comfy-router/models/byteplus/seed-2-0-mini-260215/code) | `byteplus/seed-2-0-mini-260215` | $0.02 | Text: $0.10 | Text: $0.40 |
+| [Seed 2.0 Pro](/development/comfy-router/models/byteplus/seed-2-0-pro-260328/code) | `byteplus/seed-2-0-pro-260328` | $0.10 | Text: $0.50 | Text: $3.00 |
+
+#### Usage-based rates
+
+Comfy usage-based rates are billed per active GPU-second; total cost depends on runtime.
+
+| Name | Model ID | USD |
+| --- | --- | ---: |
+| [Chat Completions](/development/comfy-router/models/openrouter/chat-completions/code) | `openrouter/chat-completions` | $0.00126066 / GPU-second |
+
+#### Not published
+
+| Name | Model ID | USD |
+| --- | --- | ---: |
+| [Structured Instruction](/development/comfy-router/models/bria/structured-instruction/code) | `bria/structured-instruction` | Not published |
+
+
+
+
+
+
+
+
+
+
+
+
+#### Rates by duration
+
+| Name | Model ID | USD |
+| --- | --- | ---: |
+| [Eleven Sfx V2](/development/comfy-router/models/elevenlabs/eleven-sfx-v2/code) | `elevenlabs/eleven_sfx_v2` | $0.14 / minute |
+| [Seed Audio 1.0](/development/comfy-router/models/byteplus/seed-audio-1-0/code) | `byteplus/seed-audio-1.0` | $0.003575 / s |
+| [Seed Audio 1.0 Multilingual](/development/comfy-router/models/byteplus/seed-audio-1-0-multilingual/code) | `byteplus/seed-audio-1.0-multilingual` | $0.003575 / s |
+| [Starfish](/development/comfy-router/models/heygen/starfish/code) | `heygen/starfish` | $0.000954 / s |
+
+#### Rates per 1K characters
+
+| Name | Model ID | Option | USD |
+| --- | --- | --- | ---: |
+| [Eleven V3](/development/comfy-router/models/elevenlabs/eleven-v3/code) | `elevenlabs/eleven_v3` | Dialogue / Text to speech | $0.10 / 1K characters |
+
+#### Not published
+
+| Name | Model ID | USD |
+| --- | --- | ---: |
+| [Eleven V4](/development/comfy-router/models/elevenlabs/eleven-v4/code) | `elevenlabs/eleven_v4` | Not published |
+| [Eleven V4 Turbo](/development/comfy-router/models/elevenlabs/eleven-v4-turbo/code) | `elevenlabs/eleven_v4_turbo` | Not published |
+
+
+
+
+
+
+
+
+
+
+
+
+#### Rates per Meshy credit
+
+| Name | Model ID | USD |
+| --- | --- | ---: |
+| [Animations](/development/comfy-router/models/meshy/animations/code) | `meshy/animations` | $0.0572 / Meshy credit |
+| [Meshy 5](/development/comfy-router/models/meshy/meshy-5/code) | `meshy/meshy-5` | $0.0572 / Meshy credit |
+| [Meshy 6](/development/comfy-router/models/meshy/meshy-6/code) | `meshy/meshy-6` | $0.0572 / Meshy credit |
+| [Meshy 7](/development/comfy-router/models/meshy/meshy-7/code) | `meshy/meshy-7` | $0.0572 / Meshy credit |
+| [Meshy 7.1](/development/comfy-router/models/meshy/meshy-7-1/code) | `meshy/meshy-7.1` | $0.0572 / Meshy credit |
+| [Remesh](/development/comfy-router/models/meshy/remesh/code) | `meshy/remesh` | $0.0572 / Meshy credit |
+| [Rigging](/development/comfy-router/models/meshy/rigging/code) | `meshy/rigging` | $0.0572 / Meshy credit |
+
+#### Rates per operation
+
+| Name | Model ID | USD |
+| --- | --- | ---: |
+| [Hunyuan 3D Part](/development/comfy-router/models/tencent/hunyuan-3d-part/code) | `tencent/hunyuan-3d-part` | $0.60 / operation |
+| [Hunyuan 3D Texture Edit](/development/comfy-router/models/tencent/hunyuan-3d-texture-edit/code) | `tencent/hunyuan-3d-texture-edit` | $0.60 / operation |
+| [Hunyuan 3D Uv](/development/comfy-router/models/tencent/hunyuan-3d-uv/code) | `tencent/hunyuan-3d-uv` | $0.20 / operation |
+
+#### Usage-based rates
+
+Comfy usage-based rates are billed per active GPU-second; total cost depends on runtime.
+
+| Name | Model ID | USD |
+| --- | --- | ---: |
+| [Hunyuan 3D Smart Topology](/development/comfy-router/models/tencent/hunyuan-3d-smart-topology/code) | `tencent/hunyuan-3d-smart-topology` | $0.00126066 / GPU-second |
+
+
+
+
+
+
+
+
+Usage-based rates vary by request. Unpublished prices do not mean free usage.
+
+Prices updated: Oct 1, 2026.
diff --git a/ja/development/comfy-router/pricing.mdx b/ja/development/comfy-router/pricing.mdx
index 7cbe23d860..c46edc95ca 100644
--- a/ja/development/comfy-router/pricing.mdx
+++ b/ja/development/comfy-router/pricing.mdx
@@ -3,10 +3,10 @@ title: "Comfy Router の料金"
sidebarTitle: "料金"
description: "Comfy Router の画像、動画、テキスト、音声、3D モデルの料金を、クレジットまたは USD で提供プロバイダー別に比較できます。"
mode: "wide"
-translationSourceHash: aab87f52
+translationSourceHash: 228405a7
translationFrom: development/comfy-router/pricing.mdx
translationBlockHashes:
- "_intro": 957e452a
+ "_intro": ca5b67b7
---
import { PricingCurrencyToggle } from "../../../snippets/router-pricing-currency.jsx";
@@ -259,6 +259,15 @@ Comfy の使用量ベース料金は GPU 秒単位です。実際の料金は GP
| | | | 最大 | 79.125 | 129.765 | 214.165 |
+
+
+| 名前 | モデル ID | クレジット |
+| --- | --- | ---: |
+| [GPT Image 2](/ja/development/comfy-router/models/openai/gpt-image-2/code) | `openai/gpt-image-2` | 未公開 |
+| [Nano Banana 2](/ja/development/comfy-router/models/google/nano-banana-2/code) | `vertexai/gemini-3.1-flash-image` | 未公開 |
+| [Nano Banana Pro](/ja/development/comfy-router/models/google/nano-banana-pro/code) | `vertexai/gemini-3-pro-image` | 未公開 |
+
+
@@ -362,6 +371,8 @@ Comfy の使用量ベース料金は GPU 秒単位です。実際の料金は GP
| 名前 | モデル ID | クレジット |
| --- | --- | ---: |
| [Grok Imagine Video 1.5 Lite](/ja/development/comfy-router/models/xai/grok-imagine-video-1-5-lite/code) | `xai/grok-imagine-video-1.5-lite` | 未公開 |
+| [Pika 2.5 Image To Video](/ja/development/comfy-router/models) | `pika/pika-2.5-image-to-video` | 未公開 |
+| [Pika 2.5 Text To Video](/ja/development/comfy-router/models) | `pika/pika-2.5-text-to-video` | 未公開 |
| [Video To Video Resize](/ja/development/comfy-router/models/moonvalley/video-to-video-resize/code) | `moonvalley/video-to-video-resize` | 未公開 |
#### Kling クレジットあたりの料金
@@ -474,6 +485,13 @@ Comfy の使用量ベース料金は GPU 秒単位です。実際の料金は GP
| [Flashvsr](/ja/development/comfy-router/models/wavespeed/flashvsr/code) | `wavespeed/flashvsr` | 12.66 / 5 秒 | 18.99 / 5 秒 | 25.32 / 5 秒 | 33.76 / 5 秒 |
+
+
+| 名前 | モデル ID | クレジット |
+| --- | --- | ---: |
+| [Dreamina Seedance 2.0](/ja/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | `byteplus/dreamina-seedance-2-0-260128` | 未公開 |
+
+
@@ -866,6 +884,15 @@ Comfy の使用量ベース料金は GPU 秒単位です。実際の料金は GP
| | | | 最大 | $0.375 | $0.615 | $1.015 |
+
+
+| 名前 | モデル ID | USD |
+| --- | --- | ---: |
+| [GPT Image 2](/ja/development/comfy-router/models/openai/gpt-image-2/code) | `openai/gpt-image-2` | 未公開 |
+| [Nano Banana 2](/ja/development/comfy-router/models/google/nano-banana-2/code) | `vertexai/gemini-3.1-flash-image` | 未公開 |
+| [Nano Banana Pro](/ja/development/comfy-router/models/google/nano-banana-pro/code) | `vertexai/gemini-3-pro-image` | 未公開 |
+
+
@@ -969,6 +996,8 @@ Comfy の使用量ベース料金は GPU 秒単位です。実際の料金は GP
| 名前 | モデル ID | USD |
| --- | --- | ---: |
| [Grok Imagine Video 1.5 Lite](/ja/development/comfy-router/models/xai/grok-imagine-video-1-5-lite/code) | `xai/grok-imagine-video-1.5-lite` | 未公開 |
+| [Pika 2.5 Image To Video](/ja/development/comfy-router/models) | `pika/pika-2.5-image-to-video` | 未公開 |
+| [Pika 2.5 Text To Video](/ja/development/comfy-router/models) | `pika/pika-2.5-text-to-video` | 未公開 |
| [Video To Video Resize](/ja/development/comfy-router/models/moonvalley/video-to-video-resize/code) | `moonvalley/video-to-video-resize` | 未公開 |
#### Kling クレジットあたりの料金
@@ -1081,6 +1110,13 @@ Comfy の使用量ベース料金は GPU 秒単位です。実際の料金は GP
| [Flashvsr](/ja/development/comfy-router/models/wavespeed/flashvsr/code) | `wavespeed/flashvsr` | $0.06 / 5 秒 | $0.09 / 5 秒 | $0.12 / 5 秒 | $0.16 / 5 秒 |
+
+
+| 名前 | モデル ID | USD |
+| --- | --- | ---: |
+| [Dreamina Seedance 2.0](/ja/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | `byteplus/dreamina-seedance-2-0-260128` | 未公開 |
+
+
diff --git a/ko/development/comfy-router/pricing.mdx b/ko/development/comfy-router/pricing.mdx
index 734f7fb457..fe288ca8f1 100644
--- a/ko/development/comfy-router/pricing.mdx
+++ b/ko/development/comfy-router/pricing.mdx
@@ -3,10 +3,10 @@ title: "Comfy Router 요금"
sidebarTitle: "요금"
description: "Comfy Router의 이미지, 동영상, 텍스트, 오디오 및 3D 모델 요금을 제공자별로 크레딧 또는 USD로 비교하세요."
mode: "wide"
-translationSourceHash: aab87f52
+translationSourceHash: 228405a7
translationFrom: development/comfy-router/pricing.mdx
translationBlockHashes:
- "_intro": 957e452a
+ "_intro": ca5b67b7
---
import { PricingCurrencyToggle } from "../../../snippets/router-pricing-currency.jsx";
@@ -259,6 +259,15 @@ Comfy 사용량 기반 요금은 GPU 초당 요금입니다. 실제 청구액은
| | | | 최대 | 79.125 | 129.765 | 214.165 |
+
+
+| 이름 | 모델 ID | 크레딧 |
+| --- | --- | ---: |
+| [GPT Image 2](/ko/development/comfy-router/models/openai/gpt-image-2/code) | `openai/gpt-image-2` | 게시되지 않음 |
+| [Nano Banana 2](/ko/development/comfy-router/models/google/nano-banana-2/code) | `vertexai/gemini-3.1-flash-image` | 게시되지 않음 |
+| [Nano Banana Pro](/ko/development/comfy-router/models/google/nano-banana-pro/code) | `vertexai/gemini-3-pro-image` | 게시되지 않음 |
+
+
@@ -362,6 +371,8 @@ Comfy 사용량 기반 요금은 GPU 초당 요금입니다. 실제 청구액은
| 이름 | 모델 ID | 크레딧 |
| --- | --- | ---: |
| [Grok Imagine Video 1.5 Lite](/ko/development/comfy-router/models/xai/grok-imagine-video-1-5-lite/code) | `xai/grok-imagine-video-1.5-lite` | 게시되지 않음 |
+| [Pika 2.5 Image To Video](/ko/development/comfy-router/models) | `pika/pika-2.5-image-to-video` | 게시되지 않음 |
+| [Pika 2.5 Text To Video](/ko/development/comfy-router/models) | `pika/pika-2.5-text-to-video` | 게시되지 않음 |
| [Video To Video Resize](/ko/development/comfy-router/models/moonvalley/video-to-video-resize/code) | `moonvalley/video-to-video-resize` | 게시되지 않음 |
#### Kling 크레딧당 요금
@@ -474,6 +485,13 @@ Comfy 사용량 기반 요금은 GPU 초당 요금입니다. 실제 청구액은
| [Flashvsr](/ko/development/comfy-router/models/wavespeed/flashvsr/code) | `wavespeed/flashvsr` | 12.66 / 5초 | 18.99 / 5초 | 25.32 / 5초 | 33.76 / 5초 |
+
+
+| 이름 | 모델 ID | 크레딧 |
+| --- | --- | ---: |
+| [Dreamina Seedance 2.0](/ko/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | `byteplus/dreamina-seedance-2-0-260128` | 게시되지 않음 |
+
+
@@ -866,6 +884,15 @@ Comfy 사용량 기반 요금은 GPU 초당 요금입니다. 실제 청구액은
| | | | 최대 | $0.375 | $0.615 | $1.015 |
+
+
+| 이름 | 모델 ID | USD |
+| --- | --- | ---: |
+| [GPT Image 2](/ko/development/comfy-router/models/openai/gpt-image-2/code) | `openai/gpt-image-2` | 게시되지 않음 |
+| [Nano Banana 2](/ko/development/comfy-router/models/google/nano-banana-2/code) | `vertexai/gemini-3.1-flash-image` | 게시되지 않음 |
+| [Nano Banana Pro](/ko/development/comfy-router/models/google/nano-banana-pro/code) | `vertexai/gemini-3-pro-image` | 게시되지 않음 |
+
+
@@ -969,6 +996,8 @@ Comfy 사용량 기반 요금은 GPU 초당 요금입니다. 실제 청구액은
| 이름 | 모델 ID | USD |
| --- | --- | ---: |
| [Grok Imagine Video 1.5 Lite](/ko/development/comfy-router/models/xai/grok-imagine-video-1-5-lite/code) | `xai/grok-imagine-video-1.5-lite` | 게시되지 않음 |
+| [Pika 2.5 Image To Video](/ko/development/comfy-router/models) | `pika/pika-2.5-image-to-video` | 게시되지 않음 |
+| [Pika 2.5 Text To Video](/ko/development/comfy-router/models) | `pika/pika-2.5-text-to-video` | 게시되지 않음 |
| [Video To Video Resize](/ko/development/comfy-router/models/moonvalley/video-to-video-resize/code) | `moonvalley/video-to-video-resize` | 게시되지 않음 |
#### Kling 크레딧당 요금
@@ -1081,6 +1110,13 @@ Comfy 사용량 기반 요금은 GPU 초당 요금입니다. 실제 청구액은
| [Flashvsr](/ko/development/comfy-router/models/wavespeed/flashvsr/code) | `wavespeed/flashvsr` | $0.06 / 5초 | $0.09 / 5초 | $0.12 / 5초 | $0.16 / 5초 |
+
+
+| 이름 | 모델 ID | USD |
+| --- | --- | ---: |
+| [Dreamina Seedance 2.0](/ko/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | `byteplus/dreamina-seedance-2-0-260128` | 게시되지 않음 |
+
+
diff --git a/zh/development/comfy-router/pricing.mdx b/zh/development/comfy-router/pricing.mdx
index e918cd705e..68f7b324a9 100644
--- a/zh/development/comfy-router/pricing.mdx
+++ b/zh/development/comfy-router/pricing.mdx
@@ -3,10 +3,10 @@ title: "Comfy Router 定价"
sidebarTitle: "定价"
description: "按服务提供方比较 Comfy Router 图像、视频、文本、音频和 3D 模型的积分或 USD 价格。"
mode: "wide"
-translationSourceHash: aab87f52
+translationSourceHash: 228405a7
translationFrom: development/comfy-router/pricing.mdx
translationBlockHashes:
- "_intro": 957e452a
+ "_intro": ca5b67b7
---
import { PricingCurrencyToggle } from "../../../snippets/router-pricing-currency.jsx";
@@ -259,6 +259,15 @@ Comfy 的按用量计费费率按 GPU 秒计算。实际费用取决于 GPU 运
| | | | 最高 | 79.125 | 129.765 | 214.165 |
+
+
+| 名称 | 模型 ID | 积分 |
+| --- | --- | ---: |
+| [GPT Image 2](/zh/development/comfy-router/models/openai/gpt-image-2/code) | `openai/gpt-image-2` | 未发布 |
+| [Nano Banana 2](/zh/development/comfy-router/models/google/nano-banana-2/code) | `vertexai/gemini-3.1-flash-image` | 未发布 |
+| [Nano Banana Pro](/zh/development/comfy-router/models/google/nano-banana-pro/code) | `vertexai/gemini-3-pro-image` | 未发布 |
+
+
@@ -362,6 +371,8 @@ Comfy 的按用量计费费率按 GPU 秒计算。实际费用取决于 GPU 运
| 名称 | 模型 ID | 积分 |
| --- | --- | ---: |
| [Grok Imagine Video 1.5 Lite](/zh/development/comfy-router/models/xai/grok-imagine-video-1-5-lite/code) | `xai/grok-imagine-video-1.5-lite` | 未发布 |
+| [Pika 2.5 Image To Video](/zh/development/comfy-router/models) | `pika/pika-2.5-image-to-video` | 未发布 |
+| [Pika 2.5 Text To Video](/zh/development/comfy-router/models) | `pika/pika-2.5-text-to-video` | 未发布 |
| [Video To Video Resize](/zh/development/comfy-router/models/moonvalley/video-to-video-resize/code) | `moonvalley/video-to-video-resize` | 未发布 |
#### 每 Kling 积分费率
@@ -474,6 +485,13 @@ Comfy 的按用量计费费率按 GPU 秒计算。实际费用取决于 GPU 运
| [Flashvsr](/zh/development/comfy-router/models/wavespeed/flashvsr/code) | `wavespeed/flashvsr` | 12.66 / 5 秒 | 18.99 / 5 秒 | 25.32 / 5 秒 | 33.76 / 5 秒 |
+
+
+| 名称 | 模型 ID | 积分 |
+| --- | --- | ---: |
+| [Dreamina Seedance 2.0](/zh/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | `byteplus/dreamina-seedance-2-0-260128` | 未发布 |
+
+
@@ -866,6 +884,15 @@ Comfy 的按用量计费费率按 GPU 秒计算。实际费用取决于 GPU 运
| | | | 最高 | $0.375 | $0.615 | $1.015 |
+
+
+| 名称 | 模型 ID | USD |
+| --- | --- | ---: |
+| [GPT Image 2](/zh/development/comfy-router/models/openai/gpt-image-2/code) | `openai/gpt-image-2` | 未发布 |
+| [Nano Banana 2](/zh/development/comfy-router/models/google/nano-banana-2/code) | `vertexai/gemini-3.1-flash-image` | 未发布 |
+| [Nano Banana Pro](/zh/development/comfy-router/models/google/nano-banana-pro/code) | `vertexai/gemini-3-pro-image` | 未发布 |
+
+
@@ -969,6 +996,8 @@ Comfy 的按用量计费费率按 GPU 秒计算。实际费用取决于 GPU 运
| 名称 | 模型 ID | USD |
| --- | --- | ---: |
| [Grok Imagine Video 1.5 Lite](/zh/development/comfy-router/models/xai/grok-imagine-video-1-5-lite/code) | `xai/grok-imagine-video-1.5-lite` | 未发布 |
+| [Pika 2.5 Image To Video](/zh/development/comfy-router/models) | `pika/pika-2.5-image-to-video` | 未发布 |
+| [Pika 2.5 Text To Video](/zh/development/comfy-router/models) | `pika/pika-2.5-text-to-video` | 未发布 |
| [Video To Video Resize](/zh/development/comfy-router/models/moonvalley/video-to-video-resize/code) | `moonvalley/video-to-video-resize` | 未发布 |
#### 每 Kling 积分费率
@@ -1081,6 +1110,13 @@ Comfy 的按用量计费费率按 GPU 秒计算。实际费用取决于 GPU 运
| [Flashvsr](/zh/development/comfy-router/models/wavespeed/flashvsr/code) | `wavespeed/flashvsr` | $0.06 / 5 秒 | $0.09 / 5 秒 | $0.12 / 5 秒 | $0.16 / 5 秒 |
+
+
+| 名称 | 模型 ID | USD |
+| --- | --- | ---: |
+| [Dreamina Seedance 2.0](/zh/development/comfy-router/models/byteplus/dreamina-seedance-2-0-260128/code) | `byteplus/dreamina-seedance-2-0-260128` | 未发布 |
+
+
From 107c7f9c219ced55524a1626be841c272ddc6815 Mon Sep 17 00:00:00 2001
From: Matt Miller
Date: Mon, 12 Oct 2026 04:22:32 +0000
Subject: [PATCH 3/3] test(router-pricing): expect Pika as a serving provider
cloud@ad24085 publishes pika/* alias documents (x-comfy-router-alias-provider: pika)
and lists a pika leg on openai/gpt-image-2, so the generated pricing page now has a
Pika provider group. Update the exact provider-set expectations to match.
Co-Authored-By: Claude Opus 5.5
---
.github/scripts/snippets/gen-router-pricing.test.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/scripts/snippets/gen-router-pricing.test.ts b/.github/scripts/snippets/gen-router-pricing.test.ts
index c8a5fd7ec0..3ed367e992 100644
--- a/.github/scripts/snippets/gen-router-pricing.test.ts
+++ b/.github/scripts/snippets/gen-router-pricing.test.ts
@@ -60,8 +60,8 @@ describe("public Router pricing", () => {
const routes = catalog.flatMap((model) => model.providers.map((provider) => ({ model, provider })));
expect(catalog.length).toBeGreaterThan(0);
expect(routes.length).toBeGreaterThan(0);
- expect([...new Set(routes.map(({ provider }) => provider))].sort()).toEqual(["Comfy", "Higgsfield", "Runware", "WaveSpeed", "fal"].sort());
- expect([...new Set(tables.map((table) => table.provider))].sort()).toEqual(["Comfy", "Higgsfield", "Runware", "WaveSpeed", "fal"].sort());
+ expect([...new Set(routes.map(({ provider }) => provider))].sort()).toEqual(["Comfy", "Higgsfield", "Pika", "Runware", "WaveSpeed", "fal"].sort());
+ expect([...new Set(tables.map((table) => table.provider))].sort()).toEqual(["Comfy", "Higgsfield", "Pika", "Runware", "WaveSpeed", "fal"].sort());
for (const { model, provider } of routes) {
expect(page).toContain(`](/${model.page})`);
expect(page).toContain(`](/${model.page}) | \`${model.id}\` |`);
@@ -83,7 +83,7 @@ describe("public Router pricing", () => {
const providersFor = (id: string) => catalog.find((model) => model.id === id)!.providers;
expect(providersFor("fal/patina")).toEqual(["fal"]);
expect(providersFor("wavespeed/seedvr2")).toEqual(["WaveSpeed"]);
- expect(providersFor("openai/gpt-image-2")).toEqual(["Comfy", "fal", "Runware", "WaveSpeed"]);
+ expect(providersFor("openai/gpt-image-2")).toEqual(["Comfy", "fal", "Pika", "Runware", "WaveSpeed"]);
expect(providersFor("kling/kling-v3")).toEqual(["Comfy", "Higgsfield"]);
expect(page).not.toContain("Model ID prefixes identify model owners.");
expect(page).not.toContain("[See provider coverage]");