> ## 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.

# Use Chat Completions with Comfy Router

> Call openrouter/chat-completions through Comfy Router: endpoint, request shape and the response Router returns.

API Reference for `openrouter/chat-completions`, served by Comfy Router from Openrouter.

## Request setup

Create a key in [your Comfy workspace](https://platform.comfy.org/profile/api-keys) and export it as `COMFY_API_KEY`. For Python, run `pip install comfy-sdk`. For TypeScript, run `npm install @comfyorg/sdk`. cURL uses raw HTTP.

**Model ID:** `openrouter/chat-completions`

**Endpoint:** `POST https://api.comfy.org/v2/models/openrouter/chat-completions`

<Note>
  This model has no runnable request example. Build the body from the input documentation below, then use it with the [Router quickstart](/development/comfy-router/quickstart).
</Note>

## Schema

### Input

<ParamField body="cache_control" type="object">
  Enable automatic prompt caching. When set at the top level, the system automatically applies cache breakpoints to the last cacheable block in the request. Currently supported for Anthropic Claude models.
</ParamField>

<ParamField body="cache_control.ttl" type="string">
  Possible values: `5m`, `1h`
</ParamField>

<ParamField body="cache_control.type" type="string" required>
  Possible values: `ephemeral`
</ParamField>

<ParamField body="debug" type="object">
  Debug options for inspecting request transformations (streaming only)
</ParamField>

<ParamField body="debug.echo_upstream_body" type="boolean">
  If true, includes the transformed upstream request body in a debug chunk at the start of the stream. Only works with streaming mode.
</ParamField>

<ParamField body="frequency_penalty" type="number">
  Frequency penalty (-2.0 to 2.0)

  Format: `double`
</ParamField>

<ParamField body="image_config" type="object | string | number | object[]" />

<ParamField body="logit_bias" type="object">
  Token logit bias adjustments
</ParamField>

<ParamField body="logprobs" type="boolean">
  Return log probabilities
</ParamField>

<ParamField body="max_completion_tokens" type="integer">
  Maximum tokens in completion
</ParamField>

<ParamField body="max_tokens" type="integer">
  Maximum tokens (deprecated, use max\_completion\_tokens). Note: some providers enforce a minimum of 16.
</ParamField>

<ParamField body="messages" type="object[]" required>
  List of messages for the conversation
</ParamField>

<ParamField body="metadata" type="object">
  Key-value pairs for additional object information (max 16 pairs, 64 char keys, 512 char values)
</ParamField>

<ParamField body="modalities" type="`text`, `image`, `audio`[]">
  Output modalities for the response. Supported values are "text", "image", and "audio".
</ParamField>

<ParamField body="model" type="string">
  Model to use for completion
</ParamField>

<ParamField body="models" type="string[]">
  Models to use for completion
</ParamField>

<ParamField body="parallel_tool_calls" type="boolean">
  Whether to enable parallel function calling during tool use. When true, the model may generate multiple tool calls in a single response.
</ParamField>

<ParamField body="plugins" type="object[]">
  Plugins you want to enable for this request, including their settings.
</ParamField>

<ParamField body="presence_penalty" type="number">
  Presence penalty (-2.0 to 2.0)

  Format: `double`
</ParamField>

<ParamField body="provider" type="object">
  When multiple model providers are available, optionally indicate your routing preference.
</ParamField>

<ParamField body="provider.allow_fallbacks" type="boolean">
  Whether to allow backup providers to serve requests

  * true: (default) when the primary provider (or your custom providers in "order") is unavailable, use the next best provider.
  * false: use only the primary/custom provider, and return the upstream error if it's unavailable.
</ParamField>

<ParamField body="provider.data_collection" type="`deny`, `allow`">
  Data collection setting. If no available model provider meets the requirement, your request will return an error.

  * allow: (default) allow providers which store user data non-transiently and may train on it

  * deny: use only providers which do not collect user data.
</ParamField>

<ParamField body="provider.enforce_distillable_text" type="boolean">
  Whether to restrict routing to only models that allow text distillation. When true, only models where the author has allowed distillation will be used.
</ParamField>

<ParamField body="provider.ignore" type="`AkashML`, `AI21`, `AionLabs`, `Alibaba`, `Ambient`, `Baidu`, `Amazon Bedrock`, `Amazon Nova`, `Anthropic`, `Arcee AI`, `AtlasCloud`, `Avian`, `Azure`, `BaseTen`, `BytePlus`, `Black Forest Labs`, `Cerebras`, `Chutes`, `Cirrascale`, `Clarifai`, `Cloudflare`, `Cohere`, `Crucible`, `Crusoe`, `DeepInfra`, `DeepSeek`, `DekaLLM`, `Featherless`, `Fireworks`, `Friendli`, `GMICloud`, `Google`, `Google AI Studio`, `Groq`, `Hyperbolic`, `Inception`, `Inceptron`, `InferenceNet`, `Ionstream`, `Infermatic`, `Io Net`, `Inflection`, `Liquid`, `Mara`, `Mancer 2`, `Minimax`, `ModelRun`, `Mistral`, `Modular`, `Moonshot AI`, `Morph`, `NCompass`, `Nebius`, `Nex AGI`, `NextBit`, `Novita`, `Nvidia`, `OpenAI`, `OpenInference`, `Parasail`, `Poolside`, `Perceptron`, `Perplexity`, `Phala`, `Recraft`, `Reka`, `Relace`, `SambaNova`, `Seed`, `SiliconFlow`, `Sourceful`, `StepFun`, `Stealth`, `StreamLake`, `Switchpoint`, `Together`, `Upstage`, `Venice`, `WandB`, `Xiaomi`, `xAI`, `Z.AI`, `FakeProvider` | string[]">
  List of provider slugs to ignore. If provided, this list is merged with your account-wide ignored provider settings for this request.
</ParamField>

<ParamField body="provider.max_price" type="object">
  The object specifying the maximum price you want to pay for this request. USD price per million tokens, for prompt and completion.
</ParamField>

<ParamField body="provider.max_price.audio" type="string">
  Price per million prompt tokens
</ParamField>

<ParamField body="provider.max_price.completion" type="string">
  Price per million prompt tokens
</ParamField>

<ParamField body="provider.max_price.image" type="string">
  Price per million prompt tokens
</ParamField>

<ParamField body="provider.max_price.prompt" type="string">
  Price per million prompt tokens
</ParamField>

<ParamField body="provider.max_price.request" type="string">
  Price per million prompt tokens
</ParamField>

<ParamField body="provider.only" type="`AkashML`, `AI21`, `AionLabs`, `Alibaba`, `Ambient`, `Baidu`, `Amazon Bedrock`, `Amazon Nova`, `Anthropic`, `Arcee AI`, `AtlasCloud`, `Avian`, `Azure`, `BaseTen`, `BytePlus`, `Black Forest Labs`, `Cerebras`, `Chutes`, `Cirrascale`, `Clarifai`, `Cloudflare`, `Cohere`, `Crucible`, `Crusoe`, `DeepInfra`, `DeepSeek`, `DekaLLM`, `Featherless`, `Fireworks`, `Friendli`, `GMICloud`, `Google`, `Google AI Studio`, `Groq`, `Hyperbolic`, `Inception`, `Inceptron`, `InferenceNet`, `Ionstream`, `Infermatic`, `Io Net`, `Inflection`, `Liquid`, `Mara`, `Mancer 2`, `Minimax`, `ModelRun`, `Mistral`, `Modular`, `Moonshot AI`, `Morph`, `NCompass`, `Nebius`, `Nex AGI`, `NextBit`, `Novita`, `Nvidia`, `OpenAI`, `OpenInference`, `Parasail`, `Poolside`, `Perceptron`, `Perplexity`, `Phala`, `Recraft`, `Reka`, `Relace`, `SambaNova`, `Seed`, `SiliconFlow`, `Sourceful`, `StepFun`, `Stealth`, `StreamLake`, `Switchpoint`, `Together`, `Upstage`, `Venice`, `WandB`, `Xiaomi`, `xAI`, `Z.AI`, `FakeProvider` | string[]">
  List of provider slugs to allow. If provided, this list is merged with your account-wide allowed provider settings for this request.
</ParamField>

<ParamField body="provider.order" type="`AkashML`, `AI21`, `AionLabs`, `Alibaba`, `Ambient`, `Baidu`, `Amazon Bedrock`, `Amazon Nova`, `Anthropic`, `Arcee AI`, `AtlasCloud`, `Avian`, `Azure`, `BaseTen`, `BytePlus`, `Black Forest Labs`, `Cerebras`, `Chutes`, `Cirrascale`, `Clarifai`, `Cloudflare`, `Cohere`, `Crucible`, `Crusoe`, `DeepInfra`, `DeepSeek`, `DekaLLM`, `Featherless`, `Fireworks`, `Friendli`, `GMICloud`, `Google`, `Google AI Studio`, `Groq`, `Hyperbolic`, `Inception`, `Inceptron`, `InferenceNet`, `Ionstream`, `Infermatic`, `Io Net`, `Inflection`, `Liquid`, `Mara`, `Mancer 2`, `Minimax`, `ModelRun`, `Mistral`, `Modular`, `Moonshot AI`, `Morph`, `NCompass`, `Nebius`, `Nex AGI`, `NextBit`, `Novita`, `Nvidia`, `OpenAI`, `OpenInference`, `Parasail`, `Poolside`, `Perceptron`, `Perplexity`, `Phala`, `Recraft`, `Reka`, `Relace`, `SambaNova`, `Seed`, `SiliconFlow`, `Sourceful`, `StepFun`, `Stealth`, `StreamLake`, `Switchpoint`, `Together`, `Upstage`, `Venice`, `WandB`, `Xiaomi`, `xAI`, `Z.AI`, `FakeProvider` | string[]">
  An ordered list of provider slugs. The router will attempt to use the first provider in the subset of this list that supports your requested model, and fall back to the next if it is unavailable. If no providers are available, the request will fail with an error message.
</ParamField>

<ParamField body="provider.preferred_max_latency" type="number | object">
  Preferred maximum latency (in seconds). Can be a number (applies to p50) or an object with percentile-specific cutoffs. Endpoints above the threshold(s) may still be used, but are deprioritized in routing. When using fallback models, this may cause a fallback model to be used instead of the primary model if it meets the threshold.
</ParamField>

<ParamField body="provider.preferred_min_throughput" type="number | object">
  Preferred minimum throughput (in tokens per second). Can be a number (applies to p50) or an object with percentile-specific cutoffs. Endpoints below the threshold(s) may still be used, but are deprioritized in routing. When using fallback models, this may cause a fallback model to be used instead of the primary model if it meets the threshold.
</ParamField>

<ParamField body="provider.quantizations" type="`int4`, `int8`, `fp4`, `fp6`, `fp8`, `fp16`, `bf16`, `fp32`, `unknown`[]">
  A list of quantization levels to filter the provider by.
</ParamField>

<ParamField body="provider.require_parameters" type="boolean">
  Whether to filter providers to only those that support the parameters you've provided. If this setting is omitted or set to false, then providers will receive only the parameters they support, and ignore the rest.
</ParamField>

<ParamField body="provider.sort" type="`price`, `throughput`, `latency`, `exacto` | object">
  The sorting strategy to use for this request, if "order" is not specified. When set, no load balancing is performed.
</ParamField>

<ParamField body="provider.zdr" type="boolean">
  Whether to restrict routing to only ZDR (Zero Data Retention) endpoints. When true, only endpoints that do not retain prompts will be used.
</ParamField>

<ParamField body="reasoning" type="object">
  Configuration options for reasoning models
</ParamField>

<ParamField body="reasoning.effort" type="`xhigh`, `high`, `medium`, `low`, `minimal`, `none`">
  Constrains effort on reasoning for reasoning models
</ParamField>

<ParamField body="reasoning.summary" type="string">
  Possible values: `auto`, `concise`, `detailed`
</ParamField>

<ParamField body="response_format" type="object">
  Response format configuration
</ParamField>

<ParamField body="route" type="object">
  Any type
</ParamField>

<ParamField body="seed" type="integer">
  Random seed for deterministic outputs
</ParamField>

<ParamField body="service_tier" type="`auto`, `default`, `flex`, `priority`, `scale`">
  The service tier to use for processing this request.
</ParamField>

<ParamField body="session_id" type="string">
  A unique identifier for grouping related requests (e.g., a conversation or agent workflow) for observability. If provided in both the request body and the x-session-id header, the body value takes precedence. Maximum of 256 characters.
</ParamField>

<ParamField body="stop" type="string | string[] | object">
  Stop sequences (up to 4)
</ParamField>

<ParamField body="stop_server_tools_when" type="object[]">
  Stop conditions for the server-tool agent loop. Any condition firing halts the loop (OR logic). When set, this overrides `max_tool_calls`.
</ParamField>

<ParamField body="stream" type="boolean" default="false">
  Enable streaming response
</ParamField>

<ParamField body="stream_options" type="object">
  Streaming configuration options
</ParamField>

<ParamField body="stream_options.include_usage" type="boolean">
  Deprecated: This field has no effect. Full usage details are always included.
</ParamField>

<ParamField body="temperature" type="number">
  Sampling temperature (0-2)

  Format: `double`
</ParamField>

<ParamField body="tool_choice" type="`none` | `auto` | `required` | object">
  Tool choice configuration
</ParamField>

<ParamField body="tools" type="object[]">
  Available tools for function calling
</ParamField>

<ParamField body="top_logprobs" type="integer">
  Number of top log probabilities to return (0-20)
</ParamField>

<ParamField body="top_p" type="number">
  Nucleus sampling parameter (0-1)

  Format: `double`
</ParamField>

<ParamField body="trace" type="object">
  Metadata for observability and tracing. Known keys (trace\_id, trace\_name, span\_name, generation\_name, parent\_span\_id) have special handling. Additional keys are passed through as custom metadata to configured broadcast destinations.
</ParamField>

<ParamField body="trace.generation_name" type="string" />

<ParamField body="trace.parent_span_id" type="string" />

<ParamField body="trace.span_name" type="string" />

<ParamField body="trace.trace_id" type="string" />

<ParamField body="trace.trace_name" type="string" />

<ParamField body="user" type="string">
  Unique user identifier
</ParamField>

Generated from the schema Router serves at `GET /v2/models/openrouter/chat-completions/openapi.json`, the same document it validates a call against before the request reaches the provider.

### Output

<ResponseField name="choices" type="object[]" required>
  List of completion choices
</ResponseField>

<ResponseField name="choices[].finish_reason" type="string" required>
  Possible values: `tool_calls`, `stop`, `length`, `content_filter`, `error`
</ResponseField>

<ResponseField name="choices[].index" type="integer" required>
  Choice index
</ResponseField>

<ResponseField name="choices[].logprobs" type="object">
  Log probabilities for the completion
</ResponseField>

<ResponseField name="choices[].logprobs.content" type="object[]" required>
  Log probabilities for content tokens
</ResponseField>

<ResponseField name="choices[].logprobs.content[].bytes" type="integer[]" required>
  UTF-8 bytes of the token
</ResponseField>

<ResponseField name="choices[].logprobs.content[].logprob" type="number" required>
  Log probability of the token

  Format: `double`
</ResponseField>

<ResponseField name="choices[].logprobs.content[].token" type="string" required>
  The token
</ResponseField>

<ResponseField name="choices[].logprobs.content[].top_logprobs" type="object[]" required>
  Top alternative tokens with probabilities
</ResponseField>

<ResponseField name="choices[].logprobs.content[].top_logprobs[].bytes" type="integer[]" required />

<ResponseField name="choices[].logprobs.content[].top_logprobs[].logprob" type="number" required>
  Format: `double`
</ResponseField>

<ResponseField name="choices[].logprobs.content[].top_logprobs[].token" type="string" required />

<ResponseField name="choices[].logprobs.refusal" type="object[]">
  Log probabilities for refusal tokens
</ResponseField>

<ResponseField name="choices[].logprobs.refusal[].bytes" type="integer[]" required>
  UTF-8 bytes of the token
</ResponseField>

<ResponseField name="choices[].logprobs.refusal[].logprob" type="number" required>
  Log probability of the token

  Format: `double`
</ResponseField>

<ResponseField name="choices[].logprobs.refusal[].token" type="string" required>
  The token
</ResponseField>

<ResponseField name="choices[].logprobs.refusal[].top_logprobs" type="object[]" required>
  Top alternative tokens with probabilities
</ResponseField>

<ResponseField name="choices[].logprobs.refusal[].top_logprobs[].bytes" type="integer[]" required />

<ResponseField name="choices[].logprobs.refusal[].top_logprobs[].logprob" type="number" required>
  Format: `double`
</ResponseField>

<ResponseField name="choices[].logprobs.refusal[].top_logprobs[].token" type="string" required />

<ResponseField name="choices[].message" type="object" required>
  Assistant message for requests and responses
</ResponseField>

<ResponseField name="choices[].message.audio" type="object">
  Audio output data or reference
</ResponseField>

<ResponseField name="choices[].message.audio.data" type="string">
  Base64 encoded audio data
</ResponseField>

<ResponseField name="choices[].message.audio.expires_at" type="integer">
  Audio expiration timestamp
</ResponseField>

<ResponseField name="choices[].message.audio.id" type="string">
  Audio output identifier
</ResponseField>

<ResponseField name="choices[].message.audio.transcript" type="string">
  Audio transcript
</ResponseField>

<ResponseField name="choices[].message.content" type="string | object[] | object">
  Assistant message content
</ResponseField>

<ResponseField name="choices[].message.images" type="object[]">
  Generated images from image generation models
</ResponseField>

<ResponseField name="choices[].message.images[].image_url" type="object" required />

<ResponseField name="choices[].message.images[].image_url.url" type="string" required>
  URL or base64-encoded data of the generated image
</ResponseField>

<ResponseField name="choices[].message.name" type="string">
  Optional name for the assistant
</ResponseField>

<ResponseField name="choices[].message.reasoning" type="string">
  Reasoning output
</ResponseField>

<ResponseField name="choices[].message.reasoning_details" type="object[]">
  Reasoning details for extended thinking models
</ResponseField>

<ResponseField name="choices[].message.refusal" type="string">
  Refusal message if content was refused
</ResponseField>

<ResponseField name="choices[].message.tool_calls" type="object[]">
  Tool calls made by the assistant
</ResponseField>

<ResponseField name="choices[].message.tool_calls[].function" type="object" required />

<ResponseField name="choices[].message.tool_calls[].function.arguments" type="string" required>
  Function arguments as JSON string
</ResponseField>

<ResponseField name="choices[].message.tool_calls[].function.name" type="string" required>
  Function name to call
</ResponseField>

<ResponseField name="choices[].message.tool_calls[].id" type="string" required>
  Tool call identifier
</ResponseField>

<ResponseField name="choices[].message.tool_calls[].type" type="string" required>
  Possible values: `function`
</ResponseField>

<ResponseField name="created" type="integer" required>
  Unix timestamp of creation
</ResponseField>

<ResponseField name="id" type="string" required>
  Unique completion identifier
</ResponseField>

<ResponseField name="model" type="string" required>
  Model used for completion
</ResponseField>

<ResponseField name="object" type="string" required>
  Possible values: `chat.completion`
</ResponseField>

<ResponseField name="openrouter_metadata" type="object" />

<ResponseField name="openrouter_metadata.attempt" type="integer" required />

<ResponseField name="openrouter_metadata.attempts" type="object[]" />

<ResponseField name="openrouter_metadata.attempts[].model" type="string" required />

<ResponseField name="openrouter_metadata.attempts[].provider" type="string" required />

<ResponseField name="openrouter_metadata.attempts[].status" type="integer" required />

<ResponseField name="openrouter_metadata.endpoints" type="object" required />

<ResponseField name="openrouter_metadata.endpoints.available" type="object[]" required />

<ResponseField name="openrouter_metadata.endpoints.available[].model" type="string" required />

<ResponseField name="openrouter_metadata.endpoints.available[].provider" type="string" required />

<ResponseField name="openrouter_metadata.endpoints.available[].selected" type="boolean" required />

<ResponseField name="openrouter_metadata.endpoints.total" type="integer" required />

<ResponseField name="openrouter_metadata.is_byok" type="boolean" required />

<ResponseField name="openrouter_metadata.params" type="object" />

<ResponseField name="openrouter_metadata.params.quality_floor" type="number">
  Format: `double`
</ResponseField>

<ResponseField name="openrouter_metadata.params.throughput_floor" type="number">
  Format: `double`
</ResponseField>

<ResponseField name="openrouter_metadata.params.version_group" type="string" />

<ResponseField name="openrouter_metadata.pipeline" type="object[]" />

<ResponseField name="openrouter_metadata.pipeline[].cost_usd" type="number">
  Format: `double`
</ResponseField>

<ResponseField name="openrouter_metadata.pipeline[].data" type="object" />

<ResponseField name="openrouter_metadata.pipeline[].guardrail_id" type="string" />

<ResponseField name="openrouter_metadata.pipeline[].guardrail_scope" type="string" />

<ResponseField name="openrouter_metadata.pipeline[].name" type="string" required />

<ResponseField name="openrouter_metadata.pipeline[].summary" type="string" />

<ResponseField name="openrouter_metadata.pipeline[].type" type="string" required>
  Categorical kind of a pipeline stage. Multiple plugins can share a type (e.g. all guardrail-level plugins emit `guardrail`); the `name` field disambiguates which plugin emitted it.

  Possible values: `guardrail`, `plugin`, `server_tools`, `response_healing`, `context_compression`
</ResponseField>

<ResponseField name="openrouter_metadata.region" type="string" required />

<ResponseField name="openrouter_metadata.requested" type="string" required />

<ResponseField name="openrouter_metadata.strategy" type="string" required>
  Possible values: `direct`, `auto`, `free`, `latest`, `alias`, `fallback`, `pareto`, `bodybuilder`, `fusion`
</ResponseField>

<ResponseField name="openrouter_metadata.summary" type="string" required />

<ResponseField name="service_tier" type="string">
  The service tier used by the upstream provider for this request
</ResponseField>

<ResponseField name="system_fingerprint" type="string" required>
  System fingerprint
</ResponseField>

<ResponseField name="usage" type="object">
  Token usage statistics
</ResponseField>

<ResponseField name="usage.completion_tokens" type="integer" required>
  Number of tokens in the completion
</ResponseField>

<ResponseField name="usage.completion_tokens_details" type="object">
  Detailed completion token usage
</ResponseField>

<ResponseField name="usage.cost" type="number">
  Cost of the completion

  Format: `double`
</ResponseField>

<ResponseField name="usage.cost_details" type="object">
  Breakdown of upstream inference costs
</ResponseField>

<ResponseField name="usage.cost_details.upstream_inference_completions_cost" type="number" required>
  Format: `double`
</ResponseField>

<ResponseField name="usage.cost_details.upstream_inference_cost" type="number">
  Format: `double`
</ResponseField>

<ResponseField name="usage.cost_details.upstream_inference_prompt_cost" type="number" required>
  Format: `double`
</ResponseField>

<ResponseField name="usage.is_byok" type="boolean">
  Whether a request was made using a Bring Your Own Key configuration
</ResponseField>

<ResponseField name="usage.prompt_tokens" type="integer" required>
  Number of tokens in the prompt
</ResponseField>

<ResponseField name="usage.prompt_tokens_details" type="object">
  Detailed prompt token usage
</ResponseField>

<ResponseField name="usage.total_tokens" type="integer" required>
  Total number of tokens
</ResponseField>

## Examples

### Output

```json theme={null}
{
  "choices": [
    {
      "finish_reason": "stop",
      "index": 0,
      "message": {}
    }
  ],
  "created": 1750000000,
  "id": "gen-0000000000-examplecompletion",
  "model": "anthropic/claude-sonnet-4.5",
  "object": "chat.completion",
  "system_fingerprint": null,
  "usage": {
    "completion_tokens": 128,
    "cost": 0.00123,
    "prompt_tokens": 42,
    "total_tokens": 170
  }
}
```

## Before you ship

The SDKs create an `Idempotency-Key` and reuse it for automatic retries. For manual retries, reuse the original key. Router can hold the connection for up to 10 minutes.

When a request fails, Router sends an `X-Comfy-Error-Type` response header explaining why. A `422` means Router rejected the input before calling the provider, and a `413` means the request body was larger than Router accepts. Download generated assets promptly because [result URLs can expire](/development/comfy-router/reference#result-assets).

Any size limit named in a field description above is the provider's own bound on that field, quoted from the provider's specification. Router applies a separate cap to the whole request body, which base64-encoded media counts against: see [request body size](/development/comfy-router/limitations#request-bodies-are-capped).

<CardGroup cols={3}>
  <Card title="Headers" icon="list" href="/development/comfy-router/headers">
    Authentication, idempotency, request IDs, error buckets, retry pacing, spend limits.
  </Card>

  <Card title="Using the Router API" icon="code" href="/development/comfy-router/api">
    Model discovery, validation errors, retries, and billing.
  </Card>

  <Card title="Limitations" icon="triangle-exclamation" href="/development/comfy-router/limitations">
    What Router does not do today, and what to use instead.
  </Card>
</CardGroup>
