Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 54 additions & 14 deletions spec/router-openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ tags:
- name: Comfy Router
description: Comfy Router's canonical, model-ID-addressed routes.
paths:
/v1/models:
/v2/models:
get:
summary: List the models Comfy Router can run.
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 `/`.'
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 `/`.'
operationId: listRouterModels
tags:
- Comfy Router
Expand All @@ -45,7 +45,7 @@ paths:
$ref: '#/components/responses/RouterRequestError'
'503':
$ref: '#/components/responses/RouterRequestError'
/v1/models/{provider}/{model}:
/v2/models/{provider}/{model}:
get:
summary: Read one partner model's catalog entry by canonical model ID.
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.
Expand Down Expand Up @@ -84,6 +84,7 @@ paths:
parameters:
- $ref: '#/components/parameters/RouterProvider'
- $ref: '#/components/parameters/RouterModel'
- $ref: '#/components/parameters/RouterIdempotencyKey'
requestBody:
required: true
description: The partner model's native JSON input, forwarded to the provider unchanged.
Expand All @@ -93,18 +94,28 @@ paths:
$ref: '#/components/schemas/RouterModelInput'
responses:
'200':
description: OK - the partner model's native JSON output, returned unchanged.
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.'
headers:
X-Comfy-Request-Id:
$ref: '#/components/headers/RouterRequestIdHeader'
Idempotent-Replayed:
$ref: '#/components/headers/RouterIdempotentReplayedHeader'
content:
application/json:
schema:
$ref: '#/components/schemas/RouterModelOutput'
'400':
$ref: '#/components/responses/RouterRequestError'
'401':
$ref: '#/components/responses/RouterRequestError'
'403':
$ref: '#/components/responses/RouterRequestError'
'404':
$ref: '#/components/responses/RouterRequestError'
'409':
$ref: '#/components/responses/RouterIdempotencyConflict'
'413':
$ref: '#/components/responses/RouterRequestError'
'422':
$ref: '#/components/responses/RouterModelValidationError'
'429':
Expand All @@ -113,7 +124,7 @@ paths:
$ref: '#/components/responses/RouterRequestError'
'504':
$ref: '#/components/responses/RouterDeadlineExceeded'
/v1/models/{provider}/{model}/openapi.json:
/v2/models/{provider}/{model}/openapi.json:
get:
summary: Read one partner model's input schema as an OpenAPI document.
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.
Expand Down Expand Up @@ -189,7 +200,7 @@ components:
x-comfy-error-types:
- value: invalid_input
tier: request
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.
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.
- value: content_policy_violation
tier: request
meaning: 'The provider refused the request on content-policy grounds. The refusal is deterministic: re-sending the same input will be refused again.'
Expand All @@ -213,7 +224,7 @@ components:
meaning: The credential is valid but is not entitled to this model or this operation.
- value: concurrency_limit_exceeded
tier: transport
meaning: The workspace already has as many calls in flight as it is allowed; retry once one of them finishes.
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.'
- value: client_disconnected
tier: transport
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.'
Expand Down Expand Up @@ -255,10 +266,10 @@ components:
format: uri
pattern: ^https://
maxLength: 2048
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.'
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.'
RouterModelId:
type: string
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.
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.
pattern: ^[a-z0-9]+([._-][a-z0-9]+)*/[a-z0-9]+([._-][a-z0-9]+)*$
maxLength: 193
example: fal-ai/flux-pro
Expand All @@ -268,7 +279,7 @@ components:
additionalProperties: true
RouterModelInputSchemaDocument:
type: object
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.
description: A standalone OpenAPI document describing ONE Comfy Router model's input - the body `POST /v2/models/{provider}/{model}` accepts for that model. It is what `GET /v2/models/{provider}/{model}/openapi.json` returns.
additionalProperties: true
RouterModelListEntry:
type: object
Expand Down Expand Up @@ -396,7 +407,20 @@ components:
schema:
$ref: '#/components/schemas/RouterErrorResponse'
RouterDeadlineExceeded:
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}`.
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}`.
headers:
X-Comfy-Error-Type:
$ref: '#/components/headers/RouterErrorTypeHeader'
X-Comfy-Request-Id:
$ref: '#/components/headers/RouterRequestIdHeader'
Retry-After:
$ref: '#/components/headers/RouterRetryAfterHeader'
content:
application/json:
schema:
$ref: '#/components/schemas/RouterErrorResponse'
RouterIdempotencyConflict:
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`.'
headers:
X-Comfy-Error-Type:
$ref: '#/components/headers/RouterErrorTypeHeader'
Expand All @@ -409,7 +433,7 @@ components:
schema:
$ref: '#/components/schemas/RouterErrorResponse'
RouterModelValidationError:
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.
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.'
headers:
X-Comfy-Error-Type:
$ref: '#/components/headers/RouterErrorTypeHeader'
Expand Down Expand Up @@ -447,6 +471,16 @@ components:
type: integer
maximum: 100
default: 20
RouterIdempotencyKey:
name: Idempotency-Key
in: header
required: false
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.'
schema:
type: string
minLength: 1
maxLength: 255
example: 6f1a1a6e-6a53-4a5f-9d3a-2b3b0a1f9c21
RouterModel:
name: model
in: path
Expand Down Expand Up @@ -491,6 +525,12 @@ components:
required: true
schema:
$ref: '#/components/schemas/RouterErrorType'
RouterIdempotentReplayedHeader:
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.
required: false
schema:
type: boolean
example: true
RouterRequestIdHeader:
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.
required: true
Expand All @@ -499,7 +539,7 @@ components:
format: uuid
example: 6f1a1a6e-6a53-4a5f-9d3a-2b3b0a1f9c21
RouterRetryAfterHeader:
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.'
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.'
required: false
schema:
type: integer
Expand All @@ -512,7 +552,7 @@ components:
type: string
example: private, max-age=300, must-revalidate
RouterSchemaETagHeader:
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.
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.
required: true
schema:
type: string
Expand Down
Loading