> ## 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.

# Generación de imágenes

> Genera imágenes de forma síncrona o asíncrona con el protocolo nativo de AIHubMix, consulta tareas y descarga resultados.

<Note>
  [API Schema del modelo](/es/api/async-tasks#model-schema)
</Note>

## Inicio rápido

El endpoint nativo de imágenes es síncrono por defecto. Establece el booleano `async` en `true` para crear una tarea en segundo plano. El ejemplo usa `qwen-image-2.0`.

<CodeGroup>
  ```bash Crear tarea 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 Respuesta de creación 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 Consultar tarea theme={null}
  curl https://aihubmix.com/ai/v1/images/{id} \
    -H "Authorization: Bearer $AIHUBMIX_API_KEY"
  ```

  ```bash Descargar resultado theme={null}
  curl "{content_url}" \
    -H "Authorization: Bearer $AIHUBMIX_API_KEY" \
    --output result.png
  ```
</CodeGroup>

<h2 id="model-schema">
  Cómo descubrir modelos multimedia asíncronos y obtener sus esquemas
</h2>

El proceso de descubrimiento tiene dos pasos. Primero, obtén del catálogo público los modelos de texto a imagen o texto a video compatibles con las API asíncronas. Después, usa el `model_id` del modelo para obtener el esquema de solicitud de sus endpoints.

<h3 id="async-media-model-list">
  Listar modelos compatibles con las API asíncronas
</h3>

El catálogo de modelos usa la misma fuente de datos que Playground. Usa `type=image_generation` para los modelos de texto a imagen y `type=video` para los modelos de texto a video. Al añadir `schema_checked=true`, la lista se limita a los modelos cuyo esquema de solicitud se ha publicado y revisado.

```bash theme={null}
# Modelos de texto a imagen
curl "https://aihubmix.com/api/v1/models?type=image_generation&schema_checked=true&sort_by=order"

# Modelos de texto a video
curl "https://aihubmix.com/api/v1/models?type=video&schema_checked=true&sort_by=order"
```

Ambas solicitudes usan el mismo endpoint. El filtro `type` acepta actualmente un solo valor, por lo que debes solicitar cada tipo de modelo por separado.

La respuesta tiene la forma `{success, message, data}`. Los siguientes campos de `data` son relevantes para las integraciones multimedia asíncronas.

| Campo                     | Descripción                                                                      |
| ------------------------- | -------------------------------------------------------------------------------- |
| `data[].model_id`         | ID del modelo usado en la generación y en las solicitudes posteriores de esquema |
| `data[].model_name`       | Nombre visible del modelo                                                        |
| `data[].types`            | Tipos separados por comas; puede incluir `image_generation` y `llm`              |
| `data[].input_modalities` | Modalidades de entrada separadas por comas                                       |
| `data[].schema_checked`   | `true` cuando el esquema de solicitud se ha publicado y revisado                 |

```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">
  Obtener el esquema de solicitud de un modelo
</h3>

Los campos, las enumeraciones y los rangos numéricos compatibles pueden variar según el modelo. Antes de enviar una solicitud de imagen o video, usa el siguiente endpoint público para obtener los endpoints disponibles y los esquemas JSON de solicitud del modelo seleccionado.

```bash theme={null}
# Modelo de texto a imagen
curl "https://aihubmix.com/call/schema/models/qwen-image-2.0/endpoints"

# Modelo de texto a video
curl "https://aihubmix.com/call/schema/models/wan2.6-t2v/endpoints"
```

El valor de `modality` en la respuesta es `image` o `video`. Cada elemento del array `endpoints` describe un protocolo de llamada disponible.

| Campo                        | Descripción                                                                                                                         |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `default_endpoint`           | Identificador del endpoint predeterminado del modelo                                                                                |
| `endpoints[].endpoint`       | Identificador del endpoint                                                                                                          |
| `endpoints[].method`         | Método HTTP, como `POST`                                                                                                            |
| `endpoints[].path`           | Ruta de la solicitud                                                                                                                |
| `endpoints[].content_types`  | Tipos de contenido aceptados por el endpoint                                                                                        |
| `endpoints[].lifecycle`      | Modo síncrono o asíncrono, ruta de consulta y valores de estado                                                                     |
| `endpoints[].request.schema` | Esquema JSON completo de la solicitud para este modelo y endpoint, incluidos los campos obligatorios y las restricciones de valores |

Un modelo puede devolver endpoints `/ai/v1` y endpoints `/v1` compatibles con OpenAI. Es posible que los endpoints compatibles con OpenAI todavía no admitan los modelos más recientes, por lo que se recomienda usar primero los endpoints `/ai/v1`. Para las API de tareas asíncronas, selecciona el elemento cuyo `path` sea `/ai/v1/images/generations` o `/ai/v1/videos` y utiliza su `request.schema`. No dependas de la posición de un elemento en el array `endpoints`.

Los siguientes comandos extraen directamente el esquema de solicitud de cada endpoint de tareas asíncronas.

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

# Texto a video
curl -s "https://aihubmix.com/call/schema/models/wan2.6-t2v/endpoints" \
  | jq '.endpoints[] | select(.path == "/ai/v1/videos") | .request.schema'
```

El endpoint devuelve `404 model_not_found` si el modelo no existe o no tiene endpoints detectables. Devuelve `500 endpoints_unavailable` cuando los datos de los endpoints no están disponibles temporalmente.

***

<h2 id="create-async-task">
  Cómo crear tareas de imagen
</h2>

<h3 id="create-sync-image">
  Imágenes síncronas
</h3>

Cuando se omite `async` o se establece en `false`, el endpoint espera a que finalice la generación y devuelve un objeto de tarea:

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

Las tareas de imagen síncronas también guardan un registro de tarea. Si se interrumpe la conexión del cliente o se pierde la respuesta de creación, usa `GET /ai/v1/images` para encontrar la tarea correspondiente.

<h3 id="create-async-image">
  Imágenes asíncronas
</h3>

Cuando `async` se establece en el valor booleano `true`, el endpoint devuelve inmediatamente un objeto de tarea y la generación continúa en segundo plano:

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

Usa `GET /ai/v1/images/{id}` para consultar una tarea de imagen asíncrona. Cuando termine, solicita directamente el `content_url` de cada elemento de `output`; esta URL ya contiene el `result_id` de la imagen correspondiente.

<Note>
  `async` en una solicitud de imagen debe ser un valor booleano. `webhook_url` y
  `webhook_events_filter` solo pueden usarse con `async: true`.
</Note>

<h3 id="image-parameters">
  Campos estándar de imagen
</h3>

| Campo                   | Tipo          | Obligatorio | Descripción                                                       |
| ----------------------- | ------------- | ----------- | ----------------------------------------------------------------- |
| `model`                 | string        | Sí          | Nombre del modelo                                                 |
| `prompt`                | string        | Sí          | Descripción de la imagen; no puede estar vacía                    |
| `n`                     | integer/null  | No          | Número de imágenes, mínimo `1`, predeterminado `1`                |
| `size`                  | string/null   | No          | `{width}x{height}`, por ejemplo `1024x1024`                       |
| `aspect_ratio`          | string/null   | No          | Relación de aspecto; excluyente con `size`                        |
| `seed`                  | integer/null  | No          | Semilla aleatoria                                                 |
| `negative_prompt`       | string/null   | No          | Prompt negativo                                                   |
| `image`                 | string/object | No          | Una imagen de entrada como URL, Data URI, Base64 u objeto `{url}` |
| `images`                | array/null    | No          | Varias imágenes de entrada                                        |
| `mask`                  | string/object | No          | Máscara para editar imágenes                                      |
| `output_format`         | string/null   | No          | `png`, `jpeg` o `webp`; predeterminado `png`                      |
| `response_format`       | string/null   | No          | `url` o `b64_json`; predeterminado `url`                          |
| `async`                 | boolean       | No          | Establécelo en `true` para la ejecución asíncrona                 |
| `webhook_url`           | string        | No          | URL HTTPS de devolución de llamada, hasta 512 caracteres          |
| `webhook_events_filter` | string\[]     | No          | Subconjunto no vacío de `completed`, `failed` y `cancelled`       |
| `extra`                 | object/null   | No          | Parámetros de extensión específicos del modelo                    |

***

<h2 id="task-object">
  Objeto de tarea multimedia
</h2>

Los endpoints específicos de imagen y video devuelven la siguiente estructura:

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

| Campo          | Tipo         | Descripción                                                                 |
| -------------- | ------------ | --------------------------------------------------------------------------- |
| `id`           | string       | ID de tarea de la plataforma                                                |
| `object`       | string       | `image` o `video`                                                           |
| `model`        | string       | Nombre del modelo                                                           |
| `status`       | string       | Estado actual de la tarea                                                   |
| `output`       | array        | Resultados generados; matriz vacía cuando aún no hay resultados             |
| `error`        | object/null  | Información del error; puede contener `code`, `message` y `upstream_detail` |
| `created_at`   | integer      | Hora de creación en segundos Unix                                           |
| `completed_at` | integer/null | Hora del estado final en segundos Unix                                      |
| `expires_at`   | null         | Los endpoints multimedia específicos devuelven actualmente `null`           |

Elemento `output` multimedia:

| Campo         | Tipo        | Descripción                              |
| ------------- | ----------- | ---------------------------------------- |
| `index`       | integer     | Orden del resultado, desde `0`           |
| `type`        | string      | Actualmente `file`                       |
| `content_url` | string/null | URL de descarga del resultado            |
| `b64_json`    | string/null | Resultado de imagen codificado en Base64 |

<h3 id="task-status">
  Estados de tarea
</h3>

| Estado        | Final | Descripción                                                  |
| ------------- | ----- | ------------------------------------------------------------ |
| `pending`     | No    | La plataforma ha recibido la tarea y espera su ejecución     |
| `in_progress` | No    | La tarea se está ejecutando                                  |
| `completed`   | Sí    | La tarea ha terminado y se puede leer `output`               |
| `failed`      | Sí    | La tarea ha fallado; consulta `error` para conocer el motivo |
| `cancelled`   | Sí    | La tarea se ha cancelado                                     |

Los clientes pueden consultar el estado cada 15 segundos hasta que cambie a `completed`, `failed` o `cancelled`. Los 15 segundos son una recomendación de sondeo para el cliente, no un límite del protocolo del servidor.

***

<h2 id="query-tasks">
  Cómo consultar tareas multimedia
</h2>

<h3 id="query-task-detail">
  Consultar detalles multimedia
</h3>

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

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

Los endpoints de detalles multimedia pueden devolver un estado actualizado de la tarea. Por ello, el sondeo multimedia debe usar el endpoint de detalles de imagen o video correspondiente.

<h3 id="query-task-list">
  Consultar la lista de tareas multimedia
</h3>

Si se pierde la respuesta de creación, puedes recuperar el ID de la tarea mediante la lista multimedia correspondiente:

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

| Parámetro | Tipo    | Predeterminado | Descripción                                                            |
| --------- | ------- | -------------- | ---------------------------------------------------------------------- |
| `after`   | string  | -              | Cursor de paginación; usa `next_after` de la página anterior           |
| `limit`   | integer | `20`           | Cantidad por página, máximo `100`                                      |
| `order`   | string  | `desc`         | `asc` para orden ascendente; los demás valores se procesan como `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"
}
```

Las listas multimedia devuelven instantáneas de las tareas en el momento de la consulta y no actualizan activamente su estado.

***

<h2 id="unified-tasks">
  Cómo usar el endpoint de tareas unificadas
</h2>

El endpoint de tareas unificadas admite los siguientes filtros:

```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"
```

| Parámetro | Tipo    | Predeterminado | Descripción                                                   |
| --------- | ------- | -------------- | ------------------------------------------------------------- |
| `object`  | string  | -              | `llm`, `image` o `video`                                      |
| `status`  | string  | -              | `pending`, `in_progress`, `completed`, `failed` o `cancelled` |
| `model`   | string  | -              | Filtro exacto por nombre de modelo                            |
| `after`   | string  | -              | Cursor de paginación                                          |
| `limit`   | integer | `20`           | Rango de `1` a `100`                                          |
| `order`   | string  | `desc`         | `asc` o `desc`                                                |

Detalles de una tarea unificada:

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

Elemento `output` unificado de una tarea multimedia:

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

Para una tarea de un solo artefacto, solicita directamente `/ai/v1/tasks/{id}/content`. Para una tarea con varios artefactos, solicita `/ai/v1/tasks/{id}/content/{result_id}`. Si no se especifica el ID del resultado, se devuelve `400 result_id_required`.

<Note>
  Los endpoints de lista, detalles y contenido de tareas unificadas están
  aislados por el Bearer Token usado para crear la tarea. Otras API Keys de la
  misma cuenta no pueden leerla.
</Note>

***

<h2 id="get-task-results">
  Cómo descargar resultados multimedia
</h2>

<h3 id="multiple-artifacts">
  Descargar imágenes
</h3>

Cuando termine la tarea de imagen, solicita cada `output[].content_url` del objeto de tarea multimedia:

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

La ruta de descarga multimedia de imágenes es `/ai/v1/images/{id}/content/{result_id}`. El objeto de tarea multimedia no expone `result_id` por separado, por lo que el cliente puede usar directamente `content_url`.

Cuando `b64_json` no esté vacío, puede descodificarse directamente desde Base64.

<h2 id="webhooks">
  Cómo usar Webhooks
</h2>

Las imágenes y los videos asíncronos admiten Webhooks a nivel de tarea:

```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` admite hasta 512 caracteres y no puede apuntar a localhost, redes privadas ni otras direcciones restringidas. Cuando se omite `webhook_events_filter`, la plataforma envía `completed`, `failed` y `cancelled`. Cuando se proporciona explícitamente, la matriz no puede estar vacía ni contener duplicados, y debe usarse junto con `webhook_url`.

Cuando la solicitud no incluye `webhook_url`, las imágenes y los videos asíncronos intentan usar la URL de devolución de llamada predeterminada configurada en la cuenta. Una URL predeterminada no válida se ignora y no impide la creación de la tarea.

<h3 id="webhook-payload">
  Solicitud de devolución de llamada
</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"
      }
    ]
  }
}
```

| Campo                | Descripción                                                         |
| -------------------- | ------------------------------------------------------------------- |
| `event_id`           | ID del evento de devolución de llamada, usado para la deduplicación |
| `event_type`         | `completed`, `failed` o `cancelled`                                 |
| `created_at`         | Hora del evento en formato RFC 3339                                 |
| `data.task_id`       | ID de tarea de la plataforma                                        |
| `data.status`        | Estado final actual                                                 |
| `data.model`         | Nombre del modelo                                                   |
| `data.results[].url` | URL de descarga del resultado, si está disponible al finalizar      |
| `data.error`         | Información del error, si está disponible cuando falla              |

`results` solo aparece cuando los resultados se han archivado. Las descargas siguen requiriendo un Bearer Token.

<h3 id="webhook-retry">
  Reintentos y deduplicación
</h3>

La plataforma usa una entrega al menos una vez, por lo que el mismo evento puede entregarse más de una vez:

* HTTP `2xx` indica que se recibió correctamente.
* HTTP `5xx`, los errores de red o los tiempos de espera agotados activan un reintento.
* HTTP `3xx` y `4xx` no se vuelven a intentar.
* Se realizan hasta 6 intentos de entrega, con intervalos de 1, 4, 16, 64 y 256 segundos.

El receptor debe guardar `event_id` y devolver `2xx` inmediatamente cuando vuelva a recibir el mismo evento.

<Warning>
  Los Webhooks a nivel de tarea no incluyen una clave de firma independiente. Si
  se requiere verificar la firma, configura una suscripción de Webhook a nivel
  de cuenta y conserva la consulta de detalles de la tarea como método para
  confirmar los resultados.
</Warning>

***

<h2 id="error-codes">
  Respuestas y códigos de error
</h2>

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

| Estado HTTP | Código de error                 | Descripción                                                                                |
| ----------- | ------------------------------- | ------------------------------------------------------------------------------------------ |
| 400         | `invalid_request`               | El tipo o el valor de un parámetro no es correcto                                          |
| 400         | `result_id_required`            | La tarea unificada tiene varios resultados, pero no se especificó `result_id` al descargar |
| 400         | `webhook_invalid`               | La URL del Webhook no es válida o el filtro no tiene URL                                   |
| 400         | `webhook_events_filter_invalid` | La lista de eventos de Webhook no es válida                                                |
| 401         | `authentication_failed`         | Falta la API Key o no es válida                                                            |
| 403         | `async_not_enabled`             | Las tareas asíncronas no están activadas para la cuenta                                    |
| 404         | `task_not_found`                | La tarea o el cursor de paginación no existe                                               |
| 404         | `result_not_found`              | El resultado no existe o no se puede descargar actualmente                                 |
| 410         | `artifact_expired`              | El resultado ha caducado                                                                   |
| 413         | `request_too_large`             | El cuerpo de la solicitud supera los 32 MiB                                                |
| 429         | `too_many_downloads`            | Se ha superado el límite de descargas del resultado                                        |
| 503         | `async_unavailable`             | El servicio de imágenes asíncronas no está disponible temporalmente                        |

`error.tid` es el ID de seguimiento de la solicitud. Inclúyelo cuando contactes con el soporte técnico para investigar un problema.

***

## Ejemplo completo

Estos ejemplos crean una tarea asíncrona, la consultan y guardan todas las imágenes devueltas.

<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>
