Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/scripts/snippets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,16 @@ is deleted. The banner is a rollout artifact, so retiring it is one `rm` plus a
regen rather than an edit to the template and every generated page in the same
commit.

## The two delivery tabs

Every runnable Quick start is a tab pair: **Wait for the result** is the
`models.run` call, **Queue and collect later** is the same body through
`models.submit`, polled to completion and collected. Both tabs are emitted from
the one `example`, so they cannot disagree about the request. The queued tab
opens with `snippets/comfy-router/queue-preview-notice.mdx` while that file
exists, on the same existence rule as the preview banner: once queued delivery
is on for every workspace, `rm` the snippet and regen.

## Schema sections

Every Code page includes a Schema section and the examples available for it.
Expand Down
201 changes: 171 additions & 30 deletions .github/scripts/snippets/gen-code-pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
*
* The template below is the only place the page shape lives. Python, TypeScript
* and cURL are all emitted from the same `example` object, so the three cannot
* disagree about the request body.
* disagree about the request body, and both delivery modes (wait for the
* result, or queue it and collect later) are emitted from that one object too.
*/
import { readFileSync, writeFileSync, existsSync, mkdirSync, mkdtempSync, rmSync } from "node:fs";
import { join, dirname, relative } from "node:path";
Expand All @@ -21,6 +22,7 @@ const SCHEMA_GLOB = "router-schemas/*/*.json";
const MODELS_DIR = "development/comfy-router/models";
const DOCS_JSON = "docs.json";
const PREVIEW_NOTICE = "snippets/comfy-router/preview-notice.mdx";
const QUEUE_NOTICE = "snippets/comfy-router/queue-preview-notice.mdx";
const BASE_URL = "https://api.comfy.org";
const ROUTE = "/v2/models";

Expand Down Expand Up @@ -357,6 +359,152 @@ function curlSnippet(model: string, example: Record<string, unknown>, files: Fil
-d "${json}"`;
}

// ---------------------------------------------------------------------------
// Queued delivery
//
// Every runnable page carries its request twice: through `models.run`, which
// holds the connection until the result is ready, and through `models.submit`,
// which returns a request handle at once and collects the result later. The
// queued builders take the same `example` and file inputs as the synchronous
// ones above, so the two tabs cannot disagree about the body either.
// ---------------------------------------------------------------------------

/**
* Python, queued. An empty `resultPath` means the page has no authored result
* path (a derived page), so the snippet prints the whole payload.
*/
function pythonQueueSnippet(model: string, example: Record<string, unknown>, files: FileInput[], resultPath: string, label: string): string {
const reads = files
.map((f) => `with open(${JSON.stringify(f.path)}, "rb") as f:\n ${f.varName} = base64.b64encode(f.read()).decode()`)
.join("\n\n");
const body = Object.entries(example)
.map(([k, v]) => ` ${JSON.stringify(k)}: ${pyLiteral(v, 12, files, k)},`)
.join("\n");
const show = resultPath ? `print("${label}:", result${pyPath(resultPath)})` : "print(result)";
return `${files.length ? "import base64\n\n" : ""}from comfy_sdk import Comfy
${reads ? `\n${reads}\n` : ""}
# Reads COMFY_API_KEY from the environment.
# Each submit() call mints its own Idempotency-Key and reuses it for automatic retries.
with Comfy() as client:
handle = client.models.submit(
"${model}",
{
${body}
},
)
print("request_id:", handle.request_id) # with the model ID, all another process needs

# Poll until the request completes, waiting the Retry-After the server names.
for update in handle.iter_events():
print(update.status, update.queue_position)

# The provider's own payload, the same value models.run() returns.
# A request that failed or was cancelled raises the typed Router error here.
result = handle.get()

${show}`;
}

/** TypeScript, queued. As `pythonQueueSnippet`, an empty `resultPath` prints the whole payload. */
function typescriptQueueSnippet(model: string, example: Record<string, unknown>, files: FileInput[], resultPath: string, label: string): string {
const imports = `import { comfy } from "@comfyorg/sdk";\n${files.length ? `import { readFile } from "node:fs/promises";\n` : ""}`;
const reads = files
.map((f) => `const ${camel(f.varName)} = (await readFile(${JSON.stringify(f.path)})).toString("base64");`)
.join("\n");
const body = Object.entries(example)
.map(([k, v]) => ` ${/^[a-zA-Z_$][\w$]*$/.test(k) ? k : JSON.stringify(k)}: ${tsLiteral(v, 2, files, k)},`)
.join("\n");
const typed = resultPath ? `type Result = ${tsResultType(resultPath)};\n` : "";
const generic = resultPath ? "<Result>" : "";
const show = resultPath
? `const result = await handle.get();
if (result.kind !== "json") throw new Error("expected a JSON result");

console.log("${label}:", result.data${tsPath(resultPath)});`
: `const result = await handle.get();

console.log(result.data);`;
return `${imports}
${reads ? `${reads}\n\n` : ""}// Reads COMFY_API_KEY from the environment.
// Each submit() call mints its own Idempotency-Key and reuses it for automatic retries.
${typed}const handle = await comfy.models.submit${generic}("${model}", {
${body}
});
console.log("requestId:", handle.requestId); // with the model ID, all another process needs

// Poll until the request completes, waiting the Retry-After the server names.
for await (const update of handle.events()) {
console.log(update.status, update.queuePosition);
}

// The same result models.run() returns. A request that failed or was cancelled rejects here.
${show}`;
}

/** cURL, queued: submit, then poll and collect by request id. */
function curlQueueSnippet(model: string, example: Record<string, unknown>, files: FileInput[]): string {
const reads = files.map((f) => `${shellVar(f.varName)}=$(base64 < ${f.path} | tr -d '\\n')`).join("\n");
const esc = (v: unknown) => JSON.stringify(v).replace(/[\\$`"]/g, (c) => `\\${c}`);
const entries = Object.entries(example).map(([k, v]) => {
const f = files.find((x) => x.key === k);
const value = f ? `\\"$${shellVar(f.varName)}\\"` : esc(v);
return `${esc(k)}: ${value}`;
});
const json = `{${entries.join(", ")}}`;
const requests = `${BASE_URL}${ROUTE}/${model}/requests`;
return `${reads ? `${reads}\n\n` : ""}# 1. Submit. Router answers 201 with request_id, status_url, response_url and cancel_url.
curl ${requests} \\
-H "X-API-Key: $COMFY_API_KEY" \\
-H "Idempotency-Key: $(uuidgen)" \\
-H "Content-Type: application/json" \\
-d "${json}"

# 2. Poll until status is COMPLETED, waiting the Retry-After seconds each response names.
REQUEST_ID="<request_id from the 201 body>"
curl -i ${requests}/$REQUEST_ID/status \\
-H "X-API-Key: $COMFY_API_KEY"

# 3. Collect. 200 with the model's native output, 202 with the status body while it is still running.
curl ${requests}/$REQUEST_ID \\
-H "X-API-Key: $COMFY_API_KEY"`;
}

/** The three languages of one delivery mode. */
function codeGroup(python: string, typescript: string, curl: string): string {
return `<CodeGroup>
\`\`\`python Python
${python}
\`\`\`

\`\`\`typescript TypeScript
${typescript}
\`\`\`

\`\`\`bash cURL
${curl}
\`\`\`
</CodeGroup>`;
}

/**
* The two delivery modes of one request as a tab pair: `sync` waits for the
* result, `queued` submits the same body and collects it later. The queued tab
* opens with the rollout notice while `snippets/comfy-router/queue-preview-notice.mdx`
* exists (see `queueNotice` below).
*/
function deliveryTabs(model: string, sync: string, queued: string): string {
return `<Tabs>
<Tab title="Wait for the result">
${sync}
</Tab>
<Tab title="Queue and collect later">
${queueNotice.body}The same body, sent to \`POST ${BASE_URL}${ROUTE}/${model}/requests\`. Router answers \`201\` with a \`request_id\` as soon as the run is admitted, and the result is collected once it is ready, from this process or another one. [Queued delivery](/development/comfy-router/queue) walks through status, cancellation and collection.

${queued}
</Tab>
</Tabs>`;
}

function possessive(name: string): string {
return name.endsWith("s") ? `${name}'` : `${name}'s`;
}
Expand Down Expand Up @@ -627,23 +775,14 @@ function quickStart(v: Variant, spec: Spec): string {
const example = v.example ?? spec.example;
const files = fileInputs(example);
const label = spec.result.label;
const path = spec.result.path;
const sync = codeGroup(pythonSnippet(v.model, example, files, path, label), typescriptSnippet(v.model, example, files, path, label), curlSnippet(v.model, example, files));
const queued = codeGroup(pythonQueueSnippet(v.model, example, files, path, label), typescriptQueueSnippet(v.model, example, files, path, label), curlQueueSnippet(v.model, example, files));
return `**Model ID:** \`${v.model}\`

**Endpoint:** \`POST ${BASE_URL}${ROUTE}/${v.model}\`

<CodeGroup>
\`\`\`python Python
${pythonSnippet(v.model, example, files, spec.result.path, label)}
\`\`\`

\`\`\`typescript TypeScript
${typescriptSnippet(v.model, example, files, spec.result.path, label)}
\`\`\`

\`\`\`bash cURL
${curlSnippet(v.model, example, files)}
\`\`\`
</CodeGroup>`;
${deliveryTabs(v.model, sync, queued)}`;
}

/** Schema + Examples for one variant. `html` headings keep them out of the TOC when rendered inside tabs. */
Expand Down Expand Up @@ -697,6 +836,19 @@ const previewNotice = (() => {
};
})();

/**
* As `previewNotice`: the queued-delivery rollout note at the top of every
* "Queue and collect later" tab, rendered while its snippet exists and retired
* with one `rm` plus a regen once the queue is on for everyone.
*/
const queueNotice = (() => {
const present = existsSync(join(ROOT, QUEUE_NOTICE));
return {
imports: present ? `import QueuedDeliveryNotice from "/${QUEUE_NOTICE}";\n` : "",
body: present ? "<QueuedDeliveryNotice />\n\n" : "",
};
})();

function renderPage(spec: Spec, dir: string): string {
const both = spec.variants.length > 1;
// The one-time setup a snippet cannot run without. Everything else that is
Expand All @@ -721,7 +873,7 @@ sidebarTitle: ${JSON.stringify(spec.name)}

{/* GENERATED FILE. Edit code.yaml in this directory and run \`pnpm code-pages:gen\`. */}

${previewNotice.imports}import RouterCodeFooter from "/snippets/comfy-router/model-code-footer.mdx";
${previewNotice.imports}${queueNotice.imports}import RouterCodeFooter from "/snippets/comfy-router/model-code-footer.mdx";

${spec.intro ?? `API Reference for ${spec.name}. ${spec.summary.replace(/\s+/g, " ").trim()}`}
${previewNotice.body}
Expand Down Expand Up @@ -796,20 +948,9 @@ ${tsBody}
});

console.log(data);`;
const curl = curlSnippet(model, body, []);
return `<CodeGroup>
\`\`\`python Python
${python}
\`\`\`

\`\`\`typescript TypeScript
${typescript}
\`\`\`

\`\`\`bash cURL
${curl}
\`\`\`
</CodeGroup>`;
const sync = codeGroup(python, typescript, curlSnippet(model, body, []));
const queued = codeGroup(pythonQueueSnippet(model, body, [], "", ""), typescriptQueueSnippet(model, body, [], "", ""), curlQueueSnippet(model, body, []));
return deliveryTabs(model, sync, queued);
}

// Adapt shared response fixtures for display only; never rewrite synced schemas.
Expand Down Expand Up @@ -877,7 +1018,7 @@ sidebarTitle: ${JSON.stringify(title)}

{/* GENERATED FILE. Generated from router-schemas/${model}.json by \`pnpm code-pages:gen\`. */}

${previewNotice.imports}import RouterCodeFooter from "/snippets/comfy-router/model-code-footer.mdx";
${previewNotice.imports}${queueNotice.imports}import RouterCodeFooter from "/snippets/comfy-router/model-code-footer.mdx";

API Reference for \`${model}\`, served by Comfy Router from ${provider}.
${previewNotice.body}
Expand Down
3 changes: 2 additions & 1 deletion development/comfy-router/api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
"billing": { "charges_on_policy_rejection": "no" }
}
],
"has_more": true,

Check warning on line 30 in development/comfy-router/api.mdx

View check run for this annotation

Mintlify / Mintlify Validation (dripart) - vale-spellcheck

development/comfy-router/api.mdx#L30

Did you really mean 'has_more'?
"next_cursor": "example-cursor",

Check warning on line 31 in development/comfy-router/api.mdx

View check run for this annotation

Mintlify / Mintlify Validation (dripart) - vale-spellcheck

development/comfy-router/api.mdx#L31

Did you really mean 'next_cursor'?
"limit": 50
}
```
Expand Down Expand Up @@ -68,11 +68,11 @@

Within the model operation, `requestBody` describes the input, and the `200` response describes the output when an output schema has been authored. Input validation and output documentation are different: Router validates against its input schema but does not validate the returned provider result against its output schema.

Inspect the output media type as well as its fields. An unauthored output can use `*/*`, and some models return binary data rather than JSON.

Check warning on line 71 in development/comfy-router/api.mdx

View check run for this annotation

Mintlify / Mintlify Validation (dripart) - vale-spellcheck

development/comfy-router/api.mdx#L71

Did you really mean 'unauthored'?

### Cache a schema

Save the schema and its `ETag`. On a later schema fetch, pass that ETag in `If-None-Match`. A `304` has no body; keep the cached document. A `200` supplies a replacement document and ETag.

Check warning on line 75 in development/comfy-router/api.mdx

View check run for this annotation

Mintlify / Mintlify Validation (dripart) - vale-spellcheck

development/comfy-router/api.mdx#L75

Did you really mean 'ETag'?

```bash
curl -H "X-API-Key: $COMFY_API_KEY" \
Expand All @@ -92,13 +92,13 @@

Router returns each model's terminal result shape. There is no common image, video, or text envelope: BFL image output uses `result.sample`, while other models can return URL lists or inline bytes.

Some asset URLs are rehosted by Comfy; others remain provider URLs or inline bytes. Check [result assets](/development/comfy-router/reference#result-assets) and download expiring assets promptly. Replays do not renew URLs.

Check warning on line 95 in development/comfy-router/api.mdx

View check run for this annotation

Mintlify / Mintlify Validation (dripart) - vale-spellcheck

development/comfy-router/api.mdx#L95

Did you really mean 'rehosted'?

## Handle errors, retries, and billing

### Read errors defensively

A failed request can return a proxy's HTML error page, truncated JSON, or plain text. Do not let a JSON parsing error hide the HTTP status or request ID. These helpers use an `httpx.Response` in Python and a Fetch `Response` in TypeScript; the SDKs already expose error fields for normal SDK calls.

Check warning on line 101 in development/comfy-router/api.mdx

View check run for this annotation

Mintlify / Mintlify Validation (dripart) - vale-spellcheck

development/comfy-router/api.mdx#L101

Did you really mean 'SDKs'?

<CodeGroup>

Expand Down Expand Up @@ -192,13 +192,13 @@

#### Timeouts and collection

One Router call may hold the connection for 10 minutes by default. Set your client timeout above that bound so you keep the typed `504` and the request ID rather than an opaque local abort.
One Router call may hold the connection for 10 minutes by default. Set your client timeout above that bound so you keep the typed `504` and the request ID rather than an opaque local abort. If your application cannot hold a connection that long, [queued delivery](/development/comfy-router/queue) returns a `request_id` at once and lets you collect the result later.

`deadline_exceeded` is Router's waiting limit; `provider_timeout` is the provider's deadline. A provider generation that completes can be billed even if the caller received a timeout or disconnected. Client cancellation stops the wait and SDK retries, but does not necessarily cancel accepted provider work.

For submit-and-poll providers, a retained handle lets a same-key request continue collecting the original generation. Dispatched calls cut off without a recoverable handle can consume the key without a replayable result; a same-key retry then returns `409`. Provider-attributed transient failures without a captured success can still release the key for another attempt. The absence of a handle alone does not tell you which outcome applies.

Check warning on line 199 in development/comfy-router/api.mdx

View check run for this annotation

Mintlify / Mintlify Validation (dripart) - vale-spellcheck

development/comfy-router/api.mdx#L199

Did you really mean 'replayable'?

The SDKs retry some failures within a bounded budget. Once they return an error, keep the request and key rather than generating a new one. For raw HTTP, this example retries only the two explicit collection hints:

Check warning on line 201 in development/comfy-router/api.mdx

View check run for this annotation

Mintlify / Mintlify Validation (dripart) - vale-spellcheck

development/comfy-router/api.mdx#L201

Did you really mean 'SDKs'?

```python
import os
Expand Down Expand Up @@ -262,4 +262,5 @@
## Next

- [Quickstart](/development/comfy-router/quickstart): installation, invocation, and saving the image.
- [Queued delivery](/development/comfy-router/queue): submit a request, follow its status, collect the result or cancel it without holding the connection.
- [API reference](/development/comfy-router/reference): endpoint parameters, schemas, and response codes.
4 changes: 2 additions & 2 deletions development/comfy-router/headers.mdx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
---
title: "Comfy Router headers"
sidebarTitle: "Headers"
description: "The request headers you can send to Comfy Router and the response headers it returns, for every model: authentication, idempotency, request IDs, error buckets, retry pacing and spend limits."

Check warning on line 4 in development/comfy-router/headers.mdx

View check run for this annotation

Mintlify / Mintlify Validation (dripart) - vale-spellcheck

development/comfy-router/headers.mdx#L4

Did you really mean 'idempotency'?
---

Every Router model uses `POST /v2/models/{provider}/{model}` with its own JSON body. This page covers the headers shared across models; the [API reference](/development/comfy-router/reference) lists the generated contract.

The Comfy SDKs (`comfy-sdk` for Python, `@comfyorg/sdk` for TypeScript) handle authentication and generate idempotency keys. They expose selected response metadata as described below. Raw HTTP clients must send and read the headers themselves.

Check warning on line 9 in development/comfy-router/headers.mdx

View check run for this annotation

Mintlify / Mintlify Validation (dripart) - vale-spellcheck

development/comfy-router/headers.mdx#L9

Did you really mean 'SDKs'?

Check warning on line 9 in development/comfy-router/headers.mdx

View check run for this annotation

Mintlify / Mintlify Validation (dripart) - vale-spellcheck

development/comfy-router/headers.mdx#L9

Did you really mean 'idempotency'?

## Request headers

<ParamField header="X-API-Key" type="string">
A Comfy API key, `comfyui-...`, created in [your Comfy workspace](https://platform.comfy.org/profile/api-keys). It uses your workspace's model access and credit balance. You can also send it as `Authorization: Bearer comfyui-...`; if both headers are present, `X-API-Key` wins.

Check warning on line 14 in development/comfy-router/headers.mdx

View check run for this annotation

Mintlify / Mintlify Validation (dripart) - vale-spellcheck

development/comfy-router/headers.mdx#L14

Did you really mean 'workspace's'?
</ParamField>

<ParamField header="Authorization" type="string">
Expand All @@ -19,7 +19,7 @@
</ParamField>

<ParamField header="Idempotency-Key" type="string">
Identifies one logical generation. Generate and store a UUID before the call, then reuse it for retries of the unchanged request. The key can replay a result or collect accepted work for up to 24 hours. The SDKs generate keys and let you supply your own (`idempotency_key=` in Python, `idempotencyKey` in TypeScript). See [retry outcomes](/development/comfy-router/api#retry-outcomes) for conflicts, expiry, and non-replayable results.

Check warning on line 22 in development/comfy-router/headers.mdx

View check run for this annotation

Mintlify / Mintlify Validation (dripart) - vale-spellcheck

development/comfy-router/headers.mdx#L22

Did you really mean 'SDKs'?
</ParamField>

<ParamField header="Content-Type" type="string">
Expand All @@ -27,7 +27,7 @@
</ParamField>

<ParamField header="If-None-Match" type="string">
On `GET /v2/models/{provider}/{model}/openapi.json` only. Send the `ETag` you hold from an earlier `200`; when it still matches, the answer is a bodyless `304` with the same `ETag`. Cache a model's schema for the life of your process and revalidate it this way rather than re-reading it before every call.

Check warning on line 30 in development/comfy-router/headers.mdx

View check run for this annotation

Mintlify / Mintlify Validation (dripart) - vale-spellcheck

development/comfy-router/headers.mdx#L30

Did you really mean 'bodyless'?
</ParamField>

## Response headers
Expand All @@ -41,11 +41,11 @@
</ResponseField>

<ResponseField name="Idempotent-Replayed" type="boolean">
Present and `true` when Router serves a stored result instead of running the model again. It is absent on a fresh run.
Present and `true` when Router serves a stored result instead of running the model again. It is absent on a fresh run. On the queued submit route, a replayed `201` returns the original request handle rather than queueing a second run.
</ResponseField>

<ResponseField name="Retry-After" type="integer">
Seconds to wait before retrying. On `409` / `concurrency_limit_exceeded` or `504` / `deadline_exceeded`, retry the same request and key after the wait. On `429` / `rate_limited`, it tells you when the rate limit resets.
Seconds to wait before retrying. On `409` / `concurrency_limit_exceeded` or `504` / `deadline_exceeded`, retry the same request and key after the wait. On `429` / `rate_limited`, it tells you when the rate limit resets. On a queued request's status read and its `202` result read, it is Router's hint for when polling again is worth the round trip.
</ResponseField>

<ResponseField name="X-Committed-Spend-Limit" type="integer">
Expand Down
2 changes: 1 addition & 1 deletion development/comfy-router/limitations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
| Show progress or stream output | No live progress, streaming, or preview frames during the call. | Show an indeterminate state, or use a supported proxy operation. |
| Recover after a lost connection | Same-key collection is available when Router retained a handle to an accepted generation. | Preserve the key and follow [retry guidance](/development/comfy-router/api#retry-outcomes). |
| Reconcile Comfy charges | No universal Comfy cost or credit-balance field on the response. | Use [workspace billing](https://platform.comfy.org). |
| Store results permanently | Asset URLs can expire, including rehosted and replayed URLs. | Download the assets; see [result assets](/development/comfy-router/reference#result-assets). |

Check warning on line 18 in development/comfy-router/limitations.mdx

View check run for this annotation

Mintlify / Mintlify Validation (dripart) - vale-spellcheck

development/comfy-router/limitations.mdx#L18

Did you really mean 'rehosted'?

## No queued submission

Router holds the connection while the model runs. For asynchronous providers, it submits the job and polls internally. It does not expose a Router job ID, status endpoint, callback, or webhook.
Router holds the connection while the model runs. For asynchronous providers, it submits the job and polls internally. Queued delivery (submit, get a `request_id`, poll, collect) is in a gated preview: see [Queued delivery](/development/comfy-router/queue). Outside the preview, Router does not expose a job ID, status endpoint, callback, or webhook.

If your request cannot stay open long enough, call Router from a worker and track the job in your application. Use a [partner proxy](#router-does-not-cover-every-partner-operation) when you need the provider's submit-and-poll controls.

Expand All @@ -27,13 +27,13 @@

Router's default deadline is **10 minutes**, configurable by the deployment. Set your client timeout above it so Router can return its error and request ID first.

`504` / `deadline_exceeded` means Router stopped waiting; `504` / `provider_timeout` means the provider timed out. A timeout or lost connection does not prove that a generation was unbilled, and it does not cancel accepted provider work. Read [timeouts and collection](/development/comfy-router/api#timeouts-and-collection) before retrying.

Check warning on line 30 in development/comfy-router/limitations.mdx

View check run for this annotation

Mintlify / Mintlify Validation (dripart) - vale-spellcheck

development/comfy-router/limitations.mdx#L30

Did you really mean 'unbilled'?

<span id="no-way-to-resume-a-call-you-lost" />

## Recovery depends on the provider

Router can retain a provider handle for an accepted submit-and-poll generation. Reuse the same `Idempotency-Key` to collect it later; completed replayable responses can also come from the key record.

Check warning on line 36 in development/comfy-router/limitations.mdx

View check run for this annotation

Mintlify / Mintlify Validation (dripart) - vale-spellcheck

development/comfy-router/limitations.mdx#L36

Did you really mean 'replayable'?

Not every disconnected call is recoverable. Preserve the request and key before sending, then use the [retry outcome table](/development/comfy-router/api#retry-outcomes). A new key creates a new call and may incur another charge.

Expand Down Expand Up @@ -70,7 +70,7 @@

Input and output fields vary by model. Moving from a provider SDK or proxy can change both the route and how you read the result.

Some assets are rehosted on Comfy storage; others are provider URLs or inline bytes. See [Result assets](/development/comfy-router/reference#result-assets) for lifetimes and replay behavior.

Check warning on line 73 in development/comfy-router/limitations.mdx

View check run for this annotation

Mintlify / Mintlify Validation (dripart) - vale-spellcheck

development/comfy-router/limitations.mdx#L73

Did you really mean 'rehosted'?

## Next

Expand Down
Loading
Loading