Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 45 additions & 16 deletions spec/openapi.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
# Comfy API v2 — public spec, vendored into this SDK.
# Comfy API v2 — public specification.
#
# GENERATED / VENDORED ONE-WAY — DO NOT HAND-EDIT.
# GENERATED ONE-WAY — DO NOT HAND-EDIT.
# Projected automatically from the canonical Comfy API v2 contract and
# synced in by CI. Change the upstream contract, not this copy: the SDK's
# own CI regenerates its low layer from this file and FAILS ON DRIFT.
# synced by CI. Change the upstream contract, not this public copy.

openapi: 3.0.3
info:
Expand All @@ -15,11 +14,12 @@ servers:
description: Self-hosted (comfy-api-proxy)
- url: https://cloud.comfy.org
description: Comfy Cloud
- url: https://{deployment}.comfy.org
description: Serverless deployment (URL shape not final)
- url: https://{deployment}.run.comfy.app
description: Serverless deployment
variables:
deployment:
default: my-deployment
description: DNS-safe deployment id (subdomain label). Staging uses {deployment}.stg.run.comfy.app.
default: dep-1234abcd-56ef-7890-abcd-ef1234567890
security:
- bearerAuth: []
- {}
Expand Down Expand Up @@ -91,6 +91,12 @@ paths:
items:
type: string
description: Category tags (e.g. `input`).
expires_in:
type: integer
minimum: 60
maximum: 604800
description: 'Optional retention override in seconds (60s–7d): the asset''s `expires_at` becomes now + `expires_in`, replacing the platform''s default retention. Implementations without configurable retention ignore it. The bounds apply to this override only — the platform default is operator-configured and may lie outside them.'
example: 86400
responses:
'201':
description: New blob stored; asset minted.
Expand Down Expand Up @@ -166,6 +172,12 @@ paths:
type: array
items:
type: string
expires_in:
type: integer
minimum: 60
maximum: 604800
description: 'Optional retention override in seconds (60s–7d): the asset''s `expires_at` becomes now + `expires_in`, replacing the platform''s default retention. Implementations without configurable retention ignore it. The bounds apply to this override only — the platform default is operator-configured and may lie outside them.'
example: 86400
responses:
'201':
description: Asset minted over the existing blob.
Expand Down Expand Up @@ -394,7 +406,7 @@ paths:
schema:
$ref: '#/components/schemas/ErrorEnvelope'
'429':
description: '`queue_full` bounded queue depth reached.'
description: '`queue_full` (bounded queue depth reached) or, on deployment-scoped surfaces, `deployment_not_ready` (deployment still provisioning/starting). Disambiguate by `error.code`; both mean back off and retry after `Retry-After`.'
headers:
Retry-After:
$ref: '#/components/headers/RetryAfter'
Expand Down Expand Up @@ -573,14 +585,14 @@ components:
required: true
schema:
type: string
example: job_01JZTGXW9Q2M4R8V0B1N3P5D7F
example: 7f3d2c1b-9a8e-4d6f-b012-3c4d5e6f7a8b
AssetId:
name: id
in: path
required: true
schema:
type: string
example: asset_01JZV8Q3M7K2W9X0Y1Z2A3B4C5
example: 9f8a1c0d-2b3e-4f56-8a7b-1c2d3e4f5a6b
BlakeHash:
name: hash
in: path
Expand Down Expand Up @@ -643,7 +655,7 @@ components:
properties:
id:
type: string
example: asset_01JZV8Q3M7K2W9X0Y1Z2A3B4C5
example: 9f8a1c0d-2b3e-4f56-8a7b-1c2d3e4f5a6b
hash:
type: string
nullable: true
Expand Down Expand Up @@ -673,6 +685,11 @@ components:
url_expires_at:
type: string
format: date-time
expires_at:
type: string
format: date-time
nullable: true
description: 'Retention deadline for the asset itself (distinct from `url_expires_at`, the signed URL''s validity). Null or absent means the asset is non-expiring. On a dedup-hit create response the deadline may be later than now + the requested/default retention: re-referencing content extends its retention, never shortens it.'
Job:
type: object
description: One execution of a workflow. Durable from creation until `expires_at`; `outputs` populates incrementally during execution.
Expand All @@ -691,7 +708,7 @@ components:
properties:
id:
type: string
example: job_01JZTGXW9Q2M4R8V0B1N3P5D7F
example: 7f3d2c1b-9a8e-4d6f-b012-3c4d5e6f7a8b
status:
$ref: '#/components/schemas/JobStatus'
created_at:
Expand Down Expand Up @@ -755,7 +772,7 @@ components:
'
JobUrls:
type: object
description: Embedded follow-up links — follow these, don't build URLs.
description: Embedded follow-up links — follow these, don't build URLs. A link is either an absolute URL or a host-relative reference (leading `/`) that already includes any prefix the serving surface is mounted under (e.g. a serverless gateway's `/deployment/{deployment_id}/api/v2`). Clients MUST resolve a host-relative link against the request origin (scheme + authority), never against a configured base URL — joining it to a base URL that carries the same mount prefix duplicates the prefix.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n -C 6 'JobUrls|urljoin|urlparse|base_url|origin|\.urls\b' src --glob '*.py'

Repository: Comfy-Org/comfy-python-sdk

Length of output: 19724


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- transport URL resolution ---'
sed -n '86,132p' src/comfy_low/transport.py

printf '%s\n' '--- all JobUrls field consumers ---'
rg -n -C 4 '\.urls\.(self|cancel|events)|JobUrls|urls\s*=' src tests spec --glob '*.py' --glob '*.yaml' --glob '*.yml' 2>/dev/null || true

printf '%s\n' '--- URL helper call sites ---'
rg -n -C 3 'self\._p\.url|_p\.url|\.url\(' src/comfy_low src/comfy_sdk --glob '*.py'

Repository: Comfy-Org/comfy-python-sdk

Length of output: 16741


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from urllib.parse import urlsplit

def resolved(base_url: str, path: str) -> str:
    base_url = base_url.rstrip("/")
    parts = urlsplit(base_url)
    origin_url = f"{parts.scheme}://{parts.netloc}"
    if path.startswith("http"):
        return path
    if path.startswith("/") and "/api/" in path:
        return origin_url + path
    return base_url + "/api/v2" + path

cases = [
    ("https://gateway.example/deployment/abc/api/v2", "/deployment/abc/api/v2/jobs/1"),
    ("https://gateway.example/deployment/abc/api/v2", "/jobs/1"),
    ("https://gateway.example/api/v2", "/custom/jobs/1"),
]
for base, link in cases:
    print(f"base={base!r} link={link!r} -> {resolved(base, link)!r}")
PY

Repository: Comfy-Org/comfy-python-sdk

Length of output: 562


Resolve every host-relative JobUrls link against the request origin

_Prepared.url() uses the request origin only when the path contains "/api/". A valid mounted link without that substring is treated as API-relative, so the client prepends base_url and _API and can duplicate the mount prefix. Separate server-link resolution from ordinary API paths, and resolve every host-relative JobUrls link against the origin.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@spec/openapi.yaml` at line 775, Update _Prepared.url() to identify JobUrls
links independently of whether the path contains "/api/"; every host-relative
JobUrls link must resolve against the request origin (scheme and authority),
while ordinary API paths retain their existing base_url and _API resolution.

required:
- self
- events
Expand Down Expand Up @@ -843,7 +860,7 @@ components:
id:
type: string
description: Asset UUID.
example: asset_01JZV9R4N8...
example: 9f8a1c0d-2b3e-4f56-...

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use a complete UUID for Output.id.

9f8a1c0d-2b3e-4f56-... is not a UUID. It conflicts with the Asset UUID description and the complete UUID examples elsewhere in the schema.

Proposed fix
-          example: 9f8a1c0d-2b3e-4f56-...
+          example: 9f8a1c0d-2b3e-4f56-8a7b-1c2d3e4f5a6b
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
example: 9f8a1c0d-2b3e-4f56-...
example: 9f8a1c0d-2b3e-4f56-8a7b-1c2d3e4f5a6b
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@spec/openapi.yaml` at line 863, Replace the truncated example for Output.id
in the OpenAPI schema with a complete valid UUID, consistent with the Asset UUID
description and the complete UUID examples used elsewhere.

hash:
type: string
nullable: true
Expand Down Expand Up @@ -899,6 +916,18 @@ components:

`not_found` (404), `unauthorized` (401), `forbidden` (403).

Deployment-scoped surfaces add: `deployment_not_ready` (429 +

Retry-After — the deployment can still reach ready; retry) and

`deployment_stopped` (422 — terminal deployment state; a retry

cannot succeed without operator action). A 429 is disambiguated

by `error.code` alone; clients should treat any 429 + Retry-After

as "back off and retry".

'
required:
- error
Expand Down Expand Up @@ -964,7 +993,7 @@ components:
type: string
AssetReference:
type: object
description: "The typed asset-reference object placed inside workflow JSON where a\nfilename would normally go (documented here for tooling; it is not a\nrequest/response body itself):\n\n {\"__type\": \"core/ASSET\",\n \"info\": {\"id\": \"asset_...\", \"hash\": \"blake3:...\",\n \"file_path\": \"photo.png\"}}\n\n`info.id` (the asset UUID) is required in v1 and authoritative;\n`hash` and `file_path` are optional staging/lookup hints and never\noverride a present `id`. A malformed reference or one that is not\nresolvable/owned by the caller fails submission with 422\n`missing_asset`.\n"
description: "The typed asset-reference object placed inside workflow JSON where a\nfilename would normally go (documented here for tooling; it is not a\nrequest/response body itself):\n\n {\"__type\": \"core/ASSET\",\n \"info\": {\"id\": \"<asset-uuid>\", \"hash\": \"blake3:...\",\n \"file_path\": \"photo.png\"}}\n\n`info.id` (the asset UUID) is required in v1 and authoritative;\n`hash` and `file_path` are optional staging/lookup hints and never\noverride a present `id`. A malformed reference or one that is not\nresolvable/owned by the caller fails submission with 422\n`missing_asset`.\n"
required:
- __type
- info
Expand All @@ -980,7 +1009,7 @@ components:
properties:
id:
type: string
example: asset_01JZV8Q3M7K2W9X0Y1Z2A3B4C5
example: 9f8a1c0d-2b3e-4f56-8a7b-1c2d3e4f5a6b
hash:
type: string
example: blake3:9f8a1c0d...
Expand Down
Loading