Read one partner model's catalog entry by canonical model ID.
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.
The path is the SAME canonical model ID that post above invokes, on the same path template: one string addresses both routes, so there is no second ID alphabet to keep in step. provider and model are governed by the same RouterProvider / RouterModel parameters, with the same alphabet. That template is TWO segments; a catalog id carrying a third variant segment addresses neither route, because how a variant is addressed over HTTP is not settled by this contract (see RouterProvider and RouterModelListEntry.id).
The 200 body is a SUPERSET of the entry GET /v1/models returns for the same model: RouterModelDetail composes the list entry RouterModelListEntry by reference rather than restating its fields, so the listing and the detail cannot drift into two shapes for one model.
An ID that resolves to no model returns 404 with error_type: model_not_found - the same Router-owned body, the same bucket on X-Comfy-Error-Type, and the same fuzzy detail suggestions the invocation route returns for that ID. Both routes resolve through one catalog, so they cannot disagree about the same typo. Those suggestions are drawn from the models the CALLER is entitled to see: a miss on an unauthenticated or unentitled ID must not name models the caller could not otherwise enumerate. This is a requirement on the handler story (see drip/codegen.yaml), not something this contract can enforce.
The read sits behind the SAME enablement gate as post above, consulted before the catalog is touched: a caller Comfy Router is not switched on for gets 403 with error_type: not_enabled and learns nothing about which models exist - not even through a 404’s suggestions - and a gate that could not be evaluated answers 503 with error_type: service_unavailable. Both carry the Router-owned body and the bucket on X-Comfy-Error-Type, exactly as on post.
This operation is deliberately tagged Comfy Router and NOT API Nodes, for the reason given on post above.
Authorizations
Bearer token authentication. Normally a Firebase or Cloud JWT. A 'comfyui-' prefixed API key is ALSO accepted here on operations served by the comfyFirebase auth middleware: the prefix classifies the value as an API key and it is validated exactly as if sent in X-API-Key (BE-9720, parity with ingest).
Path Parameters
Lowercase provider segment of the canonical {provider}/{model}[/{variant}] model ID - the partner whose model is being run.
The schema is RouterProviderSegment, the SAME component a catalog entry's provider field references, so an ID GET /v1/models lists cannot drift from the ids this route accepts. Its pattern is a CONTRACT statement, not enforcement: comfy-api installs no OpenAPI request validator and oapi-codegen binds path parameters as plain strings, so the handler must re-validate this segment itself before using it to select a provider or compose an upstream URL.
Lowercase provider segment of the canonical {provider}/{model}[/{variant}] model ID - the partner whose model is being addressed. The invocation route's provider path parameter and a catalog entry's provider field both reference this one schema, which is what keeps the listed IDs and the accepted IDs from drifting apart.
The pattern is a CONTRACT statement, not enforcement: comfy-api installs no OpenAPI request validator and oapi-codegen binds path parameters as plain strings, so a handler must re-validate the segment itself before using it to select a provider or compose an upstream URL. The alphabet deliberately admits no /, no percent-encoding, and no repeated separator, so no accepted value can contain a . or .. path segment.
64^[a-z0-9]+([._-][a-z0-9]+)*$"fal-ai"
Lowercase model segment of the canonical {provider}/{model}[/{variant}] model ID - the model to run within that provider.
As with provider, the schema is the shared RouterModelSegment component and its pattern documents the contract rather than enforcing it - see RouterProvider.
Lowercase model segment of the canonical {provider}/{model}[/{variant}] model ID - the model to run within that provider. Shared by the invocation route's model path parameter and a catalog entry's model field, for the same no-drift reason as RouterProviderSegment.
As with the provider segment, the pattern documents the contract and does not enforce it. Dots are permitted inside the segment because partner model IDs use them for versions (flux-1.1-pro), but a repeated separator is not, so .. cannot appear.
128^[a-z0-9]+([._-][a-z0-9]+)*$"flux-pro"
Response
OK - the model's catalog entry.
Per-model detail for one Comfy Router model: everything the catalog listing reports for it, plus the per-model fields that only the single-model route carries.
It is allOf: [RouterModelListEntry, RouterModelDetailFields] so that the shared half IS the list entry rather than a copy of it - see the section comment above. A generated client can therefore treat a detail response as a list entry everywhere a list entry is expected.
Per-model billing FACTS a caller needs before invoking - not prices. Usage and cost figures never appear here.
It is an object with one member rather than a flat sibling field because more pre-invocation billing facts are coming and a flat billing_charges_* family would have to be un-flattened later; a nested object absorbs them without a breaking rename. The member is required for the reason its own description gives - "we did not say" and "we do not charge" must not be the same wire state.
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.
TestRouterCatalogIdsMatchInvocationRoute probes all three patterns behaviourally against one corpus, so loosening or tightening any of them alone fails CI rather than silently letting the catalog advertise an ID the invocation route would reject. The optional variant third segment is deliberately absent: how a variant is addressed is not settled by the invocation contract, so the catalog must not list an ID that route is not yet defined to accept. ONE bound does not survive the composition: maxLength here is the TOTAL, so a 193-character ID made of a 100-character provider and a 92-character model satisfies this schema while its provider segment exceeds RouterProviderSegment's own 64. A single pattern cannot express a per-segment length bound - the structural alphabet and a character count are not jointly expressible without lookahead, which this repo's Go-side pattern probes cannot compile. The per-segment bounds are therefore carried by the sibling provider and model fields of RouterModelListEntry, which reference the bounded segment schemas directly, so no CONFORMING entry can carry such an id. TestRouterCatalogIdsMatchInvocationRoute pins that this residual is length-only: over-length segments are in its corpus, and any divergence that is not purely a per-segment length overrun fails.
193^[a-z0-9]+([._-][a-z0-9]+)*/[a-z0-9]+([._-][a-z0-9]+)*$"fal-ai/flux-pro"
Lowercase model segment of the canonical {provider}/{model}[/{variant}] model ID - the model to run within that provider. Shared by the invocation route's model path parameter and a catalog entry's model field, for the same no-drift reason as RouterProviderSegment.
As with the provider segment, the pattern documents the contract and does not enforce it. Dots are permitted inside the segment because partner model IDs use them for versions (flux-1.1-pro), but a repeated separator is not, so .. cannot appear.
128^[a-z0-9]+([._-][a-z0-9]+)*$"flux-pro"
Lowercase provider segment of the canonical {provider}/{model}[/{variant}] model ID - the partner whose model is being addressed. The invocation route's provider path parameter and a catalog entry's provider field both reference this one schema, which is what keeps the listed IDs and the accepted IDs from drifting apart.
The pattern is a CONTRACT statement, not enforcement: comfy-api installs no OpenAPI request validator and oapi-codegen binds path parameters as plain strings, so a handler must re-validate the segment itself before using it to select a provider or compose an upstream URL. The alphabet deliberately admits no /, no percent-encoding, and no repeated separator, so no accepted value can contain a . or .. path segment.
64^[a-z0-9]+([._-][a-z0-9]+)*$"fal-ai"
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.
It is an ABSOLUTE https URI, deliberately not a uri-reference. Clients dereference or render this value, and a relative reference would resolve against the caller's own base while a file:, data: or javascript: scheme would be scheme confusion in every generated SDK. If this field is ever populated from partner- or admin-supplied metadata, that constraint is the only thing standing between that metadata and the client, so a server MUST NOT emit a value this pattern rejects.
2048^https://