> ## Documentation Index
> Fetch the complete documentation index at: https://dripart-matt-router-pricing-pika-and-guard.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Comfy SDK: ComfyUI용 Python 및 TypeScript 클라이언트

> Python 또는 TypeScript로 Comfy Cloud, 자체 배포 또는 자체 ComfyUI를 대상으로 ComfyUI 워크플로를 실행하고, 에셋, 작업, 라이브 이벤트, 멱등 재시도를 다룹니다.

<Note>
  Comfy API v2는 버전 관리됩니다. v2 내 변경 사항은 추가적으로 제공되며, 호환성을 깨뜨리는 변경은 v3로 출시됩니다. 문제는 [피드백](#피드백)을 통해 알려주세요.
</Note>

**하나의 패키지, 두 개의 표면.** `comfy-sdk`(PyPI)와 `@comfyorg/sdk`(npm)는 서로 다른 두 서비스와 통신하는 두 개의 클라이언트를 제공합니다. 메서드 이름이 양쪽에서 겹치지만(`run`, `submit`, `events`) 각각 다른 의미를 가지므로, 스니펫을 복사하기 전에 그 코드가 어떤 클라이언트를 만드는지 확인하세요.

| 표면 | 기능 | 접근 방법 | 기본 URL | 인증 |
| - | - | - | - | - |
| **[Comfy Router](/ko/development/comfy-router/quickstart)** | 해당 공급자의 자체 요청 본문으로 파트너 모델(FLUX, Veo, Gemini, Kling)을 호출하고, 그 고유한 응답을 그대로 반환합니다 | Python: `Comfy()`의 `client.models`. TypeScript: 모듈 수준 `comfy` 네임스페이스의 `comfy.models` | `https://api.comfy.org`, 라우트 `/v2/models/{provider}/{model}` | `COMFY_API_KEY`, 또는 `comfy.config({ credentials })` |
| **[Comfy Cloud 및 Comfy API v2](/ko/development/api-development/sdks)** | API 형식 ComfyUI 워크플로를 처음부터 끝까지 실행합니다: 에셋 업로드, 그래프 제출, 작업 추적, 출력 다운로드 | `Comfy(api_key=...)` / `new Comfy({ apiKey })`, 그다음 `client.workflows`, `client.assets`, `client.jobs` | 기본값은 `https://cloud.comfy.org`이며, `COMFY_BASE_URL`이 지정하는 주소도 사용할 수 있습니다 | 생성자의 `api_key` / `apiKey` |

어느 표면도 다른 쪽을 감싸지 않으며, [Comfy 워크스페이스](https://platform.comfy.org/profile/api-keys)에서 생성한 API 키 하나로 둘 다 사용할 수 있습니다.

<Note>
  두 언어는 Router 표면을 서로 다르게 노출합니다. Python에서는 `Comfy()` 하나가 둘 다 담당합니다. Router에는 `client.models.run(...)`, Cloud에는 `client.workflows` / `client.assets` / `client.jobs`를 사용합니다. TypeScript에서는 의도적으로 비슷한 이름을 가진 별도의 export입니다. `comfy`(소문자, 모듈 수준 네임스페이스)에는 `comfy.models`가 있고, `Comfy`(클래스)는 Cloud 클라이언트이며 `.models`가 없습니다.
</Note>

이 페이지에서는 두 번째 행, 즉 Comfy Cloud와 Comfy API v2 클라이언트를 다룹니다. 모델 라우터에 대해서는 [Comfy Router 빠른 시작](/ko/development/comfy-router/quickstart)에서 시작하세요.

Comfy SDK를 사용하면 애플리케이션에서 ComfyUI 워크플로를 실행하고 결과를 돌려받을 수 있습니다. 워크플로를 제출하면 ComfyUI가 실행하고 출력을 다운로드합니다. 동일한 코드가 Comfy Cloud 또는 직접 호스팅하는 ComfyUI 인스턴스에서 실행됩니다. 기본 URL만 변경됩니다. 단, [작업 레이블](#작업에-레이블을-붙이고-나중에-찾기)은 Comfy API 배포에서만 동작하고, 작업 목록은 Comfy Cloud에서 아직 제공되지 않습니다.

SDK는 [Comfy API v2](/ko/api-reference/v2/overview)용 클라이언트입니다. Comfy API v2는 장기적으로 지원할 계획인 버전 관리되는 HTTP API입니다. ComfyUI의 새 릴리스는 이를 기반으로 구축된 통합을 깨뜨리지 않습니다.

이 방식으로 구축하는 것들:

* Blender나 Krita와 같은 다른 애플리케이션 내부에서 콘텐츠를 생성하는 플러그인
* 사용자를 대신하여 생성 작업을 실행하는 소비자 앱
* 배치 파이프라인, 예를 들어 비디오의 모든 프레임에 워크플로 하나를 실행하는 경우
* 한 번에 많은 워크플로를 동시에 실행해야 하는 백엔드 서비스

<Note>
  이 SDK는 ComfyUI를 **외부에서** 구동합니다. ComfyUI **내부에서** 실행되는 커스텀 노드나 프런트엔드 확장 프로그램을 작성하는 경우에는 [커스텀 노드 개발](/ko/custom-nodes/overview)을 대신 참조하세요. 이들은 별도의 API 집합입니다.
</Note>

## 설치

<CodeGroup>
  ```bash Python theme={null}
  pip install comfy-sdk
  ```

  ```bash TypeScript theme={null}
  npm i @comfyorg/sdk
  ```
</CodeGroup>

Python 3.10 이상. Node 22 이상.

<Note>
  **현재 릴리스: 0.5.0.** PyPI의 [`comfy-sdk`](https://pypi.org/project/comfy-sdk/)와 npm의 [`@comfyorg/sdk`](https://www.npmjs.com/package/@comfyorg/sdk)는 함께 릴리스되며 동일한 버전 번호를 공유합니다. 최신 버전을 설치하거나(`pip install comfy-sdk`, `npm install @comfyorg/sdk`), 정확한 고정 대신 범위를 지정하세요. 두 생태계는 이를 다르게 표기합니다. `comfy-sdk>=0.5`는 하한이며 이후 마이너 버전을 허용하지만, `@comfyorg/sdk@^0.5`는 `0.5.x`에 대한 npm의 호환 범위로 `0.6.0` 직전에서 멈추므로, 새 마이너 버전이 나오면 캐럿 범위를 올려야 합니다. npm 쪽도 이후 마이너 버전을 따라가게 하려면 `@comfyorg/sdk@>=0.5.0`을 사용하세요. 이 안내가 뒤처져 있다면 레지스트리 페이지가 우선입니다.
</Note>

## 빠른 시작

입력 이미지를 업로드하고 워크플로를 실행한 다음 결과를 디스크에 기록합니다.

<CodeGroup>
  ```python Python theme={null}
  from comfy_sdk import Comfy

  # Comfy Cloud
  client = Comfy(api_key="comfyui-...")

  wf = client.workflows.from_file("workflow_api.json")

  asset = client.assets.from_file("photo.png")
  wf.set_input("10", "image", asset)

  job = client.run(wf)
  for output in job.get_outputs("9"):
      output.to_file(output.name)
  ```

  ```typescript TypeScript theme={null}
  import { Comfy } from "@comfyorg/sdk";

  // Comfy Cloud
  const client = new Comfy({ apiKey: "comfyui-..." });

  const wf = await client.workflows.fromFile("workflow_api.json");

  const asset = client.assets.fromFile("photo.png");
  wf.setInput("10", "image", asset);

  const job = await client.run(wf);
  await job.getOutputs("9")[0].toFile("out.png");
  ```
</CodeGroup>

`workflow_api.json`은 [API 형식](/ko/development/api-development/workflow-api-format)으로 저장된 워크플로입니다. `"10"`과 `"9"`는 해당 파일의 노드 ID로, 입력 이미지를 받는 노드와 결과를 가져올 출력 노드를 나타냅니다.

에셋 핸들은 지연 방식으로 동작합니다. `photo.png`는 로컬에서 해시되며 서버에 해당 바이트가 없을 때만 업로드되므로, 동일한 입력으로 다시 실행해도 비용이 들지 않습니다.

`run()`은 작업을 제출하고 터미널 상태에 도달할 때까지 대기합니다. 실행되는 동안 다른 작업을 수행하려면 `submit()`을 대신 사용하고 [이벤트 스트림](#작업-실행-보기)을 확인하세요.

대신 자체 ComfyUI를 대상으로 실행하려면 `COMFY_BASE_URL`을 설정하고 키를 제거하세요. 아래를 참조하세요.

## 기본 URL 선택

| 연결 대상 | 기본 URL | API 키 |
| - | - | - |
| **Comfy Cloud** | `https://cloud.comfy.org` (기본값) | 필수 |
| **Comfy API 배포** | `https://<deployment>.run.comfy.app` | 필수 |
| **자체 ComfyUI** | `http://127.0.0.1:8189` (로컬 프록시) | 기본적으로 없음. 선택적 정적 Bearer 토큰 |

기본 URL은 생성자 인자가 아닌 `COMFY_BASE_URL` 환경 변수에서 가져옵니다:

```bash theme={null}
export COMFY_BASE_URL="https://<deployment>.run.comfy.app"  # Comfy API 배포
export COMFY_BASE_URL="http://127.0.0.1:8189"               # 자체 호스팅 프록시
```

이 값은 클라이언트가 생성될 때마다 읽히며, `http(s)` URL이어야 합니다. 설정되지 않았거나 비어 있으면 Comfy Cloud를 의미합니다. 따라서 클라이언트 자체는 어디서나 동일합니다:

<CodeGroup>
  ```python Python theme={null}
  client = Comfy(api_key="comfyui-...")
  ```

  ```typescript TypeScript theme={null}
  const client = new Comfy({ apiKey: "comfyui-..." });
  ```
</CodeGroup>

<Note>
  초기 빌드에서 업그레이드하시나요? `Comfy("<url>", "<key>")`는 이제 `COMFY_BASE_URL`을 설정한 상태의 `Comfy(api_key="<key>")`입니다. `api_key`는 키워드 전용이므로, 이전의 위치 기반 호출은 URL을 키로 조용히 읽어들이는 대신 `TypeError`를 발생시킵니다.
</Note>

### Comfy Cloud

즉시 사용할 수 있습니다. [API 키](/ko/development/api-development/getting-an-api-key)를 생성하여 클라이언트에 전달하세요.

<Note>
  API 액세스에는 유료 Comfy Cloud 구독이 필요합니다. 무료 티어에는 포함되지 않습니다. 동시에 실행할 수 있는 작업 수는 티어에 따라 다릅니다. [동시 작업](/ko/development/deploy/cloud-usage#동시-작업)을 참조하세요.
</Note>

<a id="serverless-deployment" />

### Comfy API 배포

[개발자 플랫폼](https://platform.comfy.org)을 통해 배포한 워크플로에는 자체 엔드포인트가 제공됩니다. `COMFY_BASE_URL`을 해당 엔드포인트로 지정하고 Comfy Cloud와 동일하게 API 키를 사용하세요. 이 가이드의 모든 내용이 동일하게 작동합니다. 단, [작업 레이블과 작업 목록](#작업에-레이블을-붙이고-나중에-찾기)은 Comfy Cloud에서 아직 지원되지 않습니다.

Comfy API 배포는 Build에 포함된 모델과 커스텀 노드를 사용하여 여러 워크플로를 실행할 수 있습니다. 각 작업에 대해 `get_workflow()`는 `format: "api"`를 가지며 실행된 그래프를 `.graph` 속성에 담은 워크플로 응답을 반환합니다.

### 자체 ComfyUI

자체 호스팅 ComfyUI에서는 함께 실행되는 작은 오픈소스 서비스인 [comfy-api-proxy](https://github.com/Comfy-Org/comfy-api-proxy)가 v2 API를 제공합니다. 설치하고 실행한 다음 `COMFY_BASE_URL="http://127.0.0.1:8189"`를 설정하세요:

```bash theme={null}
pip install comfy-api-proxy
comfy-api-proxy
export COMFY_BASE_URL="http://127.0.0.1:8189"
```

설정과 인증은 [자체 호스팅 ComfyUI용 API 프록시](/ko/development/comfyui-server/api-proxy)를 참고하세요.

## 제출 재시도: 멱등성 키

`submit()`과 `run()`은 제출할 때마다 `Idempotency-Key`를 전송하며, 이 키에 대한 Comfy API v2 계약은 **중복 시 거부이며, 기록 후 재생이 아닙니다**. submit을 재시도 루프로 감싸기 전에 이 내용을 먼저 읽어 보세요.

* **호출할 때마다 새 키가 발급됩니다.** 같은 워크플로로 `submit()`을 두 번 호출하면 제출 두 번, 작업 두 개, 청구 두 번입니다. `submit()`을 감싼 순진한 `for attempt in range(3)`는 재시도가 아니라 이중 청구입니다.
* **재사용한 키는 재생되지 않고 거부됩니다.** 재시도를 멱등하게 만들려면 직접 `idempotency_key`(TypeScript에서는 `idempotencyKey`)를 전달하세요. 그러면 해당 키를 사용한 두 번째 요청은 첫 번째 작업을 반환하는 대신 `422 idempotency_key_reuse`로 실패합니다. SDK는 `IdempotencyKeyReuse`를 발생시킵니다. 이는 "멱등 재시도"가 보통 의미하는 것과 반대이므로 명시적으로 처리해야 합니다.
* **복구 방법은 다시 제출하는 것이 아니라 작업을 찾아가는 것입니다.** `IdempotencyKeyReuse`가 발생하면 첫 번째 시도가 작업을 생성했을 가능성이 충분히 있습니다. `client.jobs.get(job_id)`로 해당 작업을 가져오세요. 이 조회에는 이미 저장해 둔 id가 필요하므로, 제출이 반환된 뒤 실패 전에 id를 영속화한 경우에만 복구할 수 있습니다. id가 기록되기 전에 연결이 끊어졌다면 조회할 대상도, 자동 복구도 없습니다. 아래 예제는 점유된 키로 다시 제출하는 대신 수동 처리를 위해 예외를 다시 발생시킵니다.
* **제출이 확실히 실패하면 키가 해제됩니다.** 검증 오류, 크레딧 부족으로 인한 거부, 실행 대기열 가득 참으로 인한 거부는 작업을 생성하지 않고 키를 해제하므로, 그 키로 다시 제출해도 괜찮습니다. *모호한* 실패(읽기 타임아웃, 요청 도중 끊긴 연결) 이후에는 키가 계속 점유된 상태로 남습니다. 다시 제출하지 말고 작업을 찾으세요.
* **키는 24시간 후 만료되며**, 비어 있지 않은 출력 가능 ASCII여야 하고 길이 제한을 넘지 않아야 합니다. 유효하지 않은 키는 요청이 전송되기 전에 `ValueError`를 발생시키므로, 명시적인 `""`가 조용히 발급된 키로 대체되는 일은 없습니다.

따라서 두 번 이상 실행해도 안전한 재시도는 시도 간에 두 가지를 유지해야 합니다. 키는 서버가 재시도와 새 제출을 구분할 수 있게 해 주고, 작업 ID는 점유된 키가 이미 생성한 작업을 찾을 수 있게 해 줍니다.

<CodeGroup>
  ```python Python theme={null}
  import uuid
  from comfy_sdk import IdempotencyKeyReuse

  # 두 값 모두 요청이 당신에게 의미하는 바를 기준으로 당신의 저장소에 보관합니다.
  # `key`는 첫 번째 시도에서 한 번만 발급되고, `job_id`는 submit이 반환되는 즉시
  # 채워집니다.
  state = load_submission_state()  # {"key": ..., "job_id": ...} or {}
  key = state.get("key") or str(uuid.uuid4())
  save_submission_state(key=key)

  try:
      job = client.submit(wf, idempotency_key=key)
      save_submission_state(key=key, job_id=job.id)
  except IdempotencyKeyReuse:
      # 이 키를 사용한 이전 시도가 이미 제출까지 도달했으므로 작업이 존재할 수
      # 있습니다. 다시 제출하지 말고 찾아가세요.
      job_id = state.get("job_id")
      if job_id is None:
          raise  # 조회할 기록이 없음. 사람의 개입이 필요합니다
      job = client.jobs.get(job_id)
  ```

  ```typescript TypeScript theme={null}
  import { IdempotencyKeyReuse } from "@comfyorg/sdk";

  // 두 값 모두 요청이 당신에게 의미하는 바를 기준으로 당신의 저장소에 보관합니다.
  // `key`는 첫 번째 시도에서 한 번만 발급되고, `jobId`는 submit이 반환되는 즉시
  // 채워집니다.
  const state = await loadSubmissionState(); // { key?, jobId? }
  const key = state.key ?? crypto.randomUUID();
  await saveSubmissionState({ key });

  let job;
  try {
    job = await client.submit(wf, { idempotencyKey: key });
    await saveSubmissionState({ key, jobId: job.id });
  } catch (err) {
    // 이 키를 사용한 이전 시도가 이미 제출까지 도달했으므로 작업이 존재할 수
    // 있습니다. 다시 제출하지 말고 찾아가세요.
    if (!(err instanceof IdempotencyKeyReuse) || !state.jobId) throw err;
    job = await client.jobs.get(state.jobId);
  }
  ```
</CodeGroup>

`client.jobs.get(job_id)`는 SDK의 재수화 경로이며 id가 필요합니다. 그래서 submit이 반환되는 순간 id를 기록하는 것이 이 복구를 가능하게 하는 핵심입니다. `POST /api/v2/jobs`의 전체 키 계약은 [Comfy API v2](/ko/api-reference/v2/overview)를 참고하세요.

<Note>
  Comfy Router의 `Idempotency-Key`는 **다르게** 동작합니다. Router에서 이 키는 일회용 토큰이 아니라 재생 핸들입니다. 같은 키로 대기 중인 제출을 다시 보내면 두 번째 요청을 실행 대기열에 넣는 대신 원래 요청을 반환합니다. [대기 중 멱등성 및 과금](/ko/development/comfy-router/queue#멱등성-및-과금)과 [Router 재시도 결과](/ko/development/comfy-router/errors#재시도-결과)를 참고하세요. 이 두 표면은 계약이 아니라 헤더 이름만 공유합니다.
</Note>

## 작업 실행 보기

`job.events()`는 작업 상태의 라이브 스트림을 제공합니다. 노드와 단계 진행 상황, 미리보기 프레임, 그리고 각 출력이 커밋되는 순간을 보여줍니다. 연결이 끊어지면 자동으로 다시 연결됩니다.

<CodeGroup>
  ```python Python theme={null}
  from comfy_sdk import Progress, Preview, OutputReady, StatusChange

  job = client.submit(wf)

  for event in job.events():
      match event:
          case Progress() as p:
              print(f"{p.value:.0%} {p.message}")
          case Preview() as pv:
              image = pv.to_pil()
          case OutputReady() as o:
              o.output.to_file(f"partial/{o.output.name}")
          case StatusChange(status="succeeded"):
              break

  result = job.result()
  ```

  ```typescript TypeScript theme={null}
  const job = await client.submit(wf);

  // 라벨이 필요합니다: switch 안의 일반 `break`는 루프가 아니라
  // switch만 빠져나갑니다.
  eventLoop: for await (const event of job.events()) {
    switch (event.kind) {
      case "progress":
        console.log(event.value);
        break;
      case "outputReady":
        await event.output.toFile(`${event.output.name}`);
        break;
      case "statusChange":
        if (event.status === "succeeded") break eventLoop;
    }
  }
  ```
</CodeGroup>

`Preview.to_pil()`에는 선택적 Pillow extra가 필요합니다: `pip install "comfy-sdk[pil]"`.

`result()`는 완료된 작업을 반환하거나, 실행이 실패한 경우 노드 수준의 세부 정보와 함께 `JobFailed`를 발생시킵니다. 전체 이벤트 카탈로그는 해당 언어의 [SDK README](#참조)를 참조하세요.

### `events()`는 `subscribe()`가 아닙니다

두 인터페이스에 걸쳐 이름이 비슷한 세 가지가 존재하며, 이 페이지에 나오는 것은 그중 하나뿐입니다.

| 호출 | 인터페이스 | 제공하는 항목 |
| - | - | - |
| `job.events()` | Comfy Cloud(이 페이지) | 워크플로 작업 하나의 라이브 SSE 스트림: `Progress`, `Preview`, `OutputReady`, `StatusChange`. 자동으로 다시 연결하고, 폴링으로 대체합니다. |
| `handle.events()`(TypeScript) / `handle.iter_events()`(Python) | [큐 전송](/ko/development/comfy-router/queue) | 하나의 Router 요청에 대한 실행 대기열 관찰: 상태와 대기열 위치. 상태 라우트를 폴링하며, 스트림이 없고 진행 상황이나 미리보기 이벤트도 없습니다. |
| `comfy.models.subscribe(...)` | [큐 전송](/ko/development/comfy-router/queue) | 반복자가 아닙니다. 한 번의 호출로 제출하고 폴링하고 수집하며, `on_queue_update` / `onQueueUpdate` 콜백과 함께 결과를 반환합니다. |

Comfy Cloud 클라이언트에는 `subscribe()`가 없으며, 어디에도 `job.subscribe()`는 없습니다. `subscribe`에 해당하는 클라우드 버전은 `run()`입니다: 제출하고 터미널 상태를 기다립니다.

스트림은 재생 가능한 로그가 아니라 실시간 피드입니다. 결과를 위해 의존할 수 있도록 존재하는 것이 아니라, 진행 상황을 표시하기 위해 존재합니다. 작업을 폴링하는 것이 가장 신뢰할 수 있는 정보이며, `run()`, `wait()`, `result()`는 자동으로 폴링을 사용합니다. 그 이유는 [설계 노트](/ko/development/api-development/sdks-design#폴링-우선-진행-상황은-스트리밍으로)를 참조하세요.

## 출력을 해당 워크플로까지 역추적하기

출력에는 해당 출력을 생성한 작업의 id가 담겨 있으므로, 별도의 보조 테이블을 유지하지 않고도 파일에서 시작해 작업까지 역추적할 수 있습니다.

<CodeGroup>
  ```python Python theme={null}
  output = job.outputs[0]
  output.job_id          # 이 파일을 생성한 작업
  ```

  ```typescript TypeScript theme={null}
  const output = job.outputs[0];
  output.jobId; // 이 파일을 생성한 작업
  ```
</CodeGroup>

동일한 id는 단독으로 가져온 에셋에도 존재하므로, 나중에 찾은 파일도 여전히 해당 작업까지 거슬러 올라갈 수 있습니다. 직접 업로드한 에셋의 경우 id는 `None`(TypeScript: `undefined`)이며, 이는 해당 에셋을 생성한 작업이 없기 때문입니다.

작업에서는 해당 작업의 기반이 된 워크플로를 요청할 수 있습니다. 이 기능은 이 프로세스에서 제출한 것이 아니라 id로 복원한 작업에서도 작동합니다:

<CodeGroup>
  ```python Python theme={null}
  wf = job.get_workflow()

  if wf.format == "save":
      ...  # 작성된 그대로의 워크플로, 캔버스 레이아웃과 Note 노드 유지
  else:
      ...  # 실행된 API 형식 그래프
  ```

  ```typescript TypeScript theme={null}
  const wf = await job.getWorkflow();

  if (wf.format === "save") {
    // 작성된 그대로의 워크플로, 캔버스 레이아웃과 Note 노드 유지
  } else {
    // 실행된 API 형식 그래프
  }
  ```
</CodeGroup>

**항상 `format`으로 분기하세요.** 반환되는 형태는 작업이 제출된 방식에 따라 달라지며, 요청별로 제어할 수 있는 항목에 따라 달라지지 않습니다:

| `format` | 반환되는 내용 | 적용 시기 |
| - | - | - |
| `save` | 작업이 실행된 버전의 작성용 워크플로. 캔버스 레이아웃과 Note와 같은 편집기 전용 노드가 그대로 포함됩니다 | 워크플로 버전을 고정하는 Comfy Cloud 편집기에서 제출된 작업 |
| `api` | 실행된 그래프. 편집기 전용 구성 요소는 제거되고 Get/Set 노드가 확장됩니다 | 그 외 모든 경우. 현재 이 SDK를 통해 제출되는 모든 작업을 포함합니다 |

SDK를 통해 제출하는 작업은 항상 `api`를 반환합니다. v2 제출에는 아직 버전 고정 필드가 없기 때문입니다. 이는 변경될 예정입니다. 판별자가 있는 이유는 코드가 변경되지 않아도 되도록 하기 위해서입니다.

## 작업에 레이블을 붙이고 나중에 찾기

레이블은 작업을 제출할 때 붙이는 키와 값의 쌍입니다. 예를 들어 `client: acme` 또는 `project: spring`입니다. 레이블로 작업이 어느 클라이언트나 프로젝트를 위해 실행되었는지 기록할 수 있습니다. 그러면 작업 ID 표를 직접 관리하지 않아도 한 클라이언트의 작업을 나열할 수 있습니다. 레이블과 작업 목록에는 현재 SDK 릴리스, 즉 [설치](#설치)의 버전 안내에 적힌 릴리스가 필요합니다.

레이블은 [Comfy API 배포](#comfy-api-배포)로 보낸 작업에서 동작합니다. 따라서 [기본 URL 선택](#기본-url-선택)에 나온 대로 `COMFY_BASE_URL`을 배포 주소로 설정하세요. Comfy Cloud는 현재 레이블을 거부하며, 작업 목록에는 `501`과 코드 `not_implemented`로 응답합니다. 자체 호스팅한 [comfy-api-proxy](#자체-comfyui)도 레이블을 보관하지 않습니다. proxy 자체의 `metadata` 필드는 하나의 텍스트 문자열이므로 레이블 맵을 거부합니다. proxy의 작업 목록은 레이블 필터를 무시하므로, 그곳에서 레이블을 지정한 SDK 목록 호출은 작업을 하나도 반환하지 않고, HTTP로 같은 필터를 보내면 모든 작업이 반환됩니다.

제한:

* 작업 하나에 레이블은 최대 16개입니다.
* 키는 1\~40자이며 영문자, 숫자, `_`, `-`, `.`만 쓸 수 있습니다.
* 값은 최대 256바이트(UTF-8)의 문자열입니다.
* 레이블은 작업을 제출할 때 정해지며 나중에 바꿀 수 없습니다.

제한을 어긴 제출은 `422`와 코드 `metadata_invalid`로 실패하며, 작업은 생성되지 않습니다. 오류 메시지는 규칙을 어긴 키를 알려 주고, 레이블이 16개를 넘으면 그 개수를 알려 줍니다. SDK는 제한을 직접 검사하지 않습니다. 서버의 코드와 메시지를 담은 오류를 발생시킵니다.

<Warning>
  워크스페이스에서 작업을 볼 수 있는 사람은 누구나 그 레이블도 볼 수 있습니다. 레이블에 비밀 정보나 개인 데이터를 넣지 마세요.
</Warning>

제출할 때 `metadata`를 전달합니다. 작업을 다시 읽으면 레이블이 함께 들어 있습니다.

<CodeGroup>
  ```python Python theme={null}
  job = client.submit(wf, metadata={"client": "acme", "project": "spring"})
  job.metadata  # {"client": "acme", "project": "spring"}
  ```

  ```typescript TypeScript theme={null}
  const job = await client.submit(wf, {
    metadata: { client: "acme", project: "spring" },
  });
  job.metadata; // { client: "acme", project: "spring" }
  ```
</CodeGroup>

레이블 없이 제출한 작업의 `metadata`는 비어 있습니다.

한 클라이언트의 작업을 찾으려면 레이블로 필터링해 작업을 나열합니다. 목록에는 기본 URL의 배포로 보낸 작업만 포함됩니다. 결과는 최신순으로 오며, 반복하는 동안 SDK가 다음 페이지를 가져옵니다.

<CodeGroup>
  ```python Python theme={null}
  for job in client.list_jobs(metadata={"client": "acme"}):
      print(job.id, job.status)
  ```

  ```typescript TypeScript theme={null}
  for await (const job of client.listJobs({ metadata: { client: "acme" } })) {
    console.log(job.id, job.status);
  }
  ```
</CodeGroup>

한 번에 최대 3개의 레이블로 필터링할 수 있으며, 작업은 모든 레이블과 정확히 일치해야 합니다. 목록 항목에는 출력이 포함되지 않습니다. 작업의 출력을 가져오려면 `client.jobs.get(job.id)`로 ID를 사용해 작업을 읽으세요.

### HTTP로 사용하기

`POST /api/v2/jobs`의 본문에 `metadata`를 보냅니다.

```bash theme={null}
curl -X POST "https://<deployment>.run.comfy.app/api/v2/jobs" \
  -H "Authorization: Bearer $COMFY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "workflow": { ... },
    "metadata": { "client": "acme", "project": "spring" }
  }'
```

`GET /api/v2/jobs`는 레이블마다 `metadata[<key>]=<value>` 쿼리 매개변수 하나로 필터링합니다. `-g` 플래그를 쓰면 curl이 대괄호를 패턴으로 해석하지 않습니다.

```bash theme={null}
curl -g "https://<deployment>.run.comfy.app/api/v2/jobs?metadata[client]=acme" \
  -H "Authorization: Bearer $COMFY_API_KEY"
```

응답에는 작업 한 페이지와, 다음 페이지가 있으면 `next_cursor`가 들어 있습니다. 다음 페이지를 받으려면 그 값을 `cursor`로, 같은 `metadata[...]` 필터와 함께 다시 보냅니다. 필터 없이 보낸 커서는 필터링되지 않은 작업을 반환합니다. 페이지 크기는 `limit`로 정합니다(최대 500).

```json theme={null}
{
  "jobs": [
    {
      "id": "6f1c2b9e-...",
      "status": "succeeded",
      "metadata": { "client": "acme", "project": "spring" }
    }
  ],
  "next_cursor": "eyJ..."
}
```

레이블 필터가 3개를 넘거나 키 규칙을 어긴 키를 쓰면 `400`과 코드 `invalid_metadata_filter`로 실패합니다. 작업과 목록의 모든 필드는 [Comfy API v2 참조](/ko/api-reference/v2/overview)를 확인하세요.

## 현재 SDK가 다루는 범위

첫 번째 버전은 한 가지 작업을 제대로 수행합니다. 워크플로를 실행하고 결과를 돌려받는 것입니다.

* **에셋.** 파일, 바이트, 스트림 또는 URL에서 입력 핸들을 생성합니다. 핸들은 지연(lazy) 방식이며 콘텐츠 주소 기반이므로 동일한 입력으로 다시 실행해도 다시 업로드되지 않습니다.
* **제출.** API 형식의 그래프를 제출합니다. 제출은 멱등적이며, 실행 대기열이 가득 찬 경우 제한된 예산 내에서 자동으로 재시도됩니다.
* **실행.** `wait()`으로 폴링하거나 `events()`를 통해 실시간 진행 상황을 추적합니다.
* **출력.** 디스크에 쓰거나, 메모리에 버퍼링하거나, 바이트 범위를 가져오거나, 단기 다운로드 URL을 받을 수 있습니다. `getDownloadUrl()`은 API 키 없이 누구나 읽을 수 있는 서명된 URL을 반환하며, 약 6시간 동안 유효합니다. URL을 저장하기 전에 [출력 URL과 유효 기간](/ko/api-reference/v2/overview#출력-url과-유지-기간)을 먼저 읽어보세요. 나중에도 출력을 계속 보여주려면 바이트를 다시 호스팅하거나 필요할 때 URL을 다시 발급받아야 합니다.
* **추적 가능성.** 모든 출력에는 해당 출력을 생성한 작업의 ID가 포함되며, 작업은 그 뒤에 있는 워크플로를 반환할 수 있습니다.
* **에셋 삭제.** 업로드한 에셋을 핸들이나 ID로 제거할 수 있습니다.
* **오류.** 원시 상태 코드 대신 `JobFailed`, `Unauthorized`, `InsufficientCredits`, `QueueFull`과 같은 타입화된 예외를 제공합니다.
* **취소.** 실행 중인 작업을 취소할 수 있습니다. TypeScript는 추가로 모든 호출에서 `AbortSignal`을 허용합니다.

Python은 동일한 API 표면(surface)을 가진 동기식 `Comfy` 클라이언트와 `AsyncComfy` 클라이언트를 모두 제공합니다. TypeScript는 비동기 전용입니다.

이 버전에는 포함되지 않는 것: 저장된 워크플로 관리, 모델 라이브러리, 노드 인트로스펙션, 명명된 워크플로 파라미터. [설계 노트](/ko/development/api-development/sdks-design#첫-번째-버전의-범위)에서 API 표면이 이렇게 작게 시작하는 이유를 설명합니다.

## 참조

SDK README는 인증, 에셋, 오류, 그리고 저수준 이스케이프 해치를 포함하여 각 언어에 대한 전체 참조 자료입니다.

<CardGroup cols={2}>
  <Card title="Python SDK" icon="python" href="https://github.com/Comfy-Org/comfy-python-sdk">
    PyPI의 <code>comfy-sdk</code>. 동기 및 비동기 클라이언트를 제공합니다.
  </Card>

  <Card title="TypeScript SDK" icon="js" href="https://github.com/Comfy-Org/comfy-typescript-sdk">
    npm의 <code>@comfyorg/sdk</code>. 타입이 지정된 비동기 클라이언트와 저수준 클라이언트를 제공합니다.
  </Card>

  <Card title="Comfy API v2 참조" icon="code" href="/ko/api-reference/v2/overview">
    두 SDK의 기반이 되는 HTTP API입니다. 어떤 언어에서든 직접 사용할 수 있습니다.
  </Card>

  <Card title="설계 노트" icon="compass" href="/ko/development/api-development/sdks-design">
    이 API가 존재하는 이유, 기존 ComfyUI API와의 관계, 그리고 앞으로의 계획을 설명합니다.
  </Card>

  <Card title="Comfy Router" icon="shuffle" href="/ko/development/comfy-router/quickstart">
    같은 패키지의 또 다른 표면입니다. Flux, Veo, Gemini 같은 파트너 모델에 대해 <code>comfy.models.run</code>을 실행합니다.
  </Card>
</CardGroup>

## 피드백

SDK와 API에 관한 피드백을 환영합니다. 사용하기 어려운 점, 찾을 수 없었던 정보, 필요했던 우회 방법을 알려주세요.

어색한 부분, 기대했지만 찾지 못한 부분, 그리고 우회해서 해결해야 했던 부분을 알려주세요. [저희 Discord](https://discord.com/invite/comfyorg)의 `#developer-platform` 채널이 그런 피드백을 보내실 곳입니다.

다른 언어로 된 퍼스트파티 SDK를 원하신다면 그곳에서 말씀해 주세요. 두 SDK 모두 동일한 문서화된 HTTP 계약을 기반으로 하므로 어떤 언어든 오늘날 API와 통신할 수 있지만, 저희는 수요가 어디에 있는지 알고 싶습니다.


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