> ## Documentation Index
> Fetch the complete documentation index at: https://docs.comfy.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Run a partner model synchronously by canonical model ID.

> Comfy Router's canonical, model-ID-addressed entry point. The request body is the partner model's own native JSON input and the success response is that model's own native output — JSON for most models, but the partner's raw bytes (for example `audio/mpeg` for a text-to-speech model) for a model whose partner answers with a binary body: Router forwards both without imposing a Comfy-shaped envelope, so for most models a caller can move between the partner's API and Router by changing the host. The exception is a model whose partner operation takes an identifier in its request path: Router has the caller name it as a top-level body field and moves it into the partner path, so that request needs that one adaptation. This is the synchronous path: the response carries the finished result.
The path addresses a model by the canonical `{provider}/{model}` model ID. Both segments are lowercase. A third `variant` segment is RESERVED rather than part of the canonical ID: how a variant is addressed is not settled by this contract, so no published `pattern` accepts one.
For a model whose partner API names the model in the request body, Router sets that field to the path's model; a body that names a different model is refused with `invalid_input`.
The mirror of that also exists, for a model whose partner operation needs an identifier the two-segment model ID cannot carry: the caller names it as a top-level field of the request body, and Router moves it into the partner request PATH and removes it from the forwarded body, because the partner's own input schema does not declare it. `elevenlabs/eleven_multilingual_v2` is the first such model — it requires a top-level `voice_id`, which must be ASCII letters and digits only (A-Z, a-z, 0-9) and at most 64 characters. A body that omits it, names it twice, gives it a value that is not a string, gives it a value outside that alphabet, or gives it a value longer than 64 characters is refused with `invalid_input` before anything is dispatched or charged.
Router serves image results as re-hosted URLs on Comfy storage, so for an image model it OWNS the partner's `response_format` on the outbound request and coerces it to `url`. The field is still accepted for compatibility with the `/proxy/` surface and is not an error, but it does not select what a successful response carries: a `b64_json` request and a `url` request are answered identically, with URLs. Where an individual image cannot be re-hosted, its entry keeps xAI's own short-lived URL rather than a Comfy one — see the model's output schema. Naming the field twice in one body is refused with `invalid_input`, because which spelling the partner would read is not defined. An out-of-enum value is REFUSED rather than coerced, on this Router dispatch and on the `/proxy/` image routes alike; only the shape of the refusal differs. Here it is the per-field `422` pre-provider validation writes, because the input schema this surface publishes is the one it enforces. The `/proxy/` image routes coerce the field the same way rather than honouring it as written, and they refuse an out-of-enum value with a `400` at ingress.
This operation is deliberately tagged `Comfy Router` and NOT `API Nodes`. The `API Nodes` tag drives ComfyUI's Partner-Node Pydantic codegen, so reusing it here would couple the Router contract to that generator.



## OpenAPI

````yaml https://api.comfy.org/openapi post /v2/models/{provider}/{model}
openapi: 3.0.2
info:
  title: Comfy API
  version: '1.0'
servers:
  - url: https://api.comfy.org
security: []
tags:
  - description: Comfy Router's canonical, model-ID-addressed routes.
    name: Comfy Router
  - description: >-
      Signed-in users running Comfy-owned serverless apps with free runs and
      flat or per-second pricing (FE-2737).
    name: App Proxy
paths:
  /v2/models/{provider}/{model}:
    post:
      tags:
        - Comfy Router
      summary: Run a partner model synchronously by canonical model ID.
      description: >-
        Comfy Router's canonical, model-ID-addressed entry point. The request
        body is the partner model's own native JSON input and the success
        response is that model's own native output — JSON for most models, but
        the partner's raw bytes (for example `audio/mpeg` for a text-to-speech
        model) for a model whose partner answers with a binary body: Router
        forwards both without imposing a Comfy-shaped envelope, so for most
        models a caller can move between the partner's API and Router by
        changing the host. The exception is a model whose partner operation
        takes an identifier in its request path: Router has the caller name it
        as a top-level body field and moves it into the partner path, so that
        request needs that one adaptation. This is the synchronous path: the
        response carries the finished result.

        The path addresses a model by the canonical `{provider}/{model}` model
        ID. Both segments are lowercase. A third `variant` segment is RESERVED
        rather than part of the canonical ID: how a variant is addressed is not
        settled by this contract, so no published `pattern` accepts one.

        For a model whose partner API names the model in the request body,
        Router sets that field to the path's model; a body that names a
        different model is refused with `invalid_input`.

        The mirror of that also exists, for a model whose partner operation
        needs an identifier the two-segment model ID cannot carry: the caller
        names it as a top-level field of the request body, and Router moves it
        into the partner request PATH and removes it from the forwarded body,
        because the partner's own input schema does not declare it.
        `elevenlabs/eleven_multilingual_v2` is the first such model — it
        requires a top-level `voice_id`, which must be ASCII letters and digits
        only (A-Z, a-z, 0-9) and at most 64 characters. A body that omits it,
        names it twice, gives it a value that is not a string, gives it a value
        outside that alphabet, or gives it a value longer than 64 characters is
        refused with `invalid_input` before anything is dispatched or charged.

        Router serves image results as re-hosted URLs on Comfy storage, so for
        an image model it OWNS the partner's `response_format` on the outbound
        request and coerces it to `url`. The field is still accepted for
        compatibility with the `/proxy/` surface and is not an error, but it
        does not select what a successful response carries: a `b64_json` request
        and a `url` request are answered identically, with URLs. Where an
        individual image cannot be re-hosted, its entry keeps xAI's own
        short-lived URL rather than a Comfy one — see the model's output schema.
        Naming the field twice in one body is refused with `invalid_input`,
        because which spelling the partner would read is not defined. An
        out-of-enum value is REFUSED rather than coerced, on this Router
        dispatch and on the `/proxy/` image routes alike; only the shape of the
        refusal differs. Here it is the per-field `422` pre-provider validation
        writes, because the input schema this surface publishes is the one it
        enforces. The `/proxy/` image routes coerce the field the same way
        rather than honouring it as written, and they refuse an out-of-enum
        value with a `400` at ingress.

        This operation is deliberately tagged `Comfy Router` and NOT `API
        Nodes`. The `API Nodes` tag drives ComfyUI's Partner-Node Pydantic
        codegen, so reusing it here would couple the Router contract to that
        generator.
      operationId: RunRouterModel
      parameters:
        - $ref: '#/components/parameters/RouterProvider'
        - $ref: '#/components/parameters/RouterModel'
        - $ref: '#/components/parameters/RouterIdempotencyKey'
        - $ref: '#/components/parameters/ModelProvider'
        - $ref: '#/components/parameters/StrictMode'
        - $ref: '#/components/parameters/FallbackProvider'
        - $ref: '#/components/parameters/RejectUnknownFields'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RouterModelInput'
        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.
        required: true
      responses:
        '200':
          content:
            '*/*':
              schema:
                format: binary
                type: string
            application/json:
              schema:
                $ref: '#/components/schemas/RouterModelOutput'
          description: >-
            OK - without `model_provider`, or with `model_provider` and
            `strict_mode=false` (the default, translated back into this model's
            native contract when possible, falling back to the alternate
            provider's own raw response on a translation failure - logged, never
            silent), the shape is this model's own native output; with
            `strict_mode=true` it is the alternate provider's response returned
            unchanged. For most models that is JSON (`RouterModelOutput`); for a
            model whose partner answers a generation directly as bytes - the
            ElevenLabs audio models are the first in the catalog - it is those
            bytes, and the response carries the partner's own `Content-Type`
            (`audio/mpeg`, `audio/wav`, ...) rather than `application/json`. A
            client must branch on the response `Content-Type` and must not
            assume a JSON document; the per-model contract is published at `GET
            /v2/models/{provider}/{model}/openapi.json`. This response carries
            `X-Content-Type-Options: nosniff`, so a partner media type is taken
            at its word and never sniffed into something else. 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:
            Idempotent-Replayed:
              $ref: '#/components/headers/RouterIdempotentReplayedHeader'
            X-Comfy-Credits-Used:
              $ref: '#/components/headers/RouterCreditsUsedHeader'
            X-Comfy-Request-Id:
              $ref: '#/components/headers/RouterRequestIdHeader'
            X-Comfy-Router-Dropped-Params:
              $ref: '#/components/headers/RouterDroppedParamsHeader'
            X-Comfy-Router-Fallback-Provider:
              $ref: '#/components/headers/RouterFallbackProviderHeader'
            X-Comfy-Router-Provider:
              $ref: '#/components/headers/RouterProviderHeader'
            X-Committed-Spend-Current:
              $ref: '#/components/headers/CommittedSpendCurrentHeader'
            X-Committed-Spend-Limit:
              $ref: '#/components/headers/CommittedSpendLimitHeader'
            X-Committed-Spend-Remaining:
              $ref: '#/components/headers/CommittedSpendRemainingHeader'
            X-Content-Type-Options:
              $ref: '#/components/headers/RouterNoSniffHeader'
        '400':
          $ref: '#/components/responses/RouterRunRequestError'
        '401':
          $ref: '#/components/responses/RouterRequestError'
        '402':
          $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':
          $ref: '#/components/responses/RouterConcurrencyLimited'
        '502':
          $ref: '#/components/responses/RouterProviderError'
        '503':
          $ref: '#/components/responses/RouterRequestUnavailable'
        '504':
          $ref: '#/components/responses/RouterDeadlineExceeded'
      security:
        - BearerAuth: []
        - ApiKeyAuth: []
components:
  parameters:
    RouterProvider:
      description: >-
        Lowercase provider segment of the canonical `{provider}/{model}` 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 /v2/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.
      in: path
      name: provider
      required: true
      schema:
        $ref: '#/components/schemas/RouterProviderSegment'
    RouterModel:
      description: >-
        Lowercase model segment of the canonical `{provider}/{model}` 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`.
      in: path
      name: model
      required: true
      schema:
        $ref: '#/components/schemas/RouterModelSegment'
    RouterIdempotencyKey:
      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`. Keys are scoped to the workspace
        your credential carries, or to your user when it carries none - so the
        keyspace is shared by every member of a workspace rather than private to
        one caller. Make a key unique across the whole workspace, not just
        within your own client: a second member who reuses a key string is
        answered from the first member's record, or refused `409` if the request
        differs. Because the scope follows the credential and not the person, a
        credential that carries no workspace at all scopes to your user id
        instead - so retrying one logical call under a different credential can
        land in a different namespace, where it is dispatched and charged again.
        Retry with the credential you started with. A keyed request with no
        authenticated caller is refused `401`. 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.
        Some answers are recorded but not replayable for the full 24 hours, and
        the billing guarantee is the half that always holds: the key stays
        consumed - the retry never re-runs and never re-charges - but it is
        answered `409 invalid_input` instead of being served the original body.
        That happens whenever Comfy does not hold a copy of the response it can
        still stand behind; a response past the replay size cap and a result
        addressed by an asset URL Comfy does not host are the two you are most
        likely to meet. The second is the one worth planning for, because it
        looks like an ordinary success: which models answer with a Comfy-hosted
        asset link, how long one stays valid, and what a result carries when an
        individual asset could not be copied are stated in one place, under
        Result assets in the API reference, and this paragraph does not restate
        them. On a model that returns its result on the original call, an answer
        still holding a partner's own asset link is replayed for a few minutes -
        which is where a dropped connection puts an SDK's automatic same-key
        re-send, and while the partner's link is certainly still alive - and
        refused after that rather than replayed dead. So a prompt retry of a
        partially re-hosted result behaves exactly like any other replay, and
        only a later one meets the `409`. That short window is deliberately not
        offered on a model that submits and is polled, because there the partner
        may have minted the URL long before your call collected it and its
        remaining life is unknowable - and those models do not need it: a call
        cut off mid-generation keeps its key holding the generation, so the
        same-key retry collects the original result rather than a recorded copy
        of it. A response past the size cap has no window either and is refused
        from the start. The action on any of these `409 invalid_input` refusals
        is the same: use a new key. Only an answer a provider actually produced
        is recorded, though. A refusal Router raises on its own before
        dispatching anything - not enabled for you yet (`403`), unknown model
        (`404`), not entitled to the model (`403`), a body the model's schema
        rejects or that names a different model than the path (`422`), a
        malformed request (`400 invalid_input`) - dispatched nothing and charged
        nothing, so it releases the key: re-send the same key once you are on
        the rollout ramp or have corrected the request and it runs for real,
        rather than replaying the refusal or colliding with it as a `409`. That
        turns on whether a provider was reached, never on the status, so a `400
        content_policy_violation` - the partner's own answer to a call that ran,
        which some models meter - is recorded and replayed like any other
        answer. Releasing a refusal that dispatched nothing frees nothing
        chargeable, so it does not weaken the at-most-once billing guarantee
        above.

        Generate a fresh key per logical call and reuse it across that call's
        retry attempts; a UUID is the intended shape. Persist the key BEFORE
        sending the request - a key held only in the memory of the process that
        crashed cannot be resent, and the retry that would have collected the
        result becomes a fresh, separately charged run.

        A retry that arrives while the original is still in flight is answered
        `409` with `Retry-After`: wait and re-send the same key. The same key
        presented with a DIFFERENT request is a `409` too, rather than a silent
        overwrite - and "different" is judged on the whole request, not the body
        alone: the method, the path and query string, and the body must all
        match the original.

        THE KEY IS NEVER RETURNED IN ANY RESPONSE - not on the `200`, not on the
        `504`, not in any error body. It is yours to mint and yours to keep: a
        caller who did not keep it has no way to collect a generation they were
        billed for. A `deadline_exceeded` `504` on a generation the provider is
        still running answers a same-key retry with `504` and `Retry-After`
        again until it finishes, then with the result.

        Omitting the header is allowed and leaves the call unguarded rather than
        refused: there is nothing to check, so every retry is a fresh dispatch
        and a fresh charge.
      in: header
      name: Idempotency-Key
      schema:
        example: 6f1a1a6e-6a53-4a5f-9d3a-2b3b0a1f9c21
        maxLength: 255
        minLength: 1
        type: string
    ModelProvider:
      description: >-
        Selects an alternate provider to serve this model, instead of its
        current default. Omitting it runs native dispatch on the model's own
        default provider; `default`, `comfy`, and `comfyui` are aliases for that
        same native behavior and name no override, because Comfy Router is never
        itself a serving backend. The alternate providers Router can retarget a
        model onto are `fal`, `wavespeed`, `runware`, and `higgsfield`; which of
        them a given model supports is reported by `GET
        /v2/models/{provider}/{model}`. When an alternate provider is selected,
        the native request body is translated into that provider's real schema
        unless `strict_mode=true`; see `strict_mode` and `fallback_provider`.
        Its refusals are checked in a fixed order, and an earlier one answers
        whether or not a later one would. A value that is not a registered
        provider at all is refused `400` with `error_type: invalid_input`. Then
        a request whose body selects a multipart operation (an edit - for
        example gpt-image's `image` field) is refused `409`, also with
        `error_type: invalid_input`, whose `detail` begins "this request's
        `image` selects the edit operation" - but only when the named provider's
        translator for this model cannot itself serve that operation; a leg
        whose translator does carry the media is not refused and proceeds
        normally, so this is a per-leg refusal rather than a blanket one. Then a
        request that has already resolved a bring-your-own-key credential for
        the provider in the path is refused `409`, also with `error_type:
        invalid_input`, whose `detail` begins `this request resolved a BYOK
        credential` - see the `409` on this route, where that `detail` is the
        only thing separating this case, and the multipart case above, from the
        `Idempotency-Key` one. That BYOK check runs whether or not the named
        provider has a leg for this model. Then the named provider's own gate
        refuses `403` with `error_type: not_enabled` when that provider is not
        turned on for you, and `503` with `error_type: service_unavailable` when
        the gate cannot be evaluated - a flag-evaluation failure, or a missing
        or nil gate entry. Only past all of those is a real provider that does
        not serve this model refused `400` with `error_type: invalid_input`, the
        same answer as a value that is not a registered provider at all - both
        mean the `model_provider` value cannot serve this model, and that
        `400`'s `detail` is human-readable and not a contract, so read `GET
        /v2/models/{provider}/{model}` to learn which providers a model does
        support rather than parsing it; past that `400`, this workspace's
        partner-provider policy for the named vendor is evaluated too and can
        refuse `403` or `503` of its own. Neither this parameter nor
        `fallback_provider` is available on a BYOK request, and the two are
        unavailable in different ways: the credential was resolved for the
        provider named in the path while every alternate leg dispatches on
        Comfy's own key, so an explicit `model_provider` is refused with that
        `409`, and `fallback_provider` is inert rather than refused - no retry
        against an alternate provider is attempted and the first attempt's own
        failure is what the caller receives. The same split applies to a
        multipart body: only an explicit `model_provider` reaches the `409`
        above, and only for a leg whose translator cannot serve the operation,
        while the automatic on-failure retry `fallback_provider` controls is
        simply skipped (inert, not refused) for any multipart body. Router
        defines no `provider_not_available` or `validation_error` `error_type`:
        these conditions fold onto `invalid_input`. The `error_type` set can
        still grow, so treat any value you do not recognize as `internal_error`
        rather than switching exhaustively.
      in: query
      name: model_provider
      schema:
        type: string
    StrictMode:
      description: >-
        Only meaningful together with `model_provider`. `false` (the default):
        the request body must be this model's own native contract, translated to
        the alternate provider's real schema - any native field that cannot be
        expressed exactly is dropped and disclosed via the response's
        `X-Comfy-Router-Dropped-Params` header, never silently. `true`: the body
        must already be the alternate provider's own real schema, passed through
        unmodified in both directions - no translation, so
        `X-Comfy-Router-Dropped-Params` is never sent, and provider fallback is
        disabled for the call because a body shaped for one provider cannot be
        replayed against another (see `fallback_provider`).
      in: query
      name: strict_mode
      schema:
        default: false
        type: boolean
    FallbackProvider:
      description: >-
        Controls whether Router retries this call against another of the model's
        registered providers when the first attempt fails for a reason
        attributable to Router's own side or to the specific provider tried -
        never for a reason attributable to the request itself (an unretried
        failure is refused exactly as it always was). Omitted, or any value
        other than `false`, turns fallback on (the default) and Router scans the
        model's registered alternates in a fixed order and retries once against
        the first eligible one. `false` turns fallback off: a failure is
        refused, never retried. A successful fallback response carries the
        `X-Comfy-Router-Fallback-Provider` header, naming the provider that
        served it; a fallback attempt that itself also fails does not carry the
        header, and no case retries a generation that may already have been
        submitted to a provider. Fallback also turns itself off, regardless of
        this parameter, on a request that resolved a bring-your-own-key
        credential, on a request whose body selects a multipart operation, and
        under `strict_mode=true` - each binds the call to one specific provider
        and cannot be faithfully replayed against another: the alternate leg
        would dispatch on Comfy's own key rather than on the caller's
        credential, no alternate translator is guaranteed to carry the multipart
        media field, and a strict body is already shaped for one provider rather
        than for the native contract. Each of those is inert, not refused - no
        retry is attempted and the first attempt's own failure reaches the
        caller unchanged. Only an explicit `model_provider` is refused, and see
        that parameter for the `409`s it answers with; note that fallback's
        multipart disable is unconditional, while `model_provider`'s multipart
        `409` applies only to a leg whose translator cannot serve the operation.
      in: query
      name: fallback_provider
      schema:
        type: string
    RejectUnknownFields:
      description: >-
        Opt in to refusing a top-level request-body field this model's input
        schema does not declare, rather than accepting it; nested fields are not
        checked, and nothing is refused for a model whose schema allows
        undeclared fields or has no authored schema, or on a queued submit sent
        with `strict_mode=true`.

        Omitted, or `false` (the default): unchanged behaviour. Most model input
        schemas here are open, so a field Comfy has not authored is accepted and
        forwarded, which is what lets a field a partner already supports reach
        it before Comfy documents it. The cost is that a misspelled field name,
        or a nested field written at the top level, is accepted too - and then
        dropped on the way to the provider, so the generation runs, and is
        billed, under the defaults that field was meant to change.

        `true`: a top-level field the published input schema does not declare is
        answered 422 with an `extra_forbidden` entry naming it, before any
        provider call is made and before anything is billed. Only the top level
        is checked, and only names that match no declared property even
        case-insensitively - a field whose spelling differs from a declared one
        only by case is already refused (`property_name_case`) regardless of
        this parameter. A model with no authored input schema declares no
        properties, so it refuses nothing here.

        What it refuses is a FLOOR, not a ceiling, in two stated cases. A model
        whose published schema sets `additionalProperties` itself is left
        exactly as it is: where that is `false` the schema already refuses
        undeclared fields with or without this parameter, and where it is `true`
        - as on the Anthropic and Gemini Interactions models, whose schemas
        promise fields such as `tools`, `temperature` and `safety_settings` pass
        through unchanged - the schema has declared that an undeclared field is
        allowed, and this parameter does not overrule it. And where a schema is
        a union (`oneOf`/`anyOf`), a field belonging to any branch is accepted,
        not only to the branch the body matched.

        The parameter is per-request on purpose: closing every schema instead
        would start refusing bodies that succeed today, with no way for an
        existing caller to keep the behaviour they built against. It is accepted
        on both the synchronous `POST` and the queued `POST .../requests`
        submit, which validate the same body against the same schema. The one
        exception is `strict_mode=true` on the queued submit: that body is
        already in the alternate provider's own shape, so the queue does not
        validate it against this model's schema, and this parameter is accepted
        but has no effect there.
      in: query
      name: reject_unknown_fields
      schema:
        default: false
        type: boolean
  schemas:
    RouterModelInput:
      additionalProperties: true
      description: >-
        A partner model's native JSON input document, forwarded to the provider
        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.

        Declaring an object here is a codegen requirement, not a licence to
        re-decode the payload: numeric fidelity is the handler's job, since
        decoding into a Go `map[string]any` rounds integers above 2^53 (partner
        seeds and IDs) to `float64`. Forward the raw bytes rather than
        round-tripping them through a generic map. Body size is likewise bounded
        at the handler - comfy-api caps comparable bodies with
        `http.MaxBytesReader` - not by this schema.
      type: object
    RouterModelOutput:
      additionalProperties: true
      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.

        Declaring an object here is a codegen requirement, not a licence to
        re-decode the payload: numeric fidelity is the handler's job, since
        decoding into a Go `map[string]any` rounds integers above 2^53 (partner
        seeds and IDs) to `float64`. Forward the raw bytes rather than
        round-tripping them through a generic map. Body size is likewise bounded
        at the handler - comfy-api caps comparable bodies with
        `http.MaxBytesReader` - not by this schema.
      type: object
    RouterProviderSegment:
      description: >-
        Lowercase `provider` segment of the canonical `{provider}/{model}` model
        ID - the partner whose model is being addressed. The invocation route's
        `provider` path parameter and a catalog entry's `provider` field both
        reference this one schema, which is what keeps the listed IDs and the
        accepted IDs from drifting apart.

        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.
      example: bfl
      maxLength: 64
      pattern: ^[a-z0-9]+([._-][a-z0-9]+)*$
      type: string
    RouterModelSegment:
      description: >-
        Lowercase `model` segment of the canonical `{provider}/{model}` model ID
        - the model to run within that provider. Shared by the invocation
        route's `model` path parameter and a catalog entry's `model` field, for
        the same no-drift reason as `RouterProviderSegment`.

        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-pro-1.1`), but a repeated
        separator is not, so `..` cannot appear.
      example: flux-2-pro
      maxLength: 128
      pattern: ^[a-z0-9]+([._-][a-z0-9]+)*$
      type: string
    RouterErrorResponse:
      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:
          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 `<id> was
            retired on <YYYY-MM-DD>; use <successor>` when a successor is
            recorded and visible to the caller, or just `<id> was retired on
            <YYYY-MM-DD>` 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, because
            `error_type` cannot express either: 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 resolved
            a BYOK credential` is the BYOK `model_provider` refusal; a `detail`
            beginning "this request's `image` selects the edit operation" is the
            multipart `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.
          type: string
        error_type:
          $ref: '#/components/schemas/RouterErrorType'
        refusal_subject:
          description: >-
            Which input or output a content-policy refusal was about, as a
            Router-level closed vocabulary: `input`, `output`, `input_text`,
            `input_image`, `input_video`, `input_audio`, `output_text`,
            `output_image`, `output_video`, `output_audio`. The bare `input` /
            `output` values name the side when the provider did not name a
            modality. Present only when `error_type` is
            `content_policy_violation` and the provider named the refused
            subject with a machine-readable code; absent otherwise. Never
            provider text. Named today for BytePlus, Runway, BFL, Gemini, Veo,
            Vertex, xAI and Wan refusals, and for the MiniMax Router's Nano
            Banana legs' Gemini refusals; a provider whose refusal does not say
            which side it was about leaves it absent. It mirrors the
            `X-Comfy-Refusal-Subject` header.
          type: string
        upstream_detail:
          description: >-
            A bounded, sanitized reason the model provider gave for rejecting
            the request, present only when `error_type` is `invalid_input` and
            `X-Comfy-Upstream-Status` is a provider `4xx` or `2xx` - i.e. the
            provider refused the request as malformed and said why. Usually that
            status is a `4xx`; it is a `2xx` for a provider that reports a
            rejected generation inside a success envelope (a BytePlus
            failed-task poll is HTTP `200` with the reason in its body). Absent
            on every other failure, including provider `5xx`, transport
            failures, content-policy refusals and any refusal Router raised
            about itself. It mirrors the `X-Comfy-Upstream-Detail` header.

            Unlike `detail`, this is provider-authored text, so it is
            length-capped and stripped of URLs, hostnames, emails, tokens and
            id-shaped values before it is surfaced; it is never the raw provider
            body. It is a human-readable hint, not machine-parsed - branch on
            `error_type` instead.
          type: string
      required:
        - detail
        - error_type
      type: object
    RouterValidationErrorResponse:
      description: >-
        Router's model-level `422` body, in the FastAPI form: the request was
        well-formed enough to reach the model and the model rejected its
        contents. Note it carries no `error_type` of its own - that is what
        `X-Comfy-Error-Type` on the response is for, so a client can read the
        coarse bucket off the header without first deciding which of the two
        Router error bodies it received.
      properties:
        detail:
          description: >-
            Every validation failure found on the request, one entry per
            offending field.
          items:
            $ref: '#/components/schemas/RouterValidationErrorDetail'
          type: array
      required:
        - detail
      type: object
    RouterErrorType:
      description: >-
        Coarse, machine-readable bucket for a Router failure, mirrored on the
        `X-Comfy-Error-Type` response header so a caller can branch without
        parsing the body. The set is closed at nineteen values: the six
        request-level buckets `invalid_input`, `content_policy_violation`,
        `provider_error`, `provider_timeout`, `insufficient_credits` and
        `model_not_found`, plus the transport-level `unauthorized`, `forbidden`,
        `concurrency_limit_exceeded`, `client_disconnected`, `internal_error`,
        `deadline_exceeded`, `not_enabled`, `service_unavailable`,
        `rate_limited`, `cancelled`, `queue_timeout`, `request_not_found` and
        `queue_backlog_full`. Closed describes the set as documented today, not
        a bound that holds forever: the set is expected to grow, which is why
        this is deliberately a plain string and not an `enum`, so a client must
        treat an unrecognised value as `internal_error` rather than switch
        exhaustively over the list above and break on the next addition.

        RETRY SEMANTICS. `not_enabled` is TERMINAL - the same request will be
        refused the same way, so a client must not retry it - while
        `service_unavailable` is TRANSIENT and SHOULD be retried with backoff.
        That opposition is the whole reason they are two buckets rather than
        one: a retry policy keys off the status/`error_type` pair, and a single
        shared bucket would make retry wrong in one direction or the other.

        `not_enabled` and `forbidden` both return `403` and, like the `504` pair
        below, the difference between them is the one the status cannot carry.
        `forbidden` means the credential is valid but is not entitled to this
        model or operation - a decision about the caller. `not_enabled` means
        Comfy Router is not switched on for the caller yet - a state of the
        product rollout, not a judgement about them, and one that changes
        without the caller doing anything. `404` would have claimed the model
        does not exist when it does, and a `5xx` would have blamed the server
        for a deliberate state, so neither was available as a way to separate
        the two by status instead.

        HOW OFTEN `not_enabled` IS SEEN CHANGES OVER TIME, AND IT NEVER STOPS
        BEING VALID. While Comfy Router is rolling out, most callers are not on
        the ramp yet and this is the ordinary answer for them. Once Router is
        fully rolled out it becomes rare. It does not become impossible: the
        deploy-time switch that turns Router off for an environment is a
        permanent operational lever, so `not_enabled` is still the correct
        answer the day it is pulled. It is therefore a value Comfy expects to
        STOP EMITTING often rather than one that is ever withdrawn - a published
        bucket is never removed, because removing it would delete the generated
        exception class an SDK built from it and break any client branching on
        it. `service_unavailable` has no such arc at all: a dependency can
        always fault.

        `deadline_exceeded` and `provider_timeout` both return `504` and the
        difference between them is which SIDE ran out of time, which is not
        cosmetic: `provider_timeout` means the upstream model provider ran out
        of time, while `deadline_exceeded` means COMFY stopped holding the
        connection at its own configured bound. A client that collapses the two
        onto the status alone loses that distinction, and with it the difference
        between "the partner is failing" and "the call is longer than the
        connection Comfy will hold".

        NEITHER BUCKET IS A STATEMENT ABOUT THE CHARGE. Comfy's charges settle
        on COMPLETION: a generation the provider completed is billed whether or
        not the caller was still connected to receive the response, and a
        generation that failed or never completed is not billed. Reaching
        `deadline_exceeded` or `client_disconnected` therefore does not tell a
        caller they were not charged.

        `cancelled` IS THE SAME KIND OF VALUE and it is the one most often read
        as a refund. Cancelling asks Comfy to stop waiting; it does not stop a
        partner that has already accepted the generation, so a request cancelled
        while it was still `IN_QUEUE` was never dispatched and cannot be
        charged, whereas one cancelled after it was admitted may still settle a
        charge. The bucket names what happened to the REQUEST, never what
        happened to the bill.

        It is deliberately a plain string rather than an `enum`: the set is
        expected to grow -- it already has, most recently with the
        queued-delivery buckets `cancelled`, `queue_timeout` and
        `request_not_found`, and `file_download_error` is named as a further
        addition -- and a generated client that hard-rejects an unrecognized
        bucket would fail hardest exactly when something has already gone wrong.
        Treat an unknown value as `internal_error`. Bucketing loses no
        granularity - the specific provider-level reason survives in
        `RouterValidationErrorDetail.type` and its `ctx`.
      example: invalid_input
      type: string
      x-comfy-error-types:
        - 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.
          tier: request
          value: invalid_input
        - meaning: >-
            The provider refused the request on content-policy grounds. The
            refusal is deterministic: re-sending the same input will be refused
            again.
          tier: request
          value: content_policy_violation
        - meaning: >-
            The partner provider reported a failure of its own, or returned a
            response Router could not interpret as a result.
          tier: request
          value: provider_error
        - meaning: >-
            The partner provider did not answer within its deadline. This bucket
            is the provider timing out and never Router's own server deadline,
            which is reported as `deadline_exceeded` - the two share `504` and
            are separated because they name different causes: this one says the
            partner failed, that one says Comfy stopped holding the connection.
          tier: request
          value: provider_timeout
        - meaning: The calling workspace does not have enough credits to run the model.
          tier: request
          value: insufficient_credits
        - meaning: >-
            The `{provider}/{model}` ID names no model Router can run: an
            unknown provider, an unenrolled id, one the catalog has retired past
            its sunset date, or a catalogued ID the provider does not currently
            serve for Comfy. A retired id's `detail` reads `<id> was retired on
            <YYYY-MM-DD>; use <successor>` when a successor is recorded and not
            withheld, or just `<id> was retired on <YYYY-MM-DD>` when it is not,
            instead of a suggestion list; the successor named for a retired id
            is always a live catalog ID you can re-point the call at, and a
            successor is dropped from `detail` either because the table records
            none, or because the recorded one is currently withheld behind an
            embargo the caller cannot see. A model whose sunset is still in the
            future is not withdrawn: it stays listed by `GET /v2/models` and
            stays runnable until the date, so a listing taken today remains the
            answer to what runs today. Otherwise `detail` carries up to three
            suggestions drawn from the models the caller is entitled to see,
            except for a catalogued ID the provider does not currently serve for
            Comfy, whose response carries no suggestions.
          tier: request
          value: model_not_found
        - meaning: The request carried no usable credential.
          tier: transport
          value: unauthorized
        - meaning: >-
            The credential is valid but is not entitled to this model or this
            operation.
          tier: transport
          value: forbidden
        - 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.
          tier: transport
          value: concurrency_limit_exceeded
        - 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.
          tier: transport
          value: client_disconnected
        - meaning: >-
            Router itself failed. It is also the value a client should treat any
            unrecognized bucket as, so a later addition to the set does not
            break a client generated before it.
          tier: transport
          value: internal_error
        - meaning: >-
            Comfy stopped holding the connection at its own configured bound
            before an answer arrived. It shares `504` with `provider_timeout`
            and the pair says which side ran out of time; this one is Comfy's
            own bound, so nothing about the request was rejected and the same
            request may be retried. It says nothing about the charge: a provider
            generation that completed is billed regardless of whether the caller
            received the response. Retry it with the same `Idempotency-Key`:
            when the provider had already accepted the generation, the retry
            collects that generation rather than dispatching another, and a
            `Retry-After` on the `504` says when to ask.
          tier: transport
          value: deadline_exceeded
        - meaning: >-
            Comfy Router is not switched on for this caller yet. Nothing about
            the request is wrong and the model exists, which is why this is not
            `model_not_found`; it shares `403` with `forbidden` and is not the
            same thing, because `forbidden` is an entitlement decision about the
            caller while this is a state of the rollout. It is terminal: do not
            retry, and do not treat it as an outage. The one exception to "about
            the caller" is the queued submit, which also answers `not_enabled`
            for a model whose partner answers a generation directly as bytes:
            that model cannot yet be queued, so it is the model and not the
            caller that is refused, nothing is queued or charged, and the
            synchronous route `POST /v2/models/{provider}/{model}` runs it
            instead.
          tier: transport
          value: not_enabled
        - meaning: >-
            A service Comfy Router depends on is temporarily unavailable and the
            caller did nothing wrong. Retry it with backoff: it is the one
            bucket here whose condition clears on its own, without the caller
            changing the request and without a concurrency slot freeing, which
            is what distinguishes it from the other retryable answers
            (`concurrency_limit_exceeded`, `deadline_exceeded`). It is separate
            from `internal_error` - which is a `500` and means Router itself
            failed - so a client can tell "come back shortly" from "this call is
            not going to work".
          tier: transport
          value: service_unavailable
        - meaning: >-
            The caller has spent an allowance measured over a window and must
            wait for that window to roll. It shares `429` with
            `concurrency_limit_exceeded` and is not the same thing: that one
            clears the moment one of the caller's own in-flight calls finishes,
            so retrying in seconds is right, whereas nothing the caller does
            drains this one early. `detail` names the window.
          tier: transport
          value: rate_limited
        - meaning: >-
            A queued request was withdrawn — through the cancel route, or by an
            operator — before it produced a result; it is terminal, and it is
            not by itself a statement about the charge. Cancelling stops Comfy
            waiting and it does not always stop the partner working, so a
            request cancelled while it was still `IN_QUEUE` was never dispatched
            and cannot be charged, whereas one cancelled after it was admitted
            may still be charged — a partner generation that completes is
            charged whether or not anyone collected it, which is why the cancel
            route calls the ask a request rather than a guarantee. It is not
            `client_disconnected`: that one says nobody is listening any more
            while a generation may still be running and billable, whereas this
            says the request itself was withdrawn. A caller polling the status
            read sees `200` with this in the body, because the request ended and
            so the read succeeded. Collecting the result of such a request
            answers `409` in Router's error envelope: that read also worked, and
            what it found is a request whose terminal state - the caller's own
            decision - leaves nothing to return. It is deliberately not `410`,
            which on that route means the result aged out of its retention
            window, and not a `5xx`, which would report the caller's own
            cancellation as a Router fault and read to a polling client as worth
            retrying.
          tier: transport
          value: cancelled
        - meaning: >-
            A queued request waited past its queue timeout without ever being
            admitted. Terminal, unbilled, and it never took a concurrency slot —
            the job never reached a provider. Deliberately not
            `deadline_exceeded`, which is the synchronous route's connection
            bound: a `deadline_exceeded` generation may be running and billable,
            whereas this one provably never started. It is returned under `504`,
            which it shares with `provider_timeout` and `deadline_exceeded`
            because a status can only say that a clock ran out; which clock -
            the partner's, Comfy's connection bound, or the queue's admission
            bound - is what `error_type` carries. The request itself is
            terminal, so submit a new one rather than re-reading this one.
          tier: transport
          value: queue_timeout
        - meaning: >-
            The `request_id` names no request of the caller's under this model.
            It is the second of the two conditions the queued reads' `404`
            covers; the first is the `{provider}/{model}` ID resolving to no
            partner model, which is `model_not_found` and carries fuzzy model
            suggestions. It also covers the right-id / wrong-model URL the path
            shape refuses, and it is deliberately indistinguishable from a
            request in another workspace, so a probe with a guessed id learns
            nothing. A request that has merely aged out of its retention window
            is `410`, not this.
          tier: transport
          value: request_not_found
        - meaning: >-
            The caller already has too many queued requests waiting to run, so
            this submit was refused. It shares `429` with
            `concurrency_limit_exceeded` and is not the same thing: that one is
            the synchronous route's answer for too many calls in flight at once,
            whereas the queue accepts a submit at that limit and parks it, and
            this bucket is the separate bound on how many a caller may leave
            waiting so that parking cannot mean enqueuing without end. It clears
            as the caller's own queued requests finish, so retry once some of
            them complete.
          tier: transport
          value: queue_backlog_full
    RouterValidationErrorDetail:
      description: >-
        One model-level validation failure, in the FastAPI form. `type` carries
        the specific provider reason - `value_error`, `missing`,
        `image_too_small`, `unsupported_audio_format`, `greater_than`,
        `file_too_large` and the rest - which is the granularity
        `RouterErrorType`'s coarse bucket cannot express. It is an open string
        and not an `enum` for the same reason: the provider vocabulary runs to
        roughly 48 values across two tiers and grows on the provider's release
        cycle, not ours, and an unmodelled value must reach the caller rather
        than fail deserialization.
      properties:
        ctx:
          $ref: '#/components/schemas/RouterValidationErrorContext'
        input:
          $ref: '#/components/schemas/RouterValidationErrorInput'
        loc:
          description: >-
            Path to the offending field, outermost segment first - for example
            `["body", "image_url"]`, or `["body", "images", 0]` where an integer
            indexes into an array.
          items:
            anyOf:
              - type: string
              - type: integer
          type: array
        msg:
          description: Human-readable description of this single failure.
          type: string
        type:
          description: >-
            Specific, machine-readable reason for this failure, passed through
            from the provider unchanged. This is the value a typed SDK exception
            hierarchy branches on; `error_type` on the response header is only
            its coarse bucket.
          example: image_too_small
          type: string
      required:
        - loc
        - msg
        - type
      type: object
    RouterValidationErrorContext:
      additionalProperties: true
      description: >-
        The violated bound for one `RouterValidationErrorDetail`, carried from
        the provider verbatim - for example `{"limit_value": 8}` alongside
        `greater_than`, `{"min_width": 512}` alongside `image_too_small`, or
        `{"max_size_bytes": 10485760}` alongside `file_too_large`. The key set
        is specific to the provider and the error type, so this is deliberately
        an open object: narrowing it to a fixed field list, or folding it into
        the `msg` string, is precisely how a ported integration compiles and
        then silently loses the branch that read the bound. Absent when the
        error type carries no bound.
      type: object
    RouterValidationErrorInput:
      description: >-
        The offending input value, echoed back verbatim so a caller can see what
        was rejected without re-deriving it from `loc`. Any JSON type - string,
        number, boolean, array, object or null - so this schema is deliberately
        left untyped rather than narrowed to an object. Absent when the provider
        does not echo the input back.
  headers:
    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.
      schema:
        example: true
        type: boolean
    RouterCreditsUsedHeader:
      description: >-
        What this run cost, in Comfy credits, priced from the same rate card the
        charge itself is billed against - so a caller needs no price table of
        its own, and on a run that reached the provider through more than one
        billed call it is their sum rather than the last one. It reports a
        price, not a settled ledger entry. On a model Comfy bills while your
        request is still open, the value is published only once the usage event
        was accepted by billing; on a model that submits and then polls, it is
        the price the asynchronous worker will bill, recorded before that charge
        settles - so a run whose billing later fails can still have carried this
        header. Treat it as what you will be charged, not as proof you were, and
        reconcile against the usage and billing API rather than against this
        header alone. Absent whenever no cost was reported: a request billed
        against your own provider key, a partner whose response does not carry
        every dimension its price is computed from, a usage event that matched
        no billable metric, or a charge that did not reach billing - so a
        missing header means "not reported" and must not be read as "free", and
        a report that sums absent headers as zero will not reconcile. A run that
        was rated and genuinely cost nothing reads `0`, which is a reported cost
        rather than a missing one, so branch on whether the header is present
        rather than on whether its value is non-zero. Coverage is partial today
        and widening, so do not assume the header is present for every model. On
        a response that also carries `Idempotent-Replayed: true` this restates
        what the original run cost, and that run is charged once however many
        times you retry the key - so do not add the header up across retries of
        one `Idempotency-Key`. Absent on an error response: it is written only
        on the path that returns a result, which a refused call never reaches.
      schema:
        example: '12.5'
        type: string
    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.

        It is minted by the server and is never read from a request header of
        the same name: a caller-controlled id would let two unrelated calls
        collide in the audit trail, which would make the join actively
        misleading rather than merely absent. Sending this header on a request
        has no effect.
      required: true
      schema:
        example: 6f1a1a6e-6a53-4a5f-9d3a-2b3b0a1f9c21
        format: uuid
        type: string
    RouterDroppedParamsHeader:
      description: >-
        One JSON-encoded string holding an array of strings - decode it with a
        JSON parser rather than splitting it on commas, because it is a single
        string on the wire, not a comma-separated OpenAPI array, and each entry
        is a sentence carrying commas of its own - present whenever a
        translation produced this call's request body and could not express one
        or more native fields exactly on the provider that served it, naming
        each dropped field and why, whether the caller asked for that
        translation with `model_provider` (`strict_mode=false`, the default) or
        an automatic `fallback_provider` retry ran it. Absent when no
        translation ran, when translation ran but dropped nothing, and on an
        error response. On a fallback retry it names what that retry's own
        translation (into the provider that actually served the call) dropped,
        never the primary attempt's. The disclosure is bounded three ways, so
        that it stays a fixed size rather than one that grows with the request
        body — some native arrays (`input.media` on the Wan video family) are
        deliberately uncapped, and entries quote caller-supplied values: at most
        64 entries, each at most 300 bytes plus an elision mark, summing to at
        most 4096 bytes of the encoded header value. Whichever bound binds first
        wins. Shortening is never silent: an entry cut to the per-entry bound
        ends in `…`, and when entries are left out entirely a final summarising
        entry states how many. Treat that count as a lower bound on the fields
        affected rather than a tally of them: it counts disclosure entries, and
        a translator may collapse many dropped elements into one entry (the Wan
        video family's media groups do exactly that). Entries are also sanitised
        before publication — a media URL is stripped of its query string, which
        is what carries its credential — so an entry names the field it dropped
        rather than reproducing the value verbatim. It is stored with the
        `Idempotency-Key` record and replayed unchanged on a same-key retry
        (alongside `Idempotent-Replayed: true`), so a retry carries the same
        disclosure the original call did rather than reading as a drop-free run.
        In the one case where the stored headers were too large to keep in full,
        the record is marked non-replayable and the retry is refused with `409`
        rather than served without this header - a refusal a caller can act on,
        where a replay that silently carried no disclosure is the outcome this
        header exists to prevent.
      schema:
        example: >-
          ["moderation (fal applies its own, non-configurable safety
          filtering)"]
        type: string
    RouterFallbackProviderHeader:
      description: >-
        Present, naming the provider, only when `fallback_provider` actually
        retried this call against a second provider and that retry succeeded -
        the provider that ultimately served the call, never one that was
        attempted and also failed. Absent when the primary attempt itself
        succeeded, and absent on an error response. It is stored with the
        `Idempotency-Key` record and replayed unchanged on a same-key retry
        (alongside `Idempotent-Replayed: true`), so a retry names the same
        provider the original call did rather than reading as an unsubstituted
        run. In the one case where the stored headers were too large to keep in
        full, the record is marked non-replayable and the retry is refused with
        `409` rather than served without this header, so a same-key retry never
        reads as an unsubstituted run either way. See `fallback_provider` for
        the retry policy this discloses.
      schema:
        type: string
    RouterProviderHeader:
      description: >-
        The provider of the Router leg that served this call, present on every
        successful run response - the model's own provider on a native run, the
        alternate provider `model_provider` named, or the provider a
        `fallback_provider` retry landed on (which
        `X-Comfy-Router-Fallback-Provider` also names, only in that case) -
        which is the leg Router dispatched to and not necessarily the vendor
        that produced the output, since a proxy-level cascade inside a native
        leg (the `vertexai` Gemini image chain, for one) can hand the call to a
        different vendor that no header names. Absent on an error response. It
        is stored with the `Idempotency-Key` record and replayed unchanged on a
        same-key retry; a retry of a call recorded before this header existed
        carries none.
      schema:
        example: openai
        type: string
    CommittedSpendCurrentHeader:
      description: >-
        The USD cents the caller currently has committed to calls still in
        flight. On a `429` this excludes the refused call, whose commitment was
        rolled back before the refusal was sent; on an admitted response it
        includes the call being answered. Present alongside
        `X-Committed-Spend-Limit`.
      schema:
        example: 9600
        format: int64
        minimum: 0
        type: integer
    CommittedSpendLimitHeader:
      description: >-
        The ceiling, in USD cents, on the partner spend the caller may have
        committed to calls still in flight - money held from the moment a call
        is admitted and released when that call finishes. It is not a budget, a
        balance, or any running total of what the caller has spent to date:
        settling an invoice frees no room under it, and letting an in-flight
        call finish does. Contrast `X-Concurrency-Limit`, which bounds those
        same in-flight calls counted as a number of calls rather than priced.
        How the ceiling is sized is a separate question from what it measures,
        and it is not tier-independent: the ceiling moves with the account's
        lifetime paid spend, off the same thresholds the concurrent-call tier
        uses, so paying more raises it - see [partner-node concurrency
        limits](https://docs.comfy.org/tutorials/partner-nodes/concurrency-limits)
        for that ladder and for the concurrent-call bound that shares this
        `429`. Present on both outcomes of an enforcing committed-spend gate -
        the `429` it raises and the success it admits - and absent while the
        gate is not enforcing, when it declines to decide and lets the call
        through, or on a `429` raised by the concurrent-call pool instead (a
        committed-spend `429` carries this trio and drops `X-Concurrency-*`).
      schema:
        example: 10000
        format: int64
        minimum: 0
        type: integer
    CommittedSpendRemainingHeader:
      description: >-
        The USD cents of headroom left under the ceiling, floored at zero. It
        can be positive on a refusal: the refused call cost more than what was
        left, and a cheaper call would still be admitted. Present alongside
        `X-Committed-Spend-Limit`.
      schema:
        example: 400
        format: int64
        minimum: 0
        type: integer
    RouterNoSniffHeader:
      description: >-
        Always `nosniff`, on every successful run of a Router model.

        The 200 on `POST /v2/models/{provider}/{model}` carries the PARTNER's
        own `Content-Type`, echoed verbatim, and since the ElevenLabs audio
        operations were enrolled that body can be arbitrary bytes rather than a
        JSON document. A 2xx interstitial from a partner's CDN or WAF carrying
        `text/html` would otherwise be sniffed and rendered as a document on
        Comfy's own origin. This header is what stops that, so it is a
        REQUIREMENT of reading the response rather than a decoration: treat the
        declared `Content-Type` as the type, and do not infer one from the
        bytes.

        It is stamped unconditionally on the success, not only on the models
        classified as binary - a header present only where someone remembered to
        classify a family is a header absent on the next one.

        A Router error rendered FRESH does not carry it: those bodies are
        Router's own JSON under a media type Router chooses, which is not the
        case the header exists for. A RECORDED refusal replayed against an
        `Idempotency-Key` does carry it, because the replay path stamps it on
        every record it serves rather than restoring it from the record - so a
        `4xx` marked `Idempotent-Replayed: true` can carry it while the same
        refusal rendered fresh does not. Do not read its presence on an error as
        a signal about that response, and rely on it only where it is declared.
      required: true
      schema:
        enum:
          - nosniff
        example: nosniff
        type: string
    RouterErrorTypeHeader:
      description: >-
        Coarse, machine-readable bucket for the failure, set by Router on every
        error response. It carries the same value as
        `RouterErrorResponse.error_type`, and on the `422` it is the only
        machine-readable bucket, because that body is the FastAPI `detail[]`
        shape and has no `error_type` field of its own. A client can therefore
        branch on this header alone, before deciding which of the two Router
        error bodies it received.
      required: true
      schema:
        $ref: '#/components/schemas/RouterErrorType'
    RouterRefusalSubjectHeader:
      description: >-
        Which input or output a content-policy refusal was about, as a
        Router-level closed vocabulary: `input`, `output`, `input_text`,
        `input_image`, `input_video`, `input_audio`, `output_text`,
        `output_image`, `output_video`, `output_audio`. Present only when
        `error_type` is `content_policy_violation` and the provider named the
        refused subject with a machine-readable code; absent otherwise - so
        branch on its presence. Never provider text. Mirrors
        `RouterErrorResponse.refusal_subject`.
      schema:
        example: output_audio
        type: string
    RouterUpstreamDetailHeader:
      description: >-
        A bounded, sanitized reason the model provider gave for rejecting the
        request. Present only when `error_type` is `invalid_input` and
        `X-Comfy-Upstream-Status` is a provider `4xx` or `2xx` - usually a
        `4xx`, and a `2xx` for a provider that reports a rejected generation
        inside a success envelope; absent otherwise - so branch on its presence.
        Mirrors `RouterErrorResponse.upstream_detail`.

        Provider-authored, so it is length-capped and stripped of URLs,
        hostnames, emails, tokens and id-shaped values before it is surfaced. It
        is never the raw provider body, and it is a human-readable hint rather
        than a machine-parsed value.
      schema:
        example: >-
          expected the height to be at least 300px, but received a 445x283px
          image instead
        type: string
    RouterUpstreamStatusHeader:
      description: >-
        The model provider's own HTTP status for this call. Present only when
        the failure came from the provider, and absent whenever Comfy Router
        refused the call itself - so branch on its presence: present means the
        request left Comfy, reached the provider, and the provider's answer is
        what produced this response's `error_type`.

        It is declared because `error_type` cannot carry that distinction alone.
        `invalid_input` is answered both by Router's own pre-dispatch checks and
        by Router relaying a provider's rejection, and a relayed rejection is
        returned as `400` whatever status it arrived with - so the bucket, the
        status and the `detail` are identical in the two cases, and this header
        is the only thing that separates them. Reading one as the other sends an
        investigation to the wrong side of the call.

        The value is a status code and nothing else. No provider prose, provider
        host or provider response body is ever forwarded (see
        `RouterErrorResponse.detail`), so this says WHERE the refusal came from
        without disclosing anything the provider wrote.
      schema:
        example: 400
        maximum: 599
        minimum: 100
        type: integer
    RouterRetryAfterHeader:
      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.
      schema:
        example: 2
        minimum: 1
        type: integer
    ModelConcurrencyCurrentHeader:
      description: >-
        The caller's concurrent calls to models classed expensive, as counted by
        the gate that refused this call. Present alongside
        `X-Model-Concurrency-Limit`.
      schema:
        example: 1
        format: int64
        minimum: 0
        type: integer
    ModelConcurrencyLimitHeader:
      description: >-
        The most concurrent calls to models classed expensive the caller may
        hold at once, a quarter of the concurrent-call limit, shared with
        partner-node calls. Present on a `429` raised by that bound.
      schema:
        example: 1
        format: int64
        minimum: 0
        type: integer
    ModelConcurrencyRemainingHeader:
      description: >-
        The expensive-model calls the caller may still start; `0` on the `429`.
        Present alongside `X-Model-Concurrency-Limit`.
      schema:
        example: 0
        format: int64
        minimum: 0
        type: integer
    RouterCapacityRetryAfterHeader:
      description: >-
        Seconds to wait before re-sending the same request, unchanged. It is
        present only when Router refused the request body for capacity - the
        in-flight request-body budget was full, or no slot was free to scan an
        oversized body - and a capacity refusal submitted nothing and charged
        nothing, so re-sending the identical call once the interval has passed
        is the whole remedy. There is no `Idempotency-Key` to collect under and
        no queued request to poll for; this is the one `Retry-After` on Router
        that means simply "send it again later". It is absent on the
        dependency-fault `503`s that share this status - a credential rail, an
        idempotency store or a policy decision point that could not answer -
        because none of them knows when it will, and a wrong number is worse
        than none against a dependency that is already failing.

        Read its absence as "no interval published", never as "retry
        immediately": that `503` is still worth retrying, just on the client's
        own backoff instead of Router's.
      schema:
        example: 4
        minimum: 1
        type: integer
  responses:
    RouterRunRequestError:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/RouterErrorResponse'
      description: >-
        A Router request-level failure - the request never reached the model, or
        failed for a reason the model itself did not report. The body is
        `RouterErrorResponse` and the bucket is repeated on
        `X-Comfy-Error-Type`. On this route the status is also how the partner's
        own refusal of a call that really ran is returned - the
        `content_policy_violation` some models meter - and that answer is
        recorded against an `Idempotency-Key` and served to a same-key retry, so
        unlike the catalog reads' shared error this response can arrive carrying
        `Idempotent-Replayed: true`.

        Two unrelated refusals therefore share this status and this bucket, and
        `X-Comfy-Upstream-Status` is what separates them: present, it carries
        the provider's own status and the provider is what refused the call;
        absent, Comfy Router refused it before the provider was reached.
      headers:
        Idempotent-Replayed:
          $ref: '#/components/headers/RouterIdempotentReplayedHeader'
        X-Comfy-Error-Type:
          $ref: '#/components/headers/RouterErrorTypeHeader'
        X-Comfy-Refusal-Subject:
          $ref: '#/components/headers/RouterRefusalSubjectHeader'
        X-Comfy-Request-Id:
          $ref: '#/components/headers/RouterRequestIdHeader'
        X-Comfy-Upstream-Detail:
          $ref: '#/components/headers/RouterUpstreamDetailHeader'
        X-Comfy-Upstream-Status:
          $ref: '#/components/headers/RouterUpstreamStatusHeader'
    RouterRequestError:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/RouterErrorResponse'
      description: >-
        A Router request-level failure - the request never reached the model, or
        failed for a reason the model itself did not report. The body is
        `RouterErrorResponse` and the bucket is repeated on
        `X-Comfy-Error-Type`.
      headers:
        X-Comfy-Error-Type:
          $ref: '#/components/headers/RouterErrorTypeHeader'
        X-Comfy-Request-Id:
          $ref: '#/components/headers/RouterRequestIdHeader'
    RouterIdempotencyConflict:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/RouterErrorResponse'
      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:
        Retry-After:
          $ref: '#/components/headers/RouterRetryAfterHeader'
        X-Comfy-Error-Type:
          $ref: '#/components/headers/RouterErrorTypeHeader'
        X-Comfy-Request-Id:
          $ref: '#/components/headers/RouterRequestIdHeader'
    RouterModelValidationError:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/RouterValidationErrorResponse'
      description: >-
        The request's contents were rejected against the model's schema. 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. A
        JSON request body that is not an object at all (an array, a string, a
        number, `null`) is refused here too, as `dict_type` at `["body"]`.

        What this costs your `Idempotency-Key` depends on WHERE the rejection
        happened, judged on the same dispatch test every other status is judged
        on rather than on the status itself. Router validates against the
        model's own input schema BEFORE any provider call, and that refusal
        reached nothing: it is never charged, it does not consume the key, and
        the key is RELEASED - re-send the SAME key with the corrected body and
        the call runs for real, rather than replaying this `422` or colliding
        with it as a `409`. That is the common case, and it is called out
        because this is the one error a caller CAUSES, and therefore the one
        they are most likely to fix and re-send under the original key.

        On the synchronous run route (`POST /v2/models/{provider}/{model}`),
        Router's own pre-flight check of a provider's request shape, when it can
        name the offending field in the body you sent, answers here too, on the
        same terms as the schema check: nothing was dispatched, the key is
        released, and the response carries no `X-Comfy-Upstream-Status`, which
        is present only on an answer that reached the provider. The queued route
        does not report that check in this `detail[]` shape.

        A model that performs its OWN validation can also answer `422` on a call
        that really ran. That one DISPATCHED, so it is recorded and replayed
        like any other answer: a same-key retry is served the stored response,
        marked `Idempotent-Replayed: true`, and correcting the body requires a
        NEW key.
      headers:
        Idempotent-Replayed:
          $ref: '#/components/headers/RouterIdempotentReplayedHeader'
        X-Comfy-Error-Type:
          $ref: '#/components/headers/RouterErrorTypeHeader'
        X-Comfy-Request-Id:
          $ref: '#/components/headers/RouterRequestIdHeader'
    RouterConcurrencyLimited:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/RouterErrorResponse'
      description: >-
        The caller is holding as much in-flight capacity as they are allowed and
        the request was refused before it reached the model. The bucket is
        `concurrency_limit_exceeded` in every case and `detail` says which bound
        was hit: the number of concurrent calls; the number of concurrent calls
        to models classed expensive, a quarter of the concurrent-call limit
        shared with partner-node calls, whose refusal also carries the
        `X-Model-Concurrency-Limit`, `X-Model-Concurrency-Current` and
        `X-Model-Concurrency-Remaining` headers; or the committed spend of the
        calls still in flight, whose refusal also carries the
        `X-Committed-Spend-Limit`, `X-Committed-Spend-Current` and
        `X-Committed-Spend-Remaining` headers (USD cents). Retry once one of the
        caller's own in-flight calls finishes - for the expensive-model bound,
        one of their calls to an expensive model, which may be a partner-node
        call. The body is `RouterErrorResponse` and the bucket is repeated on
        `X-Comfy-Error-Type`.
      headers:
        X-Comfy-Error-Type:
          $ref: '#/components/headers/RouterErrorTypeHeader'
        X-Comfy-Request-Id:
          $ref: '#/components/headers/RouterRequestIdHeader'
        X-Committed-Spend-Current:
          $ref: '#/components/headers/CommittedSpendCurrentHeader'
        X-Committed-Spend-Limit:
          $ref: '#/components/headers/CommittedSpendLimitHeader'
        X-Committed-Spend-Remaining:
          $ref: '#/components/headers/CommittedSpendRemainingHeader'
        X-Model-Concurrency-Current:
          $ref: '#/components/headers/ModelConcurrencyCurrentHeader'
        X-Model-Concurrency-Limit:
          $ref: '#/components/headers/ModelConcurrencyLimitHeader'
        X-Model-Concurrency-Remaining:
          $ref: '#/components/headers/ModelConcurrencyRemainingHeader'
    RouterProviderError:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/RouterErrorResponse'
      description: >-
        The provider's own response could not be turned into a result
        (`provider_error`). The body is `RouterErrorResponse` and the bucket is
        repeated on `X-Comfy-Error-Type`. `X-Comfy-Upstream-Status` carries the
        provider's own status when Router's typed failure carrier held one;
        absent, either the failure came with no such status (a poll-transport
        failure, for example) or this 502 did not come from the provider at all.
      headers:
        X-Comfy-Error-Type:
          $ref: '#/components/headers/RouterErrorTypeHeader'
        X-Comfy-Request-Id:
          $ref: '#/components/headers/RouterRequestIdHeader'
        X-Comfy-Upstream-Status:
          $ref: '#/components/headers/RouterUpstreamStatusHeader'
    RouterRequestUnavailable:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/RouterErrorResponse'
      description: >-
        A Router request-level failure - the request never reached the model, or
        failed for a reason the model itself did not report. The body is
        `RouterErrorResponse` and the bucket is repeated on
        `X-Comfy-Error-Type`. A `503` that was refused for capacity - the
        in-flight request-body budget was full, or no slot was free to scan an
        oversized body - carries `Retry-After` naming when to re-send the
        identical request; a `503` raised because a dependency faulted does not,
        because none of those rails knows when it will recover.
      headers:
        Retry-After:
          $ref: '#/components/headers/RouterCapacityRetryAfterHeader'
        X-Comfy-Error-Type:
          $ref: '#/components/headers/RouterErrorTypeHeader'
        X-Comfy-Request-Id:
          $ref: '#/components/headers/RouterRequestIdHeader'
    RouterDeadlineExceeded:
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/RouterErrorResponse'
      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}`. The status is shared with
        `provider_timeout` - the partner not answering in time, rather than
        Comfy's own bound expiring - which is why `X-Comfy-Upstream-Status` is
        declared here too: present, it carries the partner's own status and the
        bound that expired was theirs; absent, the bound was Comfy's.
      headers:
        Retry-After:
          $ref: '#/components/headers/RouterRetryAfterHeader'
        X-Comfy-Error-Type:
          $ref: '#/components/headers/RouterErrorTypeHeader'
        X-Comfy-Request-Id:
          $ref: '#/components/headers/RouterRequestIdHeader'
        X-Comfy-Upstream-Status:
          $ref: '#/components/headers/RouterUpstreamStatusHeader'
  securitySchemes:
    BearerAuth:
      bearerFormat: JWT
      description: >-
        Bearer token authentication. Normally a Firebase or Cloud JWT. A
        'comfyui-' prefixed API key is also accepted in this header: the prefix
        classifies the value as an API key and it is validated exactly as if it
        had been sent in X-API-Key.

        Accepted on the operations served by the comfyFirebase auth middleware
        (BE-9720, parity with ingest).
      scheme: bearer
      type: http
    ApiKeyAuth:
      description: >-
        API key authentication. Send the key in the X-API-Key header; keys are
        prefixed with 'comfyui-' and are generated from user account settings.
        The same 'comfyui-' key is also accepted in Authorization: Bearer (see
        BearerAuth), and when both headers carry a key, X-API-Key wins.

        The comfyFirebase auth middleware checks this header before falling back
        to the Authorization bearer token, and it backs both the /customers and
        the /proxy authset entries.
      in: header
      name: X-API-Key
      type: apiKey

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.