-
Notifications
You must be signed in to change notification settings - Fork 5
chore: sync vendored Comfy API v2 spec (cloud@7c65b1a) #48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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: | ||
|
|
@@ -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: [] | ||
| - {} | ||
|
|
@@ -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. | ||
|
|
@@ -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. | ||
|
|
@@ -233,6 +245,44 @@ paths: | |
| $ref: '#/components/responses/NotFound' | ||
| '500': | ||
| $ref: '#/components/responses/UpstreamError' | ||
| delete: | ||
| operationId: deleteAsset | ||
| tags: | ||
| - assets | ||
| summary: Delete an asset record | ||
| description: 'Deletes the asset RECORD. The underlying content-addressed blob is | ||
|
|
||
| untouched while any other asset still references it (hash dedup means | ||
|
|
||
| blobs are shared) — deleting an asset never destroys another asset''s | ||
|
|
||
| bytes. | ||
|
|
||
|
|
||
| A second delete of the same id returns `404`, indistinguishable from | ||
|
|
||
| an id that never existed or belongs to another account. | ||
|
|
||
| ' | ||
| parameters: | ||
| - $ref: '#/components/parameters/AssetId' | ||
| responses: | ||
| '204': | ||
| description: Record deleted. | ||
| '401': | ||
| $ref: '#/components/responses/Unauthorized' | ||
| '403': | ||
| $ref: '#/components/responses/Forbidden' | ||
| '404': | ||
| $ref: '#/components/responses/NotFound' | ||
| '409': | ||
| description: '`asset_in_use` — the record cannot be deleted while the platform still depends on it. Each surface defines its own holds (for example: a job''s outputs reference the record, or a content-moderation workflow requires it to be preserved); the response body deliberately never says which hold applies.' | ||
| content: | ||
| application/json: | ||
| schema: | ||
| $ref: '#/components/schemas/ErrorEnvelope' | ||
| '500': | ||
| $ref: '#/components/responses/UpstreamError' | ||
| /api/v2/assets/{id}/content: | ||
| get: | ||
| operationId: getAssetContent | ||
|
|
@@ -394,7 +444,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' | ||
|
|
@@ -440,6 +490,32 @@ paths: | |
| $ref: '#/components/responses/RateLimited' | ||
| '500': | ||
| $ref: '#/components/responses/UpstreamError' | ||
| /api/v2/jobs/{id}/workflow: | ||
| get: | ||
| operationId: getJobWorkflow | ||
| tags: | ||
| - jobs | ||
| summary: The workflow behind a job — authoring version if pinned, executed graph otherwise | ||
| description: "Returns the workflow behind a job. The response's `format` field says\nwhich of two different shapes `workflow` is in:\n\n- `format: save` — the original authoring workflow exactly as saved\n in the Comfy Cloud editor at the version the job ran, including\n canvas layout and frontend-only nodes (e.g. Note nodes; Get/Set\n nodes not yet expanded). Returned only when the job is pinned to a\n specific workflow version — see the \"when you get which\" note\n below.\n- `format: api` — the executed API-format prompt graph the job\n actually ran: frontend-only constructs are gone and Get/Set nodes\n are expanded. This is the same shape `POST /api/v2/jobs`'s\n `workflow` request field takes, and never includes the\n submission's `extra_data`, which can carry a live credential.\n\nAlways branch on `format`, never assume one or the other — which\nshape comes back depends on how the job was submitted, not on\nanything the caller controls per-request.\n\nA deliberate sub-resource, not a field on `GET /api/v2/jobs/{id}` —\nso the polling workhorse stays cheap and a caller pays for this only\nwhen it actually wants the workflow (for example, to recover what\nproduced a given output).\n\nTied to the job's own retention: this 404s under the same conditions\n`GET /api/v2/jobs/{id}` does (unknown, not-yours, or past its\nretention deadline) — there is no separate lifetime for the\nworkflow.\n\n**When you get which:** a job only carries a pinned workflow version\nwhen it was submitted with that association. Today that means jobs\nsubmitted from the Comfy Cloud frontend/editor. Jobs submitted\ndirectly through this v2 API (`POST /api/v2/jobs`) do not carry that\nassociation — v2 job submission has no version-linking fields yet —\nso they always get `format: api`. This is expected, not a bug: it\nwill change once v2 submission grows the same version pinning.\n\nA job pinned to a version also falls back to `format: api` if that\nversion, or the workflow it belongs to, is no longer readable by the\ncaller — for example the caller deleted the workflow since the job\nran. This is the same fallback as an unpinned job, and for the same\nreason: it is preferable to the alternative of erroring the whole\nrequest over data that is genuinely gone.\n" | ||
| parameters: | ||
| - $ref: '#/components/parameters/JobId' | ||
| responses: | ||
| '200': | ||
| description: The workflow graph. | ||
| content: | ||
| application/json: | ||
| schema: | ||
| $ref: '#/components/schemas/JobWorkflowResponse' | ||
| '401': | ||
| $ref: '#/components/responses/Unauthorized' | ||
| '403': | ||
| $ref: '#/components/responses/Forbidden' | ||
| '404': | ||
| $ref: '#/components/responses/NotFound' | ||
| '429': | ||
| $ref: '#/components/responses/RateLimited' | ||
| '500': | ||
| $ref: '#/components/responses/UpstreamError' | ||
| /api/v2/jobs/{id}/events: | ||
| get: | ||
| operationId: getJobEvents | ||
|
|
@@ -573,14 +649,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 | ||
|
|
@@ -643,7 +719,7 @@ components: | |
| properties: | ||
| id: | ||
| type: string | ||
| example: asset_01JZV8Q3M7K2W9X0Y1Z2A3B4C5 | ||
| example: 9f8a1c0d-2b3e-4f56-8a7b-1c2d3e4f5a6b | ||
| hash: | ||
| type: string | ||
| nullable: true | ||
|
|
@@ -673,6 +749,15 @@ 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_id: | ||
| type: string | ||
| nullable: true | ||
| description: ID of the job that produced this asset. Absent for uploaded assets, which have no producing job. | ||
| Job: | ||
| type: object | ||
| description: One execution of a workflow. Durable from creation until `expires_at`; `outputs` populates incrementally during execution. | ||
|
|
@@ -691,7 +776,7 @@ components: | |
| properties: | ||
| id: | ||
| type: string | ||
| example: job_01JZTGXW9Q2M4R8V0B1N3P5D7F | ||
| example: 7f3d2c1b-9a8e-4d6f-b012-3c4d5e6f7a8b | ||
| status: | ||
| $ref: '#/components/schemas/JobStatus' | ||
| created_at: | ||
|
|
@@ -736,6 +821,23 @@ components: | |
| execution_ms: 42000 | ||
| urls: | ||
| $ref: '#/components/schemas/JobUrls' | ||
| JobWorkflowResponse: | ||
| type: object | ||
| description: The workflow behind a job. See GET /api/v2/jobs/{id}/workflow's description for exactly when `format` is `save` vs `api`. | ||
| required: | ||
| - workflow | ||
| - format | ||
| properties: | ||
| workflow: | ||
| type: object | ||
| description: The workflow, verbatim, in the shape `format` says. | ||
| additionalProperties: true | ||
| format: | ||
| type: string | ||
| enum: | ||
| - save | ||
| - api | ||
| description: 'Discriminates the `workflow` field''s shape. `save`: the original authoring workflow JSON, at the version pinned to the job. `api`: the executed API-format prompt graph.' | ||
| JobStatus: | ||
| type: string | ||
| enum: | ||
|
|
@@ -755,7 +857,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. | ||
| required: | ||
| - self | ||
| - events | ||
|
|
@@ -843,7 +945,7 @@ components: | |
| id: | ||
| type: string | ||
| description: Asset UUID. | ||
| example: asset_01JZV9R4N8... | ||
| example: 9f8a1c0d-2b3e-4f56-... | ||
| hash: | ||
| type: string | ||
| nullable: true | ||
|
|
@@ -854,6 +956,10 @@ components: | |
| url_expires_at: | ||
| type: string | ||
| format: date-time | ||
| job_id: | ||
| type: string | ||
| nullable: true | ||
| description: ID of the job that produced this output. | ||
| OutputType: | ||
| type: string | ||
| enum: | ||
|
|
@@ -899,6 +1005,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". | ||
|
Comment on lines
+1008
to
+1018
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win Document The shared error contract adds Proposed fix '422':
- description: '`invalid_workflow` (with per-node details), `workflow_format_ui`, `missing_asset`, or `idempotency_key_reuse`.'
+ description: '`invalid_workflow` (with per-node details), `workflow_format_ui`, `missing_asset`, `idempotency_key_reuse`, or, on deployment-scoped surfaces, `deployment_stopped`.'🤖 Prompt for AI Agents |
||
|
|
||
| ' | ||
| required: | ||
| - error | ||
|
|
@@ -964,7 +1082,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 | ||
|
|
@@ -980,7 +1098,7 @@ components: | |
| properties: | ||
| id: | ||
| type: string | ||
| example: asset_01JZV8Q3M7K2W9X0Y1Z2A3B4C5 | ||
| example: 9f8a1c0d-2b3e-4f56-8a7b-1c2d3e4f5a6b | ||
| hash: | ||
| type: string | ||
| example: blake3:9f8a1c0d... | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Use a complete UUID example.
Output.idis documented as an asset UUID, but this example contains...and is not a UUID. Generated documentation and example-driven clients will receive a nonrepresentative identifier.Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents