> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aihubmix.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 비동기 작업

> AIHubMix 비동기 작업 API: 이미지 요청에 async 전달, 비디오 작업은 기본 비동기, LLM 중단 후 최종 응답 자동 저장. 세 가지 작업이 동일한 task 객체로 통합되며 /ai/v1/tasks 로 상태 조회, 결과 다운로드, Webhook 콜백 수신이 가능합니다.

비디오 생성이나 배치 이미지 생성 같은 요청은 일반적으로 한 번의 HTTP 연결에서 기다릴 수 있는 합리적인 시간을 넘깁니다. 또한 장문 생성 도중 클라이언트 연결이 끊기면 이미 생성된 응답도 다시 가져올 수 없습니다.

**비동기 작업**(Async Tasks)은 이 세 가지 시나리오를 동일한 작업 객체로 통합합니다. 이미지와 비디오는 생성 API로 작업을 만들고 즉시 `task_id`를 반환받으며, 클라이언트가 중단한 LLM 요청은 플랫폼이 계속 처리하여 최종 응답을 저장합니다. 세 가지 모두 동일한 작업 상태, 조회 API, 결과 다운로드 절차를 공유합니다.

<Note>
  작업을 생성할 때 사용한 것과 동일한 API Key로 작업을 조회하고 결과를 다운로드하세요. 작업은 API Key 단위로 격리되며, 두 Key가 같은 계정에 속하더라도 서로의 작업을 읽을 수 없습니다.
</Note>

<Card title="콘솔에서 비동기 작업 활성화하기" icon="list-check" href="https://console.aihubmix.com/support" horizontal>
  비동기 이미지 또는 비디오를 생성하기 전에 현재 계정에서 비동기 작업 기능을 먼저 활성화하세요. 콘솔에 해당 항목이 아직 표시되지 않으면 AIHubMix 기술 지원에 문의하세요.
</Card>

<Warning>
  비동기 작업 기능이 활성화되지 않은 상태에서는 미디어 작업 생성 요청이 `403 async_not_enabled`를 반환합니다. LLM 요청은 이로 인해 오류가 발생하지는 않지만, 클라이언트가 중단된 후 최종 응답을 되찾을 수 없습니다.
</Warning>

***

<h2 id="quickstart">
  빠른 시작
</h2>

이미지와 비디오 비동기 작업의 전체 프로세스는 세 단계로 나뉩니다.

```text theme={null}
1. 작업 제출 -> task_id 획득
2. 상태 조회 -> 작업 완료 대기
3. 결과 획득 -> 파일 다운로드 또는 응답 내용 읽기
```

<CodeGroup>
  ```shell curl theme={null}
  # 1단계: 비동기 비디오 작업 제출
  curl -X POST https://aihubmix.com/ai/v1/videos \
    -H "Authorization: Bearer $AIHUBMIX_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "model": "wan2.6-t2v",
      "prompt": "A cat playing jazz on a piano, warm lighting, cinematic shot",
      "seconds": "5",
      "size": "1280x720"
    }'

  # 2단계: 작업이 완료, 실패 또는 취소될 때까지 15초마다 조회
  curl https://aihubmix.com/ai/v1/tasks/{task_id} \
    -H "Authorization: Bearer $AIHUBMIX_API_KEY"

  # 3단계: 단일 결과물 다운로드
  curl https://aihubmix.com/ai/v1/tasks/{task_id}/content \
    -H "Authorization: Bearer $AIHUBMIX_API_KEY" \
    --output result.mp4
  ```

  ```json 생성 응답 theme={null}
  {
    "id": "task_01K0...",
    "object": "video",
    "model": "wan2.6-t2v",
    "status": "in_progress",
    "output": [],
    "error": null,
    "created_at": 1784707200,
    "completed_at": null,
    "expires_at": null
  }
  ```
</CodeGroup>

***

<h2 id="sync-vs-async">
  동기 호출 vs 비동기 작업 비교
</h2>

| 요청 유형      | 기본 반환 방식      | 비동기 방식                                     |
| ---------- | ------------- | ------------------------------------------ |
| 이미지 생성     | 생성 결과를 동기 반환  | 요청 본문에 `async: true`를 전달하면 즉시 `task_id` 반환 |
| 비디오 생성     | 항상 비동기        | 생성 후 `task_id` 반환, 작업 API로 결과 획득           |
| LLM 텍스트 생성 | 동기 또는 스트리밍 반환 | 클라이언트가 중단하고 조건을 충족하면 최종 응답이 `llm` 작업으로 저장  |

동기 호출은 한 번의 HTTP 응답 안에서 결과를 반환하며, 연결이 끊기면 결과를 다시 찾을 수 없습니다. 비동기 작업은 결과를 플랫폼 측에 저장하므로, 결과가 만료되기 전까지 동일한 API Key로 `task_id`를 다시 조회하고 다운로드할 수 있습니다. 시간이 오래 걸리는 생성 요청과, 중단 후 최종 응답을 회수해야 하는 장문 출력에 적합합니다.

***

<h2 id="api-overview">
  API 개요
</h2>

| 작업         | 메서드  | 경로                                           | 설명                      |
| ---------- | ---- | -------------------------------------------- | ----------------------- |
| 비동기 이미지 생성 | POST | `/ai/v1/images/generations`                  | 요청 본문에 `async: true` 추가 |
| 비동기 비디오 생성 | POST | `/ai/v1/videos`                              | 비디오 작업은 기본적으로 비동기       |
| 작업 목록 조회   | GET  | `/ai/v1/tasks`                               | 현재 API Key가 생성한 작업 검색   |
| 작업 상세 조회   | GET  | `/ai/v1/tasks/{task_id}`                     | 통합 작업 상태와 출력 조회         |
| 단일 결과 획득   | GET  | `/ai/v1/tasks/{task_id}/content`             | 단일 결과물 작업 또는 LLM 작업에 사용 |
| 지정 결과 획득   | GET  | `/ai/v1/tasks/{task_id}/content/{result_id}` | 다중 결과물 작업에 사용           |

Base URL: `https://aihubmix.com`, 인증 방식은 Bearer Token 입니다.

```bash theme={null}
Authorization: Bearer $AIHUBMIX_API_KEY
```

<Note>
  `/ai/v1/tasks`는 읽기 전용 통합 조회 엔드포인트이며, `POST /ai/v1/tasks`는 제공하지 않습니다. 이미지와 비디오는 각각 대응하는 생성 API로 만듭니다. [LLM 중단 복구](#llm-interruption-recovery) 조건을 충족하는 요청은 클라이언트 중단 후 자동으로 `llm` 작업으로 기록됩니다.
</Note>

***

<h2 id="supported-models">
  지원 모델
</h2>

비동기 작업은 작업 유형별로 지원 범위가 나뉘며, 호출 시 추가 파라미터가 필요 없습니다.

<h3 id="supported-models-image">
  비동기 이미지
</h3>

| 모델               |
| ---------------- |
| `qwen-image-2.0` |

<h3 id="supported-models-video">
  비동기 비디오
</h3>

| 모델           |
| ------------ |
| `wan2.6-t2v` |

<h3 id="supported-models-llm">
  LLM 중단 복구
</h3>

| 모델               |
| ---------------- |
| `gpt-5.5-pro`    |
| `claude-fable-5` |

지원 범위는 계속 확대되며, 이 표도 함께 업데이트됩니다.

***

<h2 id="create-async-task">
  비동기 작업을 생성하는 방법
</h2>

<h3 id="create-async-image">
  비동기 이미지
</h3>

이미지 API는 기본적으로 동기 반환입니다. `async`를 `true`로 설정하면 API가 즉시 작업 객체를 반환하고, 생성 과정은 백그라운드에서 계속 실행됩니다.

```bash theme={null}
curl -X POST https://aihubmix.com/ai/v1/images/generations \
  -H "Authorization: Bearer $AIHUBMIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen-image-2.0",
    "prompt": "A flower shop with delicate windows, warm sunlight streaming in",
    "n": 2,
    "size": "1024x1024",
    "async": true
  }'
```

`async`는 불리언 값이어야 합니다. 전달하지 않거나 `false`로 설정하면 이미지 API는 동기 동작을 유지합니다.

<h3 id="create-async-video">
  비동기 비디오
</h3>

비디오 API는 항상 비동기입니다. 생성에 성공하면 `pending` 또는 `in_progress` 상태를 반환하며, `Prefer: wait`로 동기 대기로 변경하는 방식은 지원하지 않습니다.

```bash theme={null}
curl -X POST https://aihubmix.com/ai/v1/videos \
  -H "Authorization: Bearer $AIHUBMIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "wan2.6-t2v",
    "prompt": "Ocean waves crashing on rocky cliffs at sunset",
    "seconds": "5",
    "size": "1280x720"
  }'
```

<h3 id="common-parameters">
  공통 파라미터
</h3>

예시의 `model`, `prompt`, `n`, `seconds`, `size`는 일반적인 모델 파라미터이며, 각 모델이 지원하는 필드와 값은 해당 모델의 API 문서를 기준으로 합니다. 비디오 모델은 [비디오 생성 문서](/ko/api/Video-Gen)를 참조하세요. 아래 표는 모든 비동기 작업이 공통으로 사용하는 파라미터만 설명합니다.

| 파라미터                    | 타입        | 필수                   | 설명                                                 |
| ----------------------- | --------- | -------------------- | -------------------------------------------------- |
| `async`                 | boolean   | 이미지: 필수, 비디오: 전달 불필요 | 이미지 API에서 `true`로 설정하면 비동기 실행                      |
| `webhook_url`           | string    | 아니오                  | 현재 작업의 HTTPS 콜백 주소, 최대 512자                        |
| `webhook_events_filter` | string\[] | 아니오                  | 푸시할 최종 상태, `completed`, `failed`, `cancelled` 중 선택 |

<Note>
  이미지 작업은 `async: true`일 때만 Webhook을 사용할 수 있습니다. `webhook_events_filter`를 생략하면 플랫폼이 `completed`, `failed`, `cancelled` 세 가지 최종 상태를 모두 푸시합니다. 전달할 때는 반드시 `webhook_url`과 함께 사용해야 하며, 비어 있거나 중복되어서는 안 됩니다.
</Note>

***

<h2 id="llm-interruption-recovery">
  LLM 중단 복구가 동작하는 방법
</h2>

LLM 중단 복구는 클라이언트 연결이 끊긴 후의 최종 응답을 회수하는 데 사용합니다. 이 기능은 기존 LLM 요청 방식을 그대로 활용하며, 스트리밍 동작과 응답 형식은 변하지 않습니다. 별도의 생성 API를 호출할 필요가 없고, `task_id`도 미리 반환되지 않습니다.

<h3 id="recovery-conditions">
  적용 조건
</h3>

다음 조건을 모두 충족해야 합니다.

| 조건                 | 설명                                                         |
| ------------------ | ---------------------------------------------------------- |
| 계정에서 비동기 작업 기능 활성화 | AIHubMix 콘솔에서 현재 계정에 대해 활성화                                |
| 사용 모델이 중단 복구를 지원   | [LLM 중단 복구](#supported-models-llm) 참조, 호출 시 추가 파라미터가 필요 없음 |
| 지원되는 LLM API 호출    | 요청이 아래에 나열된 텍스트 생성 API에 해당                                 |
| 클라이언트에서 중단 발생      | 클라이언트가 직접 취소하거나, 네트워크가 끊기거나, 호출 측이 요청을 취소                  |

지원되는 API:

| API                                                | 설명                                      |
| -------------------------------------------------- | --------------------------------------- |
| `POST /v1/chat/completions`                        | OpenAI Chat Completions, 스트리밍과 비스트리밍 지원 |
| `POST /v1/messages`                                | Anthropic Messages, 스트리밍과 비스트리밍 지원      |
| `POST /v1/responses`                               | OpenAI Responses API                    |
| Gemini `generateContent` / `streamGenerateContent` | Gemini 네이티브 텍스트 생성 API                  |

<Note>
  호출 시 추가 필드를 전달할 필요가 없습니다. 지원 범위는 [LLM 중단 복구](#supported-models-llm)를 참조하세요. 표에 없는 모델은 정식 연동 전에 저비용 요청 한 건으로 중단 복구를 검증할 수 있으며, 검증 요청도 정상적으로 과금됩니다. 조건 중 하나라도 충족되지 않으면 요청은 그대로 정상 실행되며, 클라이언트 중단 후 `llm` 작업이 생성되지 않습니다.
</Note>

<h3 id="recovery-flow">
  중단 후 실행 흐름
</h3>

```text theme={null}
1. 클라이언트가 평소대로 LLM 요청을 전송
2. 클라이언트가 응답 완료 전에 연결을 끊거나 취소
3. AIHubMix가 요청을 계속 처리하며, 해당 요청은 정상적으로 과금
4. 최종 JSON 또는 SSE 응답이 llm 유형의 작업으로 저장
5. 원래 API Key로 작업 목록을 조회하여 저장된 응답을 읽음
```

정상적으로 완료되어 클라이언트에 성공적으로 반환된 LLM 요청은 작업을 생성하지 않으며, 작업 목록에도 나타나지 않습니다. 중단된 요청은 최종 응답 저장이 완료된 후 목록에 나타나므로, 처리 중에는 일시적으로 조회되지 않을 수 있습니다.

<h3 id="locate-interrupted-request">
  해당 중단 요청 찾기
</h3>

LLM 응답 헤더는 `X-Aihubmix-Request-Id`를 반환합니다. 클라이언트는 응답 헤더를 받으면 즉시 이 값을 저장해야 하며, 중단이 발생한 후 AIHubMix 콘솔의 비동기 작업 목록에서 이 요청 ID로 해당 작업을 찾을 수 있습니다.

공개 작업 API는 현재 요청 ID 기준 필터링을 지원하지 않습니다. 요청 ID를 저장하지 않은 경우, 요청을 생성할 때 사용한 것과 동일한 API Key로 모델과 생성 시간을 기준으로만 찾을 수 있습니다.

```bash theme={null}
# 최근 LLM 중단 작업 조회
curl "https://aihubmix.com/ai/v1/tasks?object=llm&model={model}&order=desc&limit=20" \
  -H "Authorization: Bearer $AIHUBMIX_API_KEY"

# task_id 를 찾은 후 상세 정보를 조회하고 원본 응답을 획득
curl https://aihubmix.com/ai/v1/tasks/{task_id} \
  -H "Authorization: Bearer $AIHUBMIX_API_KEY"

curl https://aihubmix.com/ai/v1/tasks/{task_id}/content \
  -H "Authorization: Bearer $AIHUBMIX_API_KEY"
```

<Warning>
  동일한 API Key로 같은 모델의 요청을 여러 개 동시에 보내는 경우, 모델과 생성 시간만으로는 정확한 대응을 보장할 수 없습니다. 안정적인 복구가 필요하면 `X-Aihubmix-Request-Id`를 저장하고 콘솔에서 찾으세요. 응답 헤더를 확보하지 못했다면 목록의 최신 작업을 이번 요청으로 단정하는 것은 피해야 합니다.
</Warning>

<Warning>
  LLM 중단 복구 작업은 현재 Webhook을 전송하지 않으므로 작업 목록에서 결과를 조회하세요. 클라이언트 중단은 플랫폼의 요청 처리를 멈추지 않으며, 해당 호출은 기존 LLM API 규칙에 따라 과금됩니다.
</Warning>

***

<h2 id="task-object">
  작업 객체와 상태
</h2>

모든 작업은 통합된 응답 구조를 사용합니다.

```json theme={null}
{
  "id": "task_01K0ABCDEF",
  "object": "video",
  "model": "wan2.6-t2v",
  "status": "completed",
  "output": [
    {
      "index": 0,
      "result_id": "result_01K0XYZ",
      "type": "file",
      "content_type": "video/mp4",
      "content_url": "https://aihubmix.com/ai/v1/tasks/task_01K0ABCDEF/content"
    }
  ],
  "error": null,
  "created_at": 1784707200,
  "completed_at": 1784707320,
  "expires_at": 1784709120
}
```

| 필드             | 타입           | 설명                                |
| -------------- | ------------ | --------------------------------- |
| `id`           | string       | 플랫폼 작업 ID, 이후 요청에서 사용하는 `task_id` |
| `object`       | string       | 작업 유형: `llm`, `image` 또는 `video`  |
| `model`        | string       | 작업 생성 시 사용한 모델                    |
| `status`       | string       | 통합 작업 상태                          |
| `output`       | array        | 획득 가능한 결과, 작업이 결과를 만들지 않았으면 빈 배열  |
| `error`        | object/null  | 실패 정보, 일반적으로 `code`와 `message` 포함 |
| `created_at`   | integer      | 생성 시간, Unix 초                     |
| `completed_at` | integer/null | 작업이 완료, 실패 또는 취소된 시간, Unix 초      |
| `expires_at`   | integer/null | 가장 먼저 만료되는 결과의 만료 시간, Unix 초      |

`output` 안의 결과 필드:

| 필드             | 설명                                                    |
| -------------- | ----------------------------------------------------- |
| `index`        | 현재 작업에서 결과의 순서, 0부터 시작                                |
| `result_id`    | 결과 ID, 다중 결과물 작업에서 지정 결과를 다운로드할 때 사용                  |
| `type`         | 결과 유형, 파일은 `file`, LLM 응답은 `response`                 |
| `content_type` | 결과의 파일 유형(MIME), 예: `video/mp4` 또는 `application/json` |
| `content_url`  | 결과 다운로드 주소, 접근 시 작업 생성에 사용한 API Key 필요                |
| `b64_json`     | 일부 이미지 모델이 직접 반환할 수 있는 Base64 인코딩 결과                  |
| `truncated`    | LLM 응답이 크기 제한으로 잘렸는지 여부                               |

<h3 id="task-status">
  상태 설명
</h3>

| 상태            | 종료 여부 | 설명                         |
| ------------- | ----- | -------------------------- |
| `pending`     | 아니오   | 플랫폼이 작업을 접수했고 실행 시작 대기 중   |
| `in_progress` | 아니오   | 작업 실행 중                    |
| `completed`   | 예     | 작업 완료, `output`에서 결과 획득 가능 |
| `failed`      | 예     | 작업 실패, 실패 원인은 `error` 참조   |
| `cancelled`   | 예     | 작업 취소됨                     |

**15초**마다 한 번씩 조회하여 상태가 `completed`, `failed` 또는 `cancelled`로 바뀔 때까지 확인하는 것을 권장합니다.

<Note>
  `failed` 또는 `cancelled` 작업에도 이미 생성된 일부 결과가 포함될 수 있습니다. 결과 유무를 판단할 때는 상태 외에 `output`이 비어 있는지도 확인해야 합니다.
</Note>

***

<h2 id="query-tasks">
  작업을 조회하는 방법
</h2>

<h3 id="query-task-detail">
  작업 상세 조회
</h3>

```bash theme={null}
curl https://aihubmix.com/ai/v1/tasks/{task_id} \
  -H "Authorization: Bearer $AIHUBMIX_API_KEY"
```

이 API는 조회 시점의 최신 작업 정보를 반환합니다. 조회 동작은 작업을 변경하지 않으며, 작업 상태는 플랫폼이 자동으로 갱신합니다.

<h3 id="query-task-list">
  작업 목록 조회
</h3>

생성 응답을 잃어버렸거나 과거 작업을 한꺼번에 확인해야 할 때는 목록 API로 `task_id`를 다시 찾을 수 있습니다.

```bash theme={null}
curl "https://aihubmix.com/ai/v1/tasks?object=video&status=in_progress&limit=20&order=desc" \
  -H "Authorization: Bearer $AIHUBMIX_API_KEY"
```

| 파라미터     | 타입      | 기본값    | 설명                                 |
| -------- | ------- | ------ | ---------------------------------- |
| `object` | string  | -      | 유형별 필터: `llm`, `image`, `video`    |
| `status` | string  | -      | 통합 작업 상태별 필터                       |
| `model`  | string  | -      | 모델 이름 정확 일치 필터                     |
| `after`  | string  | -      | 페이지네이션 커서, 이전 페이지의 `next_after` 사용 |
| `limit`  | integer | `20`   | 페이지당 개수, 범위 1\~100                 |
| `order`  | string  | `desc` | `asc` 또는 `desc`                    |

응답 예시:

```json theme={null}
{
  "object": "list",
  "data": [
    {
      "id": "task_01K0ABCDEF",
      "object": "video",
      "model": "wan2.6-t2v",
      "status": "in_progress",
      "output": [],
      "error": null,
      "created_at": 1784707200,
      "completed_at": null,
      "expires_at": null
    }
  ],
  "has_more": true,
  "next_after": "task_01K0ABCDEF"
}
```

| 필드           | 설명                          |
| ------------ | --------------------------- |
| `object`     | 고정값 `list`, 목록 응답임을 나타냄     |
| `data`       | 현재 페이지의 작업 배열               |
| `has_more`   | 다음 페이지 존재 여부                |
| `next_after` | 다음 페이지 커서, 다음 페이지가 있을 때만 반환 |

다음 페이지를 이어서 요청하기:

```bash theme={null}
curl "https://aihubmix.com/ai/v1/tasks?limit=20&order=desc&after=task_01K0ABCDEF" \
  -H "Authorization: Bearer $AIHUBMIX_API_KEY"
```

***

<h2 id="get-task-results">
  작업 결과를 가져오는 방법
</h2>

<h3 id="single-artifact">
  단일 결과물 작업
</h3>

`output`에 파일이 하나만 있으면 바로 접근할 수 있습니다.

```bash theme={null}
curl https://aihubmix.com/ai/v1/tasks/{task_id}/content \
  -H "Authorization: Bearer $AIHUBMIX_API_KEY" \
  --output result.bin
```

`output[0].content_url`을 직접 사용해도 됩니다. 다운로드 응답의 `Content-Type`은 `output[0].content_type`과 동일합니다.

<h3 id="multiple-artifacts">
  다중 결과물 작업
</h3>

`output`에 파일이 여러 개 포함된 경우 해당 `result_id`를 반드시 지정해야 합니다.

```bash theme={null}
curl https://aihubmix.com/ai/v1/tasks/{task_id}/content/{result_id} \
  -H "Authorization: Bearer $AIHUBMIX_API_KEY" \
  --output result.png
```

다중 결과물 작업에서 `result_id`를 지정하지 않으면 API는 `400 result_id_required`를 반환합니다.

<h3 id="llm-response-task">
  LLM 응답 작업
</h3>

[LLM 중단 복구](#llm-interruption-recovery) 조건을 충족하여 응답이 저장되면, 작업의 `object`는 `llm`이고 `output` 항목의 `type`은 `response`입니다. 콘텐츠 유형은 다음과 같을 수 있습니다.

* `application/json`: 일반 JSON 응답
* `text/event-stream`: 저장된 SSE 스트리밍 응답

```bash theme={null}
curl https://aihubmix.com/ai/v1/tasks/{task_id}/content \
  -H "Authorization: Bearer $AIHUBMIX_API_KEY"
```

잘림 표시는 작업 상세의 `output[0].truncated`에 있습니다. 값이 `true`이면 저장된 응답이 크기 제한으로 잘렸음을 의미합니다. `GET /ai/v1/tasks/{task_id}/content`는 원본 JSON 또는 SSE 콘텐츠를 반환하며 콘텐츠 외부에 `truncated` 필드를 감싸지 않으므로, 작업 상세를 먼저 조회한 후 콘텐츠를 읽어야 합니다.

<Warning>
  결과는 만료될 수 있고 다운로드 횟수 제한이 있을 수 있습니다. `expires_at` 이전에 저장하세요. 만료 시 `410 artifact_expired`, 다운로드 횟수 제한 초과 시 `429 too_many_downloads`를 반환합니다.
</Warning>

***

<h2 id="webhooks">
  Webhook을 사용하는 방법
</h2>

현재 비동기 작업 생성 시 작업 단위 Webhook 제출을 지원합니다. 작업 완료 후 AIHubMix가 능동적으로 통지하도록 하려면 비동기 이미지 또는 비디오 요청 본문에 `webhook_url`과 선택 항목인 `webhook_events_filter`를 전달하세요.

```bash theme={null}
curl -X POST https://aihubmix.com/ai/v1/videos \
  -H "Authorization: Bearer $AIHUBMIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "wan2.6-t2v",
    "prompt": "A tranquil Japanese garden at sunrise",
    "seconds": "5",
    "webhook_url": "https://example.com/webhooks/aihubmix",
    "webhook_events_filter": ["completed", "failed"]
  }'
```

콜백 주소는 HTTPS를 사용해야 하며, 로컬 호스트, 사설망 또는 기타 제한된 주소를 가리킬 수 없습니다.

<h3 id="webhook-payload">
  콜백 요청
</h3>

AIHubMix는 콜백 주소로 `POST` 요청을 전송합니다.

```json theme={null}
{
  "event_id": "evt_01K0ABCDEF",
  "event_type": "completed",
  "created_at": "2026-07-22T12:00:00Z",
  "data": {
    "task_id": "task_01K0ABCDEF",
    "status": "completed",
    "model": "wan2.6-t2v",
    "results": [
      {
        "url": "https://aihubmix.com/ai/v1/tasks/task_01K0ABCDEF/content"
      }
    ]
  }
}
```

| 필드                   | 설명                                             |
| -------------------- | ---------------------------------------------- |
| `event_id`           | 이번 콜백 이벤트의 고유 ID, 중복 통지 식별에 사용                 |
| `event_type`         | 작업 최종 상태: `completed`, `failed` 또는 `cancelled` |
| `created_at`         | 콜백 이벤트 생성 시간                                   |
| `data.task_id`       | 작업 ID, 작업 상세 조회에 사용 가능                         |
| `data.status`        | 현재 작업 상태                                       |
| `data.model`         | 작업 생성 시 사용한 모델                                 |
| `data.results[].url` | 생성된 결과의 다운로드 주소                                |
| `data.error.code`    | 실패 오류 코드, 실패 이벤트에만 포함될 수 있음                    |
| `data.error.message` | 실패 원인, 실패 이벤트에만 포함될 수 있음                       |

`results`의 URL도 작업 생성 시 사용한 API Key를 함께 전달해야 접근할 수 있습니다.

<h3 id="webhook-retry">
  재시도와 중복 제거
</h3>

플랫폼은 콜백을 최소 한 번 전달하려고 시도하므로, 동일한 이벤트가 중복 전송될 수 있습니다.

* HTTP `2xx`는 수신 성공을 의미합니다.
* HTTP `5xx`, 네트워크 오류 또는 타임아웃은 재시도를 유발합니다.
* HTTP `3xx`와 `4xx`는 재시도하지 않습니다.
* 최대 6회 전달하며, 재시도 간격은 순서대로 1, 4, 16, 64, 256초입니다.

수신 측은 `event_id`를 저장해야 합니다. 동일한 `event_id`를 다시 수신하면 비즈니스 로직을 건너뛰고 바로 `2xx`를 반환하세요.

<Warning>
  현재 작업 단위 Webhook은 별도로 구성 가능한 서명 자격 증명을 제공하지 않습니다. 통지를 받은 후에는 작업 생성 시 사용한 API Key로 `GET /ai/v1/tasks/{task_id}`를 요청하여 조회 결과를 기준으로 삼아야 합니다.
</Warning>

***

<h2 id="error-codes">
  오류 응답과 오류 코드
</h2>

오류 응답은 통합된 구조를 사용합니다.

```json theme={null}
{
  "error": {
    "message": "Task not found.",
    "type": "invalid_request_error",
    "code": "task_not_found",
    "tid": "req_01K0..."
  }
}
```

| 필드              | 설명                        |
| --------------- | ------------------------- |
| `error.message` | 오류 원인                     |
| `error.type`    | 오류 유형                     |
| `error.code`    | 프로그램이 식별할 수 있는 오류 코드      |
| `error.tid`     | 요청 추적 ID, 기술 지원에 문의할 때 제공 |

| HTTP 상태 코드 | 오류 코드                           | 설명                                    |
| ---------- | ------------------------------- | ------------------------------------- |
| 400        | `invalid_request`               | 파라미터 타입 또는 값이 올바르지 않음                 |
| 400        | `result_id_required`            | 다중 결과물 작업에서 `result_id` 미지정           |
| 400        | `webhook_invalid`               | Webhook URL이 유효하지 않음                  |
| 400        | `webhook_events_filter_invalid` | Webhook 이벤트 목록이 유효하지 않음               |
| 401        | `authentication_failed`         | API Key 누락 또는 무효                      |
| 403        | `async_not_enabled`             | 계정에서 비동기 작업 기능이 활성화되지 않음              |
| 404        | `task_not_found`                | 작업이 존재하지 않거나 현재 API Key 소유가 아님        |
| 404        | `result_not_found`              | 결과가 존재하지 않거나 현재 획득할 수 없음              |
| 410        | `artifact_expired`              | 결과가 만료됨                               |
| 429        | `too_many_downloads`            | 결과 다운로드 횟수 제한 초과                      |
| 503        | `async_unavailable`             | 비동기 이미지 서비스를 일시적으로 사용할 수 없음, 잠시 후 재시도 |

***

<h2 id="full-example">
  전체 예제
</h2>

비디오 작업 생성, 상태 폴링, 전체 결과 다운로드까지의 전체 프로세스입니다.

<CodeGroup>
  ```python Python theme={null}
  import base64
  import os
  import time

  import requests

  BASE_URL = "https://aihubmix.com"
  API_KEY = os.environ["AIHUBMIX_API_KEY"]
  HEADERS = {
      "Authorization": f"Bearer {API_KEY}",
      "Content-Type": "application/json",
  }

  # 1. 작업 생성
  response = requests.post(
      f"{BASE_URL}/ai/v1/videos",
      headers=HEADERS,
      json={
          "model": "wan2.6-t2v",
          "prompt": "A cat playing jazz on a piano",
          "seconds": "5",
          "size": "1280x720",
      },
      timeout=60,
  )
  response.raise_for_status()
  task = response.json()
  task_id = task["id"]

  # 2. 작업이 완료, 실패 또는 취소될 때까지 폴링
  while task["status"] not in {"completed", "failed", "cancelled"}:
      time.sleep(15)
      response = requests.get(
          f"{BASE_URL}/ai/v1/tasks/{task_id}",
          headers=HEADERS,
          timeout=30,
      )
      response.raise_for_status()
      task = response.json()
      print("status:", task["status"])

  # 3. 결과 획득
  if task["output"]:
      for index, item in enumerate(task["output"]):
          if encoded := item.get("b64_json"):
              with open(f"result-{index}.bin", "wb") as file:
                  file.write(base64.b64decode(encoded))
              continue
          result = requests.get(
              item["content_url"],
              headers=HEADERS,
              timeout=120,
          )
          result.raise_for_status()
          with open(f"result-{index}.bin", "wb") as file:
              file.write(result.content)
  elif task["status"] == "failed":
      raise RuntimeError(task.get("error"))
  ```

  ```typescript TypeScript theme={null}
  import { writeFile } from "node:fs/promises";

  const BASE_URL = "https://aihubmix.com";
  const HEADERS = {
    Authorization: `Bearer ${process.env.AIHUBMIX_API_KEY}`,
    "Content-Type": "application/json",
  };

  // 1. 작업 생성
  const created = await fetch(`${BASE_URL}/ai/v1/videos`, {
    method: "POST",
    headers: HEADERS,
    body: JSON.stringify({
      model: "wan2.6-t2v",
      prompt: "A cat playing jazz on a piano",
      seconds: "5",
      size: "1280x720",
    }),
  });
  let task = await created.json();

  // 2. 작업이 완료, 실패 또는 취소될 때까지 폴링
  const finished = new Set(["completed", "failed", "cancelled"]);
  while (!finished.has(task.status)) {
    await new Promise((resolve) => setTimeout(resolve, 15_000));
    const polled = await fetch(`${BASE_URL}/ai/v1/tasks/${task.id}`, {
      headers: HEADERS,
    });
    task = await polled.json();
    console.log("status:", task.status);
  }

  // 3. 결과 획득
  if (task.output?.length) {
    for (const [index, item] of task.output.entries()) {
      if (item.b64_json) {
        await writeFile(`result-${index}.bin`, Buffer.from(item.b64_json, "base64"));
        continue;
      }
      const result = await fetch(item.content_url, { headers: HEADERS });
      await writeFile(`result-${index}.bin`, Buffer.from(await result.arrayBuffer()));
    }
  } else if (task.status === "failed") {
    throw new Error(JSON.stringify(task.error));
  }
  ```

  ```shell curl theme={null}
  # 1. 작업 생성, 반환된 id 기록
  curl -X POST https://aihubmix.com/ai/v1/videos \
    -H "Authorization: Bearer $AIHUBMIX_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "model": "wan2.6-t2v",
      "prompt": "A cat playing jazz on a piano",
      "seconds": "5",
      "size": "1280x720"
    }'

  # 2. 15초마다 상태 조회
  curl https://aihubmix.com/ai/v1/tasks/task_01K0ABCDEF \
    -H "Authorization: Bearer $AIHUBMIX_API_KEY"

  # 3. 상태가 completed 로 바뀌면 결과 다운로드
  curl https://aihubmix.com/ai/v1/tasks/task_01K0ABCDEF/content \
    -H "Authorization: Bearer $AIHUBMIX_API_KEY" \
    --output result.mp4
  ```
</CodeGroup>

***

<h2 id="faq">
  자주 묻는 질문 (FAQ)
</h2>

**작업 상태는 얼마나 자주 조회하는 것이 좋나요?**

15초마다 한 번씩 조회하고 고빈도 폴링은 피하는 것을 권장합니다. Webhook을 사용할 때도 저빈도 조회를 예비 수단으로 유지하는 것이 좋습니다.

**생성 응답을 잃어버린 후 작업을 어떻게 찾나요?**

작업을 생성할 때 사용한 것과 동일한 API Key로 `GET /ai/v1/tasks`를 요청하면 되며, `object`, `model`, `status`로 범위를 좁힐 수 있습니다.

**같은 계정의 다른 API Key로는 왜 작업이 조회되지 않나요?**

작업은 API Key 단위로 격리됩니다. 조회, 다운로드, 목록 요청 모두 작업을 생성할 때 사용한 것과 동일한 Key를 사용해야 합니다.

**작업이 실패했는데 `output`이 빈 배열이 아닌 이유는 무엇인가요?**

일부 모델은 전체 실패 또는 취소 전에 이미 사용 가능한 결과를 생성했을 수 있습니다. `output`에 `content_url` 또는 `b64_json`이 있으면 해당 방식으로 획득할 수 있습니다.

**Webhook을 받지 못하면 어떻게 하나요?**

콜백 주소가 공개적으로 접근 가능하고 HTTPS를 사용하며 10초 이내에 `2xx`를 반환하는지 확인하세요. Webhook 사용 여부와 관계없이 `GET /ai/v1/tasks/{task_id}`로 최종 상태를 조회할 수 있습니다.

**LLM 중단 복구를 위해 기존 코드를 수정해야 하나요?**

필요 없습니다. 요청 방식, 스트리밍 동작, 응답 형식은 그대로 유지됩니다. 중단 후 콘솔에서 해당 작업을 정확히 찾을 수 있도록 응답 헤더 `X-Aihubmix-Request-Id`를 저장하는 것을 권장합니다.

***

마지막 업데이트: 2026-07-28
