> ## 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 네이티브 이미지 프로토콜로 이미지를 동기 또는 비동기로 생성하고 작업을 조회하며 결과를 다운로드합니다.

<Note>
  [모델 Schema API](/ko/api/async-tasks#model-schema)
</Note>

## 빠른 시작

네이티브 이미지 엔드포인트는 기본적으로 동기입니다. Boolean `async`를 `true`로 설정하면 백그라운드 작업을 만듭니다. 예제는 `qwen-image-2.0`을 사용합니다.

<CodeGroup>
  ```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": 1,
      "size": "1024x1024",
      "async": true
    }'
  ```

  ```json 생성 응답 theme={null}
  {
    "id": "task_01K0ABCDEF",
    "object": "image",
    "model": "qwen-image-2.0",
    "status": "in_progress",
    "output": [],
    "error": null,
    "created_at": 1784707200,
    "completed_at": null,
    "expires_at": null
  }
  ```

  ```bash 작업 조회 theme={null}
  curl https://aihubmix.com/ai/v1/images/{id} \
    -H "Authorization: Bearer $AIHUBMIX_API_KEY"
  ```

  ```bash 결과 다운로드 theme={null}
  curl "{content_url}" \
    -H "Authorization: Bearer $AIHUBMIX_API_KEY" \
    --output result.png
  ```
</CodeGroup>

<h2 id="model-schema">
  비동기 미디어 모델을 찾고 Schema를 가져오는 방법
</h2>

검색 절차는 두 단계입니다. 먼저 공개 모델 카탈로그에서 비동기 API를 지원하는 텍스트 이미지 또는 텍스트 비디오 모델을 가져옵니다. 그런 다음 모델의 `model_id`를 사용해 해당 엔드포인트의 요청 Schema를 가져옵니다.

<h3 id="async-media-model-list">
  비동기 API를 지원하는 모델 목록 조회
</h3>

모델 카탈로그는 Playground와 같은 데이터 소스를 사용합니다. 텍스트 이미지 모델에는 `type=image_generation`, 텍스트 비디오 모델에는 `type=video`를 사용하세요. `schema_checked=true`를 추가하면 요청 Schema가 게시되고 검토된 모델만 반환됩니다.

```bash theme={null}
# 텍스트 이미지 모델
curl "https://aihubmix.com/api/v1/models?type=image_generation&schema_checked=true&sort_by=order"

# 텍스트 비디오 모델
curl "https://aihubmix.com/api/v1/models?type=video&schema_checked=true&sort_by=order"
```

두 요청은 같은 API를 사용합니다. 현재 `type` 필터는 하나의 값만 허용하므로 모델 유형별로 요청하세요.

응답 구조는 `{success, message, data}`입니다. 비동기 미디어 연동과 관련된 `data` 필드는 다음과 같습니다.

| 필드                        | 설명                                                     |
| ------------------------- | ------------------------------------------------------ |
| `data[].model_id`         | 생성 요청과 후속 Schema 요청에 사용하는 모델 ID                        |
| `data[].model_name`       | 모델 표시 이름                                               |
| `data[].types`            | 쉼표로 구분된 모델 유형이며 `image_generation`과 `llm`을 함께 포함할 수 있음 |
| `data[].input_modalities` | 쉼표로 구분된 입력 모달리티                                        |
| `data[].schema_checked`   | `true`이면 요청 Schema가 게시되고 검토되었음을 의미                     |

```json theme={null}
{
  "success": true,
  "message": "",
  "data": [
    {
      "model_id": "qwen-image-2.0",
      "model_name": "Qwen Image 2.0",
      "types": "image_generation",
      "input_modalities": "text,image",
      "schema_checked": true
    }
  ]
}
```

<h3 id="single-model-schema">
  단일 모델의 요청 Schema 조회
</h3>

지원 필드, 열거형 및 숫자 범위는 모델마다 다를 수 있습니다. 이미지 또는 비디오 요청을 보내기 전에 다음 공개 API를 사용하여 선택한 모델의 사용 가능한 엔드포인트와 요청 JSON Schema를 조회하세요.

```bash theme={null}
# 텍스트 이미지 모델
curl "https://aihubmix.com/call/schema/models/qwen-image-2.0/endpoints"

# 텍스트 비디오 모델
curl "https://aihubmix.com/call/schema/models/wan2.6-t2v/endpoints"
```

응답의 `modality`는 `image` 또는 `video`입니다. `endpoints` 배열의 각 항목은 사용 가능한 호출 프로토콜 하나를 설명합니다.

| 필드                           | 설명                                             |
| ---------------------------- | ---------------------------------------------- |
| `default_endpoint`           | 모델의 기본 엔드포인트 식별자                               |
| `endpoints[].endpoint`       | 엔드포인트 식별자                                      |
| `endpoints[].method`         | `POST`와 같은 HTTP 메서드                            |
| `endpoints[].path`           | 실제 요청 경로                                       |
| `endpoints[].content_types`  | 엔드포인트가 허용하는 콘텐츠 유형                             |
| `endpoints[].lifecycle`      | 동기 또는 비동기 모드, 폴링 경로 및 상태 값                     |
| `endpoints[].request.schema` | 필수 필드와 값 제약을 포함한 해당 모델 및 엔드포인트의 전체 JSON Schema |

같은 모델에서 `/ai/v1` 엔드포인트와 OpenAI 호환 `/v1` 엔드포인트가 함께 반환될 수 있습니다. OpenAI 호환 엔드포인트는 최신 모델을 아직 지원하지 않을 수 있으므로 `/ai/v1` 엔드포인트를 우선 사용하세요. 비동기 작업 API에는 `path`가 `/ai/v1/images/generations` 또는 `/ai/v1/videos`인 항목을 선택한 후 해당 `request.schema`를 사용하세요. `endpoints` 배열의 위치에 의존하지 마세요.

다음 명령은 각 비동기 작업 엔드포인트의 요청 Schema를 직접 추출합니다.

```bash theme={null}
# 텍스트 이미지
curl -s "https://aihubmix.com/call/schema/models/qwen-image-2.0/endpoints" \
  | jq '.endpoints[] | select(.path == "/ai/v1/images/generations") | .request.schema'

# 텍스트 비디오
curl -s "https://aihubmix.com/call/schema/models/wan2.6-t2v/endpoints" \
  | jq '.endpoints[] | select(.path == "/ai/v1/videos") | .request.schema'
```

모델이 없거나 검색 가능한 엔드포인트가 없으면 `404 model_not_found`를 반환합니다. 엔드포인트 데이터를 일시적으로 사용할 수 없으면 `500 endpoints_unavailable`를 반환합니다.

***

<h2 id="create-async-task">
  이미지 작업을 생성하는 방법
</h2>

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

`async`를 생략하거나 `false`로 설정하면 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": 1,
    "size": "1024x1024",
    "response_format": "url"
  }'
```

동기 이미지 작업도 작업 기록에 저장됩니다. 클라이언트 연결이 끊어지거나 생성 응답을 잃은 경우 `GET /ai/v1/images`로 해당 작업을 찾을 수 있습니다.

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

`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
  }'
```

`GET /ai/v1/images/{id}`로 비동기 이미지 작업을 조회하세요. 완료 후 각 `output` 항목의 `content_url`을 직접 요청할 수 있습니다. 이 URL에는 해당 이미지의 `result_id`가 포함되어 있습니다.

<Note>
  이미지 요청의 `async`는 불리언 값이어야 합니다. `webhook_url`과
  `webhook_events_filter`는 `async: true`와 함께만 사용할 수 있습니다.
</Note>

<h3 id="image-parameters">
  이미지 표준 필드
</h3>

| 필드                      | 타입            | 필수  | 설명                                                   |
| ----------------------- | ------------- | --- | ---------------------------------------------------- |
| `model`                 | string        | 예   | 모델 이름                                                |
| `prompt`                | string        | 예   | 이미지 설명, 빈 문자열은 허용되지 않음                               |
| `n`                     | integer/null  | 아니요 | 이미지 수, 최솟값 `1`, 기본값 `1`                              |
| `size`                  | string/null   | 아니요 | `{width}x{height}`, 예: `1024x1024`                   |
| `aspect_ratio`          | string/null   | 아니요 | 화면 비율, `size`와 함께 사용할 수 없음                           |
| `seed`                  | integer/null  | 아니요 | 난수 시드                                                |
| `negative_prompt`       | string/null   | 아니요 | 네거티브 프롬프트                                            |
| `image`                 | string/object | 아니요 | 단일 입력 이미지, URL, Data URI, Base64 또는 `{url}` 객체 사용 가능 |
| `images`                | array/null    | 아니요 | 여러 입력 이미지                                            |
| `mask`                  | string/object | 아니요 | 이미지 편집 마스크                                           |
| `output_format`         | string/null   | 아니요 | `png`, `jpeg` 또는 `webp`, 기본값 `png`                   |
| `response_format`       | string/null   | 아니요 | `url` 또는 `b64_json`, 기본값 `url`                       |
| `async`                 | boolean       | 아니요 | `true`로 설정하면 비동기 실행                                  |
| `webhook_url`           | string        | 아니요 | HTTPS 콜백 주소, 최대 512자                                 |
| `webhook_events_filter` | string\[]     | 아니요 | `completed`, `failed`, `cancelled`의 비어 있지 않은 부분 집합   |
| `extra`                 | object/null   | 아니요 | 특정 모델의 확장 파라미터                                       |

***

<h2 id="task-object">
  미디어 작업 객체
</h2>

이미지 및 비디오 전용 API는 다음 구조를 반환합니다.

```json theme={null}
{
  "id": "task_01K0ABCDEF",
  "object": "image",
  "model": "qwen-image-2.0",
  "status": "completed",
  "output": [
    {
      "index": 0,
      "type": "file",
      "b64_json": null,
      "content_url": "https://aihubmix.com/ai/v1/images/task_01K0ABCDEF/content/result_01K0XYZ"
    }
  ],
  "error": null,
  "created_at": 1784707200,
  "completed_at": 1784707320,
  "expires_at": null
}
```

| 필드             | 타입           | 설명                                                    |
| -------------- | ------------ | ----------------------------------------------------- |
| `id`           | string       | 플랫폼 작업 ID                                             |
| `object`       | string       | `image` 또는 `video`                                    |
| `model`        | string       | 모델 이름                                                 |
| `status`       | string       | 현재 작업 상태                                              |
| `output`       | array        | 생성된 결과, 결과가 아직 없으면 빈 배열                               |
| `error`        | object/null  | 실패 정보, `code`, `message`, `upstream_detail`을 포함할 수 있음 |
| `created_at`   | integer      | 생성 시간, Unix 초                                         |
| `completed_at` | integer/null | 최종 상태 시간, Unix 초                                      |
| `expires_at`   | null         | 미디어 전용 API는 현재 `null` 반환                              |

미디어 `output` 항목:

| 필드            | 타입          | 설명                  |
| ------------- | ----------- | ------------------- |
| `index`       | integer     | 결과 순서, `0`부터 시작     |
| `type`        | string      | 현재 값은 `file`        |
| `content_url` | string/null | 결과 다운로드 주소          |
| `b64_json`    | string/null | Base64로 인코딩된 이미지 결과 |

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

| 상태            | 종료 여부 | 설명                     |
| ------------- | ----- | ---------------------- |
| `pending`     | 아니요   | 플랫폼이 작업을 접수했으며 실행 대기 중 |
| `in_progress` | 아니요   | 작업 실행 중                |
| `completed`   | 예     | 작업 완료, `output` 읽기 가능  |
| `failed`      | 예     | 작업 실패, 원인은 `error` 참조  |
| `cancelled`   | 예     | 작업 취소됨                 |

상태가 `completed`, `failed` 또는 `cancelled`로 바뀔 때까지 클라이언트에서 15초마다 조회할 수 있습니다. 15초는 클라이언트 폴링 권장 간격이며 서버 프로토콜 제한이 아닙니다.

***

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

<h3 id="query-task-detail">
  미디어 상세 조회
</h3>

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

# 비디오
curl https://aihubmix.com/ai/v1/images/{task_id} \
  -H "Authorization: Bearer $AIHUBMIX_API_KEY"
```

미디어 상세 API는 갱신된 작업 상태를 반환할 수 있으므로, 미디어 폴링에는 해당 이미지 또는 비디오 상세 API를 사용하세요.

<h3 id="query-task-list">
  미디어 목록 조회
</h3>

생성 응답을 잃은 경우 해당 미디어 목록에서 작업 ID를 찾을 수 있습니다.

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

| 파라미터    | 타입      | 기본값    | 설명                                 |
| ------- | ------- | ------ | ---------------------------------- |
| `after` | string  | -      | 페이지네이션 커서, 이전 페이지의 `next_after` 사용 |
| `limit` | integer | `20`   | 페이지당 개수, 최대 `100`                  |
| `order` | string  | `desc` | `asc`는 오름차순, 그 외 값은 `desc`로 처리     |

```json theme={null}
{
  "object": "list",
  "data": [
    {
      "id": "task_01K0ABCDEF",
      "object": "image",
      "model": "qwen-image-2.0",
      "status": "in_progress",
      "output": [],
      "error": null,
      "created_at": 1784707200,
      "completed_at": null,
      "expires_at": null
    }
  ],
  "has_more": true,
  "next_after": "task_01K0ABCDEF"
}
```

미디어 목록은 조회 시점의 작업 스냅샷을 반환하며 작업 상태를 직접 갱신하지 않습니다.

***

<h2 id="unified-tasks">
  통합 작업 API를 사용하는 방법
</h2>

통합 작업 API는 다음 필터를 지원합니다.

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

| 파라미터     | 타입      | 기본값    | 설명                                                             |
| -------- | ------- | ------ | -------------------------------------------------------------- |
| `object` | string  | -      | `llm`, `image` 또는 `video`                                      |
| `status` | string  | -      | `pending`, `in_progress`, `completed`, `failed` 또는 `cancelled` |
| `model`  | string  | -      | 모델 이름 정확 일치 필터                                                 |
| `after`  | string  | -      | 페이지네이션 커서                                                      |
| `limit`  | integer | `20`   | 범위 `1`에서 `100`                                                 |
| `order`  | string  | `desc` | `asc` 또는 `desc`                                                |

통합 작업 상세:

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

미디어 작업의 통합 `output` 항목:

```json theme={null}
{
  "index": 0,
  "result_id": "result_01K0XYZ",
  "type": "file",
  "content_type": "image/png",
  "content_url": "https://aihubmix.com/ai/v1/tasks/task_01K0ABCDEF/content/result_01K0XYZ"
}
```

단일 결과물은 `/ai/v1/tasks/{id}/content`로 직접 요청하세요. 다중 결과물 작업은 `/ai/v1/tasks/{id}/content/{result_id}`를 요청해야 하며, 결과 ID를 지정하지 않으면 `400 result_id_required`가 반환됩니다.

<Note>
  통합 작업 목록, 상세 및 콘텐츠 API는 작업을 생성한 Bearer Token에 따라
  격리됩니다. 같은 계정의 다른 API Key로는 해당 작업을 읽을 수 없습니다.
</Note>

***

<h2 id="get-task-results">
  미디어 결과를 다운로드하는 방법
</h2>

<h3 id="multiple-artifacts">
  이미지 다운로드
</h3>

이미지가 완료되면 미디어 작업 객체의 `output[].content_url`을 각 항목별로 요청하세요.

```bash theme={null}
curl "{content_url}" \
  -H "Authorization: Bearer $AIHUBMIX_API_KEY" \
  --output result.png
```

이미지 미디어 다운로드 경로는 `/ai/v1/images/{id}/content/{result_id}`입니다. 미디어 작업 객체는 `result_id`를 별도로 공개하지 않으므로 클라이언트는 `content_url`을 직접 사용하면 됩니다.

`b64_json`이 비어 있지 않으면 해당 필드를 직접 Base64 디코딩할 수 있습니다.

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

비동기 이미지 및 비디오는 작업 단위 Webhook을 지원합니다.

```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 tranquil garden at sunrise",
    "n": 1,
    "async": true,
    "size": "1024x1024",
    "webhook_url": "https://example.com/webhooks/aihubmix",
    "webhook_events_filter": ["completed", "failed"]
  }'
```

`webhook_url`은 최대 512자이며 로컬 호스트, 사설망 또는 기타 제한된 주소를 가리킬 수 없습니다. `webhook_events_filter`를 생략하면 플랫폼은 `completed`, `failed`, `cancelled`를 전송합니다. 명시적으로 전달하는 경우 배열은 비어 있거나 중복될 수 없으며 `webhook_url`과 함께 사용해야 합니다.

요청에 `webhook_url`을 전달하지 않으면 비동기 이미지 및 비디오는 계정에 설정된 기본 콜백 주소를 사용하려고 시도합니다. 유효하지 않은 계정 기본 주소는 무시되며 작업 생성을 차단하지 않습니다.

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

```json theme={null}
{
  "event_id": "evt_01K0ABCDEF",
  "event_type": "completed",
  "created_at": "2026-08-12T12:00:00Z",
  "data": {
    "task_id": "task_01K0ABCDEF",
    "status": "completed",
    "model": "qwen-image-2.0",
    "results": [
      {
        "url": "https://aihubmix.com/ai/v1/tasks/task_01K0ABCDEF/content/result_01K0XYZ"
      }
    ]
  }
}
```

| 필드                   | 설명                                   |
| -------------------- | ------------------------------------ |
| `event_id`           | 콜백 이벤트 ID, 중복 제거에 사용                 |
| `event_type`         | `completed`, `failed` 또는 `cancelled` |
| `created_at`         | RFC 3339 형식의 이벤트 시간                  |
| `data.task_id`       | 플랫폼 작업 ID                            |
| `data.status`        | 현재 최종 상태                             |
| `data.model`         | 모델 이름                                |
| `data.results[].url` | 완료 시 반환될 수 있는 결과 다운로드 주소             |
| `data.error`         | 실패 시 반환될 수 있는 오류 정보                  |

`results`는 결과가 저장된 경우에만 표시되며 다운로드에는 여전히 Bearer Token이 필요합니다.

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

플랫폼은 최소 한 번 전달 방식을 사용하므로 동일한 이벤트가 중복 전송될 수 있습니다.

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

수신 측은 `event_id`를 저장하고 동일한 이벤트를 다시 받으면 즉시 `2xx`를 반환해야 합니다.

<Warning>
  작업 단위 Webhook 자체에는 별도의 서명 키가 없습니다. 서명 검증이 필요하면
  계정 단위 Webhook 구독을 설정하고 작업 상세 조회를 결과 확인 방법으로
  유지하세요.
</Warning>

***

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

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

| HTTP 상태 코드 | 오류 코드                           | 설명                                            |
| ---------- | ------------------------------- | --------------------------------------------- |
| 400        | `invalid_request`               | 파라미터 타입 또는 값이 올바르지 않음                         |
| 400        | `result_id_required`            | 통합 작업에 여러 결과가 있지만 다운로드 시 `result_id`를 지정하지 않음 |
| 400        | `webhook_invalid`               | Webhook URL이 유효하지 않거나 필터에 URL이 없음             |
| 400        | `webhook_events_filter_invalid` | Webhook 이벤트 목록이 유효하지 않음                       |
| 401        | `authentication_failed`         | API Key 누락 또는 무효                              |
| 403        | `async_not_enabled`             | 계정에서 비동기 작업 기능이 활성화되지 않음                      |
| 404        | `task_not_found`                | 작업 또는 페이지네이션 커서가 존재하지 않음                      |
| 404        | `result_not_found`              | 결과가 존재하지 않거나 현재 다운로드할 수 없음                    |
| 410        | `artifact_expired`              | 결과가 만료됨                                       |
| 413        | `request_too_large`             | 요청 본문이 32 MiB를 초과함                            |
| 429        | `too_many_downloads`            | 결과 다운로드 횟수 제한 초과                              |
| 503        | `async_unavailable`             | 비동기 이미지 서비스를 일시적으로 사용할 수 없음                   |

`error.tid`는 요청 추적 ID입니다. 기술 지원에 문의할 때 함께 제공하세요.

***

## 전체 예제

비동기 작업을 만들고 폴링한 뒤 반환된 이미지를 모두 저장합니다.

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

  import requests

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

  response = requests.post(
      f"{base_url}/ai/v1/images/generations",
      headers=headers,
      json={
          "model": "qwen-image-2.0",
          "prompt": "A flower shop with delicate windows, warm sunlight streaming in",
          "n": 1,
          "size": "1024x1024",
          "async": True,
      },
      timeout=60,
  )
  response.raise_for_status()
  task = response.json()

  while task["status"] not in {"completed", "failed", "cancelled"}:
      time.sleep(15)
      response = requests.get(
          f"{base_url}/ai/v1/images/{task['id']}",
          headers=headers,
          timeout=30,
      )
      response.raise_for_status()
      task = response.json()

  if task["status"] != "completed":
      raise RuntimeError(task.get("error") or task["status"])

  for output in task["output"]:
      filename = f"result-{output['index']}.png"
      if output.get("b64_json"):
          content = base64.b64decode(output["b64_json"])
      else:
          result = requests.get(output["content_url"], headers=headers, timeout=120)
          result.raise_for_status()
          content = result.content
      with open(filename, "wb") as file:
          file.write(content)
  ```

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

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

  const created = await fetch(`${baseUrl}/ai/v1/images/generations`, {
    method: "POST",
    headers,
    body: JSON.stringify({
      model: "qwen-image-2.0",
      prompt: "A flower shop with delicate windows, warm sunlight streaming in",
      n: 1,
      size: "1024x1024",
      async: true,
    }),
  });
  if (!created.ok) throw new Error(await created.text());
  let task = await created.json();

  const finished = new Set(["completed", "failed", "cancelled"]);
  while (!finished.has(task.status)) {
    await new Promise((resolve) => setTimeout(resolve, 15_000));
    const polled = await fetch(`${baseUrl}/ai/v1/images/${task.id}`, { headers });
    if (!polled.ok) throw new Error(await polled.text());
    task = await polled.json();
  }

  if (task.status !== "completed") {
    throw new Error(JSON.stringify(task.error ?? task.status));
  }

  for (const output of task.output) {
    let content;
    if (output.b64_json) {
      content = Buffer.from(output.b64_json, "base64");
    } else {
      const result = await fetch(output.content_url, { headers });
      if (!result.ok) throw new Error(await result.text());
      content = Buffer.from(await result.arrayBuffer());
    }
    await writeFile(`result-${output.index}.png`, content);
  }
  ```
</CodeGroup>
