You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
description: 'Comfy Router''s model catalog - one page of the canonical model IDs that `POST /v1/models/{provider}/{model}` accepts. An SDK calls this on cold start to discover what is runnable, and the `model_not_found` suggestions come from the same catalog, so an ID listed here that then 404s on invocation would be worse than either failure alone. That agreement is structural rather than a promise: an entry''s `provider` and `model` are the two path segments of the invocation route and reference the SAME schema components that route''s path parameters do, and `id` is those two segments joined by `/`.'
21
+
description: 'Comfy Router''s model catalog - one page of the canonical model IDs that `POST /v2/models/{provider}/{model}` accepts. An SDK calls this on cold start to discover what is runnable, and the `model_not_found` suggestions come from the same catalog, so an ID listed here that then 404s on invocation would be worse than either failure alone. That agreement is structural rather than a promise: an entry''s `provider` and `model` are the two path segments of the invocation route and reference the SAME schema components that route''s path parameters do, and `id` is those two segments joined by `/`.'
22
22
operationId: listRouterModels
23
23
tags:
24
24
- Comfy Router
@@ -45,7 +45,7 @@ paths:
45
45
$ref: '#/components/responses/RouterRequestError'
46
46
'503':
47
47
$ref: '#/components/responses/RouterRequestError'
48
-
/v1/models/{provider}/{model}:
48
+
/v2/models/{provider}/{model}:
49
49
get:
50
50
summary: Read one partner model's catalog entry by canonical model ID.
51
51
description: Per-model detail for a single Comfy Router model, so a caller can check one model without walking the whole paginated catalog. The SDKs use it to look a model up immediately before invoking it.
description: The partner model's native JSON input, forwarded to the provider unchanged.
@@ -93,18 +94,28 @@ paths:
93
94
$ref: '#/components/schemas/RouterModelInput'
94
95
responses:
95
96
'200':
96
-
description: OK - the partner model's native JSON output, returned unchanged.
97
+
description: 'OK - the partner model''s native JSON output, returned unchanged. When this response was replayed from the record held against an `Idempotency-Key` rather than produced by running the model again, it carries `Idempotent-Replayed: true` and is not charged a second time.'
summary: Read one partner model's input schema as an OpenAPI document.
119
-
description: The per-model input schema for a single Comfy Router model, served as a standalone OpenAPI document, so a caller - an SDK, a codegen tool, or an agent - can discover a model's arguments without reading Comfy's prose docs. It is the discovery mechanism the SDK quickstart depends on.
129
+
summary: Read one partner model's input and output schemas as an OpenAPI document.
130
+
description: The per-model input AND output schemas for a single Comfy Router model, served as a standalone OpenAPI document, so a caller - an SDK, a codegen tool, or an agent - can discover a model's arguments, and the shape of what it returns, without reading Comfy's prose docs. It is the discovery mechanism the SDK quickstart depends on.
120
131
operationId: getRouterModelInputSchema
121
132
tags:
122
133
- Comfy Router
@@ -133,7 +144,7 @@ paths:
133
144
type: string
134
145
responses:
135
146
'200':
136
-
description: OK - the model's input schema, as a standalone OpenAPI document.
147
+
description: OK - the model's input AND output schemas, as a standalone OpenAPI document.
meaning: The request was rejected before it reached the model - a malformed body, a malformed or expired pagination cursor, or an input the model's own schema does not accept.
203
+
meaning: The request was rejected before it reached the model - a malformed body, a malformed or expired pagination cursor, an input the model's own schema does not accept, or an `Idempotency-Key` that cannot serve this request (already used for a different request - the method, the path and query, or the body differ - or already consumed by a call whose response cannot be replayed). Sent with `409` in the key cases and with `400`/`422` in the others; the status says which, and the key cases are the ones answered by using a NEW key rather than by editing the request.
193
204
- value: content_policy_violation
194
205
tier: request
195
206
meaning: 'The provider refused the request on content-policy grounds. The refusal is deterministic: re-sending the same input will be refused again.'
@@ -213,7 +224,7 @@ components:
213
224
meaning: The credential is valid but is not entitled to this model or this operation.
214
225
- value: concurrency_limit_exceeded
215
226
tier: transport
216
-
meaning: The workspace already has as many calls in flight as it is allowed; retry once one of them finishes.
227
+
meaning: 'The workspace already has as many calls in flight as it is allowed; retry once one of them finishes. It carries one further condition on the run route, on a `409` rather than the `429` above: another call is already in flight for the `Idempotency-Key` this request presented. Re-send the SAME key after `Retry-After` seconds to collect that call''s result.'
217
228
- value: client_disconnected
218
229
tier: transport
219
230
meaning: 'The caller closed the connection before Router could return a result. It is logged rather than delivered - there is no socket left to write it to - and it is an attribution, not a billing outcome: a provider generation that completed is billed regardless of whether the caller received the response.'
@@ -255,10 +266,10 @@ components:
255
266
format: uri
256
267
pattern: ^https://
257
268
maxLength: 2048
258
-
description: 'Pointer to this model''s input schema document - the description of the body `POST /v1/models/{provider}/{model}` accepts for this model. Only the POINTER is part of this contract: the document it addresses is authored separately. Absent when no schema has been authored for the model.'
269
+
description: 'Pointer to this model''s input schema document - the description of the body `POST /v2/models/{provider}/{model}` accepts for this model. Only the POINTER is part of this contract: the document it addresses is authored separately. Absent when no schema has been authored for the model.'
259
270
RouterModelId:
260
271
type: string
261
-
description: A canonical Comfy Router model ID, `{provider}/{model}` - exactly the value that addresses the model on `POST /v1/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.
272
+
description: 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.
description: A standalone OpenAPI document describing ONE Comfy Router model's input - the body `POST /v1/models/{provider}/{model}` accepts for that model. It is what `GET /v1/models/{provider}/{model}/openapi.json` returns.
282
+
description: A standalone OpenAPI document describing ONE Comfy Router model's input AND output - the body `POST /v2/models/{provider}/{model}` accepts for that model, under the operation's `requestBody`, and the body it returns, under that operation's `200` content. It is what `GET /v2/models/{provider}/{model}/openapi.json` returns. The component keeps its historical name, which predates the output half; the shape it describes is the whole document, not the input alone.
272
283
additionalProperties: true
273
284
RouterModelListEntry:
274
285
type: object
@@ -313,7 +324,7 @@ components:
313
324
- limit
314
325
RouterModelOutput:
315
326
type: object
316
-
description: 'A partner model''s native JSON output document, returned to the caller as-is. Its concrete shape is owned by the partner rather than by Comfy, so this is an open object: Router does not narrow, rename, or re-envelope the fields. It is a named component (never an inline anonymous object) because ComfyUI''s spec-driven codegen needs a class to generate.'
327
+
description: 'A partner model''s native JSON output document, returned to the caller as-is. Its concrete shape is owned by the partner rather than by Comfy, so this is an open object: Router does not narrow, rename, or re-envelope the fields. It is a named component (never an inline anonymous object) because ComfyUI''s spec-driven codegen needs a class to generate. For the concrete shape ONE model returns, read that model''s own document at `GET /v2/models/{provider}/{model}/openapi.json`, whose `200` carries the per-model output schema when Comfy has described it.'
317
328
additionalProperties: true
318
329
RouterModelSegment:
319
330
type: string
@@ -396,7 +407,20 @@ components:
396
407
schema:
397
408
$ref: '#/components/schemas/RouterErrorResponse'
398
409
RouterDeadlineExceeded:
399
-
description: Comfy stopped holding the connection at its own configured bound (`deadline_exceeded`). The body and the two headers are exactly `RouterRequestError`'s; what this adds is the optional `Retry-After`, present when a retry with the same `Idempotency-Key` will collect the generation that is still running rather than dispatch a new one. See the `504` on `POST /v1/models/{provider}/{model}`.
410
+
description: Comfy stopped holding the connection at its own configured bound (`deadline_exceeded`). The body and the two headers are exactly `RouterRequestError`'s; what this adds is the optional `Retry-After`, present when a retry with the same `Idempotency-Key` will collect the generation that is still running rather than dispatch a new one. See the `504` on `POST /v2/models/{provider}/{model}`.
description: 'The `Idempotency-Key` on this request is already held, and this request cannot be answered from its record. Two conditions share the status 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 faithful copy of its response to replay, or the copy it holds is content-encoded in a way this request did not accept - and the answer 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 body is `RouterErrorResponse` and the bucket is repeated on `X-Comfy-Error-Type`.'
description: The request reached the model and the model rejected its contents. The body is `RouterValidationErrorResponse`, the FastAPI `detail[]` shape, so each offending field keeps its own specific `type` and `ctx`. `X-Comfy-Error-Type` carries the coarse bucket for the whole response.
436
+
description: 'The request reached the model and the model rejected its contents. The body is `RouterValidationErrorResponse`, the FastAPI `detail[]` shape, so each offending field keeps its own specific `type` and `ctx`. `X-Comfy-Error-Type` carries the coarse bucket for the whole response. The check runs before any provider call, so this answer is never charged - but it IS the answer recorded against your `Idempotency-Key`: re-sending that key with the same body replays this `422`, and re-sending it with the corrected body is refused `409`. Send the corrected request under a NEW key.'
description: 'Caller-generated key that makes retrying ONE logical call safe. A call that reached the caller with an answer is recorded against its key for 24 hours, and a retry carrying the same key is answered from that record instead of dispatching - and charging - the provider a second time, marked `Idempotent-Replayed: true`. The guarantee is a BILLING one: a key is charged at most once. It is not a promise that a key is dispatched at most once, and it does not make a lost call resumable.'
479
+
schema:
480
+
type: string
481
+
minLength: 1
482
+
maxLength: 255
483
+
example: 6f1a1a6e-6a53-4a5f-9d3a-2b3b0a1f9c21
450
484
RouterModel:
451
485
name: model
452
486
in: path
@@ -491,6 +525,12 @@ components:
491
525
required: true
492
526
schema:
493
527
$ref: '#/components/schemas/RouterErrorType'
528
+
RouterIdempotentReplayedHeader:
529
+
description: Present and `true` when this response was served from an `Idempotency-Key`'s record rather than by running the model again. It carries the original call's status, body and content type, and it is not billed a second time - the charge settled when the original completed. The header is ABSENT on a fresh run rather than sent as `false`, so branch on its presence.
530
+
required: false
531
+
schema:
532
+
type: boolean
533
+
example: true
494
534
RouterRequestIdHeader:
495
535
description: Server-generated identifier for this call, present on EVERY Router response - success, 4xx and 5xx alike, because an error response is exactly when a user needs an id to quote in a support request. The SAME value is written into the call's usage/audit event, which is what lets a complaint about a charge be joined to the charge itself instead of searched for by timestamp.
496
536
required: true
@@ -499,7 +539,7 @@ components:
499
539
format: uuid
500
540
example: 6f1a1a6e-6a53-4a5f-9d3a-2b3b0a1f9c21
501
541
RouterRetryAfterHeader:
502
-
description: 'Seconds to wait before retrying the SAME request with the SAME `Idempotency-Key`. Present on a `deadline_exceeded` `504` only when Comfy holds a handle to a generation the provider is still running; the value is Router''s own poll interval, which is the one honest number this route has for "ask again later". Absent when there is nothing to collect: an unkeyed call, or a bound that expired before the provider accepted anything.'
542
+
description: 'Seconds to wait before retrying the SAME request with the SAME `Idempotency-Key`. It is set on the two answers such a retry can actually collect from: a `409` carrying `error_type: concurrency_limit_exceeded`, where the original call for that key is still running, and a `deadline_exceeded` `504`, where Comfy stopped holding the connection but still holds a handle to a generation the provider is running. In both cases the value is the interval Router itself would wait before asking again, which is the one honest number this route has for "ask again later". Absent when there is nothing to collect: an unkeyed call, a bound that expired before the provider accepted anything, or a `409` that refuses the key outright instead of asking the caller to wait.'
503
543
required: false
504
544
schema:
505
545
type: integer
@@ -512,7 +552,7 @@ components:
512
552
type: string
513
553
example: private, max-age=300, must-revalidate
514
554
RouterSchemaETagHeader:
515
-
description: Strong entity tag over the served document's bytes, for `GET /v1/models/{provider}/{model}/openapi.json`. A per-model schema changes rarely and an SDK re-fetches it often, so a caller should store this value and send it back as `If-None-Match` to get a `304` instead of the document.
555
+
description: Strong entity tag over the served document's bytes, for `GET /v2/models/{provider}/{model}/openapi.json`. A per-model schema changes rarely and an SDK re-fetches it often, so a caller should store this value and send it back as `If-None-Match` to get a `304` instead of the document.
0 commit comments