---
title: 실행 기다리기
description: subscribeFormatRun은 Format 실행을 만들고 영수증을 기다립니다. waitForRun은 Format·Action·Agent 실행을 폴링합니다. 전달이 켜져 있다면 웹훅을 권장합니다 — 아직 SSE 스트림은 없습니다.
---

모든 Sume 실행은 비동기입니다. `POST .../runs`는 `status_url`이 담긴 영수증을
돌려주고, 결과는 나중에 알게 됩니다. bulk 큐는 그 run들의 서버 측 목록입니다.
counts는 `GET /v1/format-run-queues/{queue_id}`로 폴링하고, 이 페이지의 헬퍼는
여전히 run id 하나를 기다립니다. [대량 실행](/formats/bulk-runs)을 보세요.
`@sume-com/sdk@0.2.0`에서 파트너용 Format
경로는 **`subscribeFormatRun`**(생성 + 대기)입니다. 이미 run id가 있다면
**`waitForRun`**을 사용하세요. 기다림을 아예 건너뛸 수 있다면
[run 웹훅](/agents/run-webhooks)을 권장합니다.

오늘은 **SSE 이벤트 스트림이 없습니다**. 모든 실행에서 `events_url`은
`null`이므로 "subscribe"는 생성 후 폴링을 뜻합니다. `onStatus`는 라이브 로그
피드가 아니라 상태 폴링을 반영합니다(`next_action`, 타임스탬프, `cancelable`이
담긴 더 풍부한 스냅샷을 포함합니다).

## Format 실행: `subscribeFormatRun`

```ts
import { createSumeClient, subscribeFormatRun } from "@sume-com/sdk";

const client = createSumeClient({ apiKey: process.env.SUME_API_KEY! });

const run = await subscribeFormatRun({
  client,
  path: { handle: "acme", slug: "product-promo" },
  idempotencyKey: "order-8823-promo-v1",
  body: {
    input: { product_url: "https://shop.example.com/p/8823" },
    generation_spend_cap_usd: 3,
    // 또는 기다리지 않고 푸시를 받으세요:
    // communication: { webhook_url: "https://partner.example/hooks/sume" },
  },
  onStatus: (status, snapshot) => console.log(status, snapshot.next_action),
});

if (run.status === "completed") {
  console.log(run.primary_output_url);
} else {
  console.error(run.status, run.error);
}
```

| 옵션 | 기본값 | 설명 |
|---|---|---|
| `path` | — | 버니티 `{ handle, slug }` 또는 `{ format_id }`입니다. |
| `body` | — | `createFormatRun*`과 같은 본문입니다(input, 상한, 스키마, 첨부 등). |
| `idempotencyKey` | — | `Idempotency-Key`로 전송됩니다. 완료된 실행을 다시 보내면 즉시 반환됩니다. |
| `timeout` | **20분** | `waitForRun`의 10분보다 깁니다. 비디오 Format은 보통 10~20분 걸립니다. |
| `pollInterval` | 2초 | 상태 조회 사이의 간격입니다. |
| `signal` | — | 대기와 진행 중인 요청을 중단합니다. |
| `onStatus` | — | 상태를 읽을 때마다 `(status, snapshot)`을 호출합니다. 종료 상태도 포함합니다. |
| `onCreated` | — | 폴링이 시작되기 전에 접수된 실행과 함께 한 번 호출됩니다. |

**어떤** 종료 상태에서도 resolve합니다. 생성 호출 자체가 거부될 때만
**throw**합니다(예: 개인 키로 팀 Format을 호출했을 때의
`403 workspace_key_required`). 기다릴 실행 자체가 없기 때문입니다.

영수증의 필드별 설명은 [실행과 결과](/formats/runs)에 있습니다.

## 이미 run id가 있을 때: `waitForRun`

Action / Agent Completion 실행이거나, Format 실행을 직접 만들고 폴링 루프만
필요할 때 사용하세요.

```ts
import { createSumeClient, waitForRun } from "@sume-com/sdk";

const client = createSumeClient({ apiKey: process.env.SUME_API_KEY! });

const run = await waitForRun(runId, {
  client,
  family: "format",
  timeout: 15 * 60_000,
  pollInterval: 2_000,
  signal: AbortSignal.timeout(20 * 60_000),
  onStatus: (status, snapshot) => console.log(status, snapshot.next_action),
});
```

| 옵션 | 기본값 | 설명 |
|---|---|---|
| `family` | — | **필수**입니다. `"format"`, `"action"`, `"agent"` 중 하나입니다. |
| `client` | 모듈 기본값 | `createSumeClient()`로 만든 클라이언트입니다. 반드시 넘기세요. 모듈 기본값에는 베이스 URL도 키도 없습니다. |
| `timeout` | 10분 | 초과하면 `SumeRunTimeoutError`를 throw합니다. |
| `pollInterval` | 2초 | 상태 조회 사이의 간격입니다. |
| `signal` | — | 대기와 진행 중인 요청을 중단하고 시그널의 사유로 reject합니다. |
| `onStatus` | — | 상태를 읽을 때마다 `(status, snapshot)`을 호출합니다. 종료 상태도 포함합니다. |

**`family`는 필수이며 추론할 수 없습니다.** run id만으로는 어느 표면에 속하는지
알 수 없고, 세 계열은 서로 다른 세 URL 프리픽스 아래에 있습니다
(`/v1/format-runs/…`, `/v1/action-runs/…`, `/v1/agent-runs/…`). 반환 값의
타입을 정하는 것도 이 값입니다. `family: "format"`은 `PublicFormatRun`으로
resolve합니다.

마감 시간은 대기 **전에** 확인합니다. 대기 후가 아닙니다. 5초 타임아웃을 요청한
호출자는 5초에 폴링 간격 하나를 더한 시간이 아니라 5초 만에 결과를 듣습니다.

## 종료와 성공은 다릅니다

두 헬퍼 모두 `completed`, `failed`, `canceled`, `skipped` 등 **어떤** 종료
상태에서도 resolve합니다. 실패한 실행은 예외가 아니라 요청한 결과이므로, 웹훅
핸들러가 하듯이 영수증에서 `status`와 `error`를 읽으세요.

```ts
const run = await subscribeFormatRun({
  client,
  path: { handle: "acme", slug: "product-promo" },
  body: { input: { product_url: "https://example.com/p" } },
});

switch (run.status) {
  case "completed":
    return attachOutputs(run);
  case "failed":
    // `unattended_blocked`는 사람 없이는 통과할 수 없는 게이트에
    // 걸렸다는 뜻입니다. 메시지는 사용자에게 보여주도록 작성돼 있습니다.
    return showError(run.error);
  case "canceled":
  case "skipped":
    return noop();
}
```

`skipped`는 따로 분기할 만합니다. 이미 실행이 진행 중이었고
`on_active_run: "skip"`을 보냈다는 뜻입니다(Format run은 기본적으로 동시
실행을 허용합니다). 전체 라이프사이클은
[실행과 결과](/formats/runs)에서 살펴보세요.

## 헬퍼가 throw하는 오류

`{ data, error }`로 resolve하는 생성된 오퍼레이션과 달리, 이 헬퍼들은
throw합니다. 폴링 루프에는 결과가 아닌 값을 담을 곳이 없기 때문입니다.

| 오류 | 언제 |
|---|---|
| `SumeRunTimeoutError` | `timeout`이 먼저 지났을 때입니다. `runId`와 `lastStatus`를 담고 있습니다. |
| `SumeRunRequestError` | 생성이 거부됐거나 상태·영수증 조회가 오류로 돌아왔을 때입니다(`401`, `404`, `5xx`). `runId`(또는 `"(not created)"`), `status`, `body`를 담고 있습니다. |
| `signal`의 사유 | 직접 중단했을 때입니다. |

```ts
import { SumeRunTimeoutError, waitForRun } from "@sume-com/sdk";

try {
  const run = await waitForRun(runId, { client, family: "format" });
} catch (error) {
  if (error instanceof SumeRunTimeoutError) {
    // 실행은 계속 진행 중입니다. 잃은 것은 없으니 나중에 `result_url`에서 읽으세요.
    await markPending(error.runId, error.lastStatus);
  } else {
    throw error;
  }
}
```

타임아웃은 실행을 취소하지 **않습니다**. 실행은 계속되고, 지켜보기를 멈춘
것뿐입니다. run id를 저장해 두었다가 `getFormatRun`으로 다시 가져오거나
`cancelFormatRun`으로 명시적으로 취소하세요.

## 가능하면 웹훅을 사용하세요

폴링은 진행 중인 실행마다 타이머 하나와 열린 요청 하나를 쓰는데, 이 실행들은
보통 몇 분씩 걸립니다. [Run 웹훅](/agents/run-webhooks)은 같은 영수증을 그런
비용 없이 전달하고, 수신 측 절반은 [`verifyWebhook`](/sdk/webhooks)입니다.

폴링이 알맞은 경우는 블로킹해도 되는 Job 안에 있을 때, 프로토타이핑할 때, 또는
환경에서 웹훅 전달이 아직 꺼져 있을 때입니다. 수신기를 지금 만들어 두고
`subscribeFormatRun` / `waitForRun`은 대비책으로 남겨 두세요.

## 다음

- [웹훅 검증](/sdk/webhooks) — 푸시 경로의 수신 측 확인입니다
- [실행과 결과](/formats/runs) — 영수증을 필드별로 살펴봅니다
- [대량 실행](/formats/bulk-runs) — run을 큐에 넣고 `GET /v1/format-run-queues/{id}`를 폴링
- [제품에 Format 임베드하기](/cookbooks/embed-a-format) — 파트너 연동 전체입니다
