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

# Génération d'images

> Générez des images de manière synchrone ou asynchrone avec le protocole natif AIHubMix, consultez les tâches et téléchargez les résultats.

<Note>
  [API Schema du modèle](/fr/api/async-tasks#model-schema)
</Note>

## Démarrage rapide

Le point de terminaison image natif est synchrone par défaut. Définissez le booléen `async` sur `true` pour créer une tâche en arrière-plan. Cet exemple utilise `qwen-image-2.0`.

<CodeGroup>
  ```bash Créer la tâche 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 Réponse de création 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 Consulter la tâche theme={null}
  curl https://aihubmix.com/ai/v1/images/{id} \
    -H "Authorization: Bearer $AIHUBMIX_API_KEY"
  ```

  ```bash Télécharger le résultat theme={null}
  curl "{content_url}" \
    -H "Authorization: Bearer $AIHUBMIX_API_KEY" \
    --output result.png
  ```
</CodeGroup>

<h2 id="model-schema">
  Comment découvrir les modèles média asynchrones et obtenir leurs schémas ?
</h2>

La découverte se déroule en deux étapes. Récupérez d'abord dans le catalogue public les modèles texte-image ou texte-vidéo compatibles avec les API asynchrones. Utilisez ensuite le `model_id` du modèle pour obtenir le schéma de requête de ses endpoints.

<h3 id="async-media-model-list">
  Lister les modèles compatibles avec les API asynchrones
</h3>

Le catalogue de modèles utilise la même source de données que Playground. Utilisez `type=image_generation` pour les modèles texte-image et `type=video` pour les modèles texte-vidéo. Le paramètre `schema_checked=true` limite la liste aux modèles dont le schéma de requête a été publié et vérifié.

```bash theme={null}
# Modèles texte-image
curl "https://aihubmix.com/api/v1/models?type=image_generation&schema_checked=true&sort_by=order"

# Modèles texte-vidéo
curl "https://aihubmix.com/api/v1/models?type=video&schema_checked=true&sort_by=order"
```

Les deux requêtes utilisent le même endpoint. Le filtre `type` accepte actuellement une seule valeur. Interrogez donc chaque type de modèle séparément.

La réponse suit la forme `{success, message, data}`. Les champs de `data` utiles aux intégrations média asynchrones sont les suivants.

| Champ                     | Description                                                                     |
| ------------------------- | ------------------------------------------------------------------------------- |
| `data[].model_id`         | ID du modèle utilisé pour la génération et les requêtes de schéma suivantes     |
| `data[].model_name`       | Nom d'affichage du modèle                                                       |
| `data[].types`            | Types séparés par des virgules, avec éventuellement `image_generation` et `llm` |
| `data[].input_modalities` | Modalités d'entrée séparées par des virgules                                    |
| `data[].schema_checked`   | `true` lorsque le schéma de requête a été publié et vérifié                     |

```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">
  Obtenir le schéma de requête d'un modèle
</h3>

Les champs, les valeurs d'énumération et les plages numériques pris en charge peuvent varier selon le modèle. Avant d'envoyer une requête d'image ou de vidéo, utilisez l'endpoint public ci-dessous pour récupérer les endpoints disponibles et les schémas JSON de requête du modèle sélectionné.

```bash theme={null}
# Modèle texte-image
curl "https://aihubmix.com/call/schema/models/qwen-image-2.0/endpoints"

# Modèle texte-vidéo
curl "https://aihubmix.com/call/schema/models/wan2.6-t2v/endpoints"
```

La valeur `modality` de la réponse est `image` ou `video`. Chaque élément du tableau `endpoints` décrit un protocole d'appel disponible.

| Champ                        | Description                                                                                                            |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `default_endpoint`           | Identifiant de l'endpoint par défaut du modèle                                                                         |
| `endpoints[].endpoint`       | Identifiant de l'endpoint                                                                                              |
| `endpoints[].method`         | Méthode HTTP, par exemple `POST`                                                                                       |
| `endpoints[].path`           | Chemin de la requête                                                                                                   |
| `endpoints[].content_types`  | Types de contenu acceptés par l'endpoint                                                                               |
| `endpoints[].lifecycle`      | Mode synchrone ou asynchrone, chemin d'interrogation et valeurs de statut                                              |
| `endpoints[].request.schema` | Schéma JSON complet de la requête pour ce modèle et cet endpoint, avec les champs requis et les contraintes de valeurs |

Un modèle peut renvoyer des endpoints `/ai/v1` ainsi que des endpoints `/v1` compatibles avec OpenAI. Les endpoints compatibles avec OpenAI peuvent ne pas encore prendre en charge les modèles les plus récents. Utilisez donc de préférence les endpoints `/ai/v1`. Pour les API de tâches asynchrones, sélectionnez l'élément dont le `path` est `/ai/v1/images/generations` ou `/ai/v1/videos`, puis utilisez son `request.schema`. Ne dépendez pas de la position d'un élément dans le tableau `endpoints`.

Les commandes suivantes extraient directement le schéma de requête de chaque endpoint de tâche asynchrone.

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

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

L'endpoint renvoie `404 model_not_found` si le modèle n'existe pas ou ne dispose d'aucun endpoint détectable. Il renvoie `500 endpoints_unavailable` lorsque les données des endpoints sont temporairement indisponibles.

***

<h2 id="create-async-task">
  Comment créer une tâche d'image ?
</h2>

<h3 id="create-sync-image">
  Image synchrone
</h3>

Lorsque `async` est omis ou vaut `false`, l'API attend la fin de la génération et renvoie l'objet tâche :

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

Les tâches d'image synchrones sont également enregistrées. Si la connexion du client est interrompue ou si la réponse de création est perdue, utilisez `GET /ai/v1/images` pour retrouver la tâche.

<h3 id="create-async-image">
  Image asynchrone
</h3>

Lorsque `async` vaut le booléen `true`, l'API renvoie immédiatement l'objet tâche et la génération se poursuit en arrière-plan :

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

Consultez la tâche avec `GET /ai/v1/images/{id}`. Une fois terminée, demandez directement le `content_url` de chaque élément `output`. Cette URL contient le `result_id` de l'image correspondante.

<Note>
  Dans une requête d'image, `async` doit être un booléen. `webhook_url` et
  `webhook_events_filter` ne peuvent être utilisés qu'avec `async: true`.
</Note>

<h3 id="image-parameters">
  Champs standard des images
</h3>

| Champ                   | Type          | Obligatoire | Description                                                                       |
| ----------------------- | ------------- | ----------- | --------------------------------------------------------------------------------- |
| `model`                 | string        | Oui         | Nom du modèle                                                                     |
| `prompt`                | string        | Oui         | Description de l'image, non vide                                                  |
| `n`                     | integer/null  | Non         | Nombre d'images, minimum `1`, valeur par défaut `1`                               |
| `size`                  | string/null   | Non         | `{width}x{height}`, par exemple `1024x1024`                                       |
| `aspect_ratio`          | string/null   | Non         | Rapport largeur-hauteur, mutuellement exclusif avec `size`                        |
| `seed`                  | integer/null  | Non         | Graine aléatoire                                                                  |
| `negative_prompt`       | string/null   | Non         | Prompt négatif                                                                    |
| `image`                 | string/object | Non         | Image d'entrée unique sous forme d'URL, de Data URI, de Base64 ou d'objet `{url}` |
| `images`                | array/null    | Non         | Plusieurs images d'entrée                                                         |
| `mask`                  | string/object | Non         | Masque de modification d'image                                                    |
| `output_format`         | string/null   | Non         | `png`, `jpeg` ou `webp`, valeur par défaut `png`                                  |
| `response_format`       | string/null   | Non         | `url` ou `b64_json`, valeur par défaut `url`                                      |
| `async`                 | boolean       | Non         | Exécution asynchrone si la valeur est `true`                                      |
| `webhook_url`           | string        | Non         | Adresse de rappel HTTPS, 512 caractères maximum                                   |
| `webhook_events_filter` | string\[]     | Non         | Sous-ensemble non vide de `completed`, `failed`, `cancelled`                      |
| `extra`                 | object/null   | Non         | Paramètres étendus propres au modèle                                              |

***

<h2 id="task-object">
  Objet de tâche média
</h2>

Les API propres aux images et aux vidéos renvoient la structure suivante :

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

| Champ          | Type         | Description                                                                   |
| -------------- | ------------ | ----------------------------------------------------------------------------- |
| `id`           | string       | ID de tâche de la plateforme                                                  |
| `object`       | string       | `image` ou `video`                                                            |
| `model`        | string       | Nom du modèle                                                                 |
| `status`       | string       | Statut actuel de la tâche                                                     |
| `output`       | array        | Résultats générés, tableau vide s'il n'y en a pas encore                      |
| `error`        | object/null  | Informations d'échec, pouvant contenir `code`, `message` et `upstream_detail` |
| `created_at`   | integer      | Date de création, en secondes Unix                                            |
| `completed_at` | integer/null | Date du statut final, en secondes Unix                                        |
| `expires_at`   | null         | Les API propres aux médias renvoient actuellement `null`                      |

Élément `output` d'un média :

| Champ         | Type        | Description                           |
| ------------- | ----------- | ------------------------------------- |
| `index`       | integer     | Ordre du résultat, à partir de `0`    |
| `type`        | string      | Vaut actuellement `file`              |
| `content_url` | string/null | Adresse de téléchargement du résultat |
| `b64_json`    | string/null | Résultat d'image encodé en Base64     |

<h3 id="task-status">
  Description des statuts
</h3>

| Statut        | Terminé | Description                                           |
| ------------- | ------- | ----------------------------------------------------- |
| `pending`     | Non     | La plateforme a reçu la tâche, en attente d'exécution |
| `in_progress` | Non     | La tâche est en cours d'exécution                     |
| `completed`   | Oui     | La tâche est terminée, `output` est disponible        |
| `failed`      | Oui     | La tâche a échoué, consulter `error`                  |
| `cancelled`   | Oui     | La tâche a été annulée                                |

Le client peut interroger la tâche toutes les 15 secondes jusqu'au statut `completed`, `failed` ou `cancelled`. Cet intervalle de 15 secondes est une recommandation pour le polling côté client, pas une restriction du protocole serveur.

***

<h2 id="query-tasks">
  Comment consulter les tâches média ?
</h2>

<h3 id="query-task-detail">
  Consulter le détail d'un média
</h3>

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

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

Les API de détail média peuvent renvoyer un statut actualisé. Utilisez donc l'API de détail d'image ou de vidéo correspondante pour le polling.

<h3 id="query-task-list">
  Lister les médias
</h3>

Si la réponse de création est perdue, retrouvez l'ID de tâche dans la liste du média correspondant :

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

| Paramètre | Type    | Valeur par défaut | Description                                                             |
| --------- | ------- | ----------------- | ----------------------------------------------------------------------- |
| `after`   | string  | -                 | Curseur de pagination, utiliser le `next_after` de la page précédente   |
| `limit`   | integer | `20`              | Nombre d'éléments par page, maximum `100`                               |
| `order`   | string  | `desc`            | Ordre croissant avec `asc`, toute autre valeur est traitée comme `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"
}
```

La liste média renvoie un instantané au moment de la consultation et n'actualise pas activement le statut des tâches.

***

<h2 id="unified-tasks">
  Comment utiliser l'API de tâches unifiées ?
</h2>

L'API de tâches unifiées accepte les filtres suivants :

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

| Paramètre | Type    | Valeur par défaut | Description                                                    |
| --------- | ------- | ----------------- | -------------------------------------------------------------- |
| `object`  | string  | -                 | `llm`, `image` ou `video`                                      |
| `status`  | string  | -                 | `pending`, `in_progress`, `completed`, `failed` ou `cancelled` |
| `model`   | string  | -                 | Filtre exact par nom de modèle                                 |
| `after`   | string  | -                 | Curseur de pagination                                          |
| `limit`   | integer | `20`              | Plage de `1` à `100`                                           |
| `order`   | string  | `desc`            | `asc` ou `desc`                                                |

Détail d'une tâche unifiée :

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

Élément `output` unifié d'une tâche média :

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

Pour un résultat unique, demandez directement `/ai/v1/tasks/{id}/content`. Pour plusieurs résultats, demandez `/ai/v1/tasks/{id}/content/{result_id}`. Sans ID de résultat, l'API renvoie `400 result_id_required`.

<Note>
  Les API de liste, de détail et de contenu des tâches unifiées sont isolées
  selon le Bearer Token qui a créé la tâche. Une autre clé API du même compte ne
  peut pas lire cette tâche.
</Note>

***

<h2 id="get-task-results">
  Télécharger les résultats média
</h2>

<h3 id="multiple-artifacts">
  Télécharger des images
</h3>

Une fois l'image terminée, demandez chaque `output[].content_url` de l'objet de tâche média :

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

Le chemin de téléchargement média d'une image est `/ai/v1/images/{id}/content/{result_id}`. L'objet de tâche média n'expose pas `result_id` séparément. Le client peut utiliser directement `content_url`.

Lorsque `b64_json` n'est pas vide, décodez directement ce champ en Base64.

<h2 id="webhooks">
  Utiliser un Webhook
</h2>

Les images asynchrones et les vidéos prennent en charge les Webhooks au niveau de la tâche :

```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` est limité à 512 caractères et ne peut pas pointer vers la machine locale, un réseau privé ou une autre adresse restreinte. Lorsque `webhook_events_filter` est omis, la plateforme envoie `completed`, `failed` et `cancelled`. Lorsqu'il est fourni, le tableau ne peut pas être vide ni contenir de doublons, et doit être utilisé avec `webhook_url`.

Si `webhook_url` est absent de la requête, les images asynchrones et les vidéos tentent d'utiliser l'adresse de rappel par défaut du compte. Une adresse par défaut non valide est ignorée et ne bloque pas la création de la tâche.

<h3 id="webhook-payload">
  Requête de rappel
</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"
      }
    ]
  }
}
```

| Champ                | Description                                                   |
| -------------------- | ------------------------------------------------------------- |
| `event_id`           | ID de l'événement de rappel, utilisé pour la déduplication    |
| `event_type`         | `completed`, `failed` ou `cancelled`                          |
| `created_at`         | Date de l'événement au format RFC 3339                        |
| `data.task_id`       | ID de tâche de la plateforme                                  |
| `data.status`        | Statut final actuel                                           |
| `data.model`         | Nom du modèle                                                 |
| `data.results[].url` | Adresse de téléchargement éventuellement renvoyée à la fin    |
| `data.error`         | Informations d'erreur éventuellement renvoyées en cas d'échec |

`results` n'apparaît que si le résultat a été archivé. Le téléchargement nécessite toujours un Bearer Token.

<h3 id="webhook-retry">
  Nouvelles tentatives et déduplication
</h3>

La plateforme utilise une livraison au moins une fois. Un même événement peut être envoyé plusieurs fois :

* HTTP `2xx` indique une réception réussie.
* HTTP `5xx`, une erreur réseau ou un délai dépassé déclenchent une nouvelle tentative.
* HTTP `3xx` et `4xx` ne déclenchent pas de nouvelle tentative.
* Six livraisons au maximum, avec des intervalles successifs de 1, 4, 16, 64 et 256 secondes.

Le destinataire doit enregistrer `event_id` et renvoyer immédiatement `2xx` s'il reçoit à nouveau le même événement.

<Warning>
  Le Webhook au niveau de la tâche n'a pas de clé de signature indépendante. Si
  une vérification de signature est nécessaire, configurez un abonnement Webhook
  au niveau du compte et conservez la consultation du détail de la tâche comme
  méthode de confirmation du résultat.
</Warning>

***

<h2 id="error-codes">
  Réponses et codes d'erreur
</h2>

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

| Statut HTTP | Code d'erreur                   | Description                                                                                                  |
| ----------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| 400         | `invalid_request`               | Type ou valeur de paramètre incorrect                                                                        |
| 400         | `result_id_required`            | La tâche unifiée contient plusieurs résultats, mais aucun `result_id` n'a été précisé pour le téléchargement |
| 400         | `webhook_invalid`               | URL de Webhook non valide ou filtre fourni sans URL                                                          |
| 400         | `webhook_events_filter_invalid` | Liste d'événements Webhook non valide                                                                        |
| 401         | `authentication_failed`         | Clé API absente ou non valide                                                                                |
| 403         | `async_not_enabled`             | Tâches asynchrones non activées pour le compte                                                               |
| 404         | `task_not_found`                | Tâche ou curseur de pagination inexistant                                                                    |
| 404         | `result_not_found`              | Résultat inexistant ou actuellement indisponible au téléchargement                                           |
| 410         | `artifact_expired`              | Résultat expiré                                                                                              |
| 413         | `request_too_large`             | Corps de requête supérieur à 32 MiB                                                                          |
| 429         | `too_many_downloads`            | Limite de téléchargements du résultat dépassée                                                               |
| 503         | `async_unavailable`             | Service d'images asynchrones temporairement indisponible                                                     |

`error.tid` est l'ID de traçage de la requête. Fournissez-le au support technique lors d'un diagnostic.

***

## Exemple complet

Ces exemples créent une tâche asynchrone, l'interrogent et enregistrent chaque image retournée.

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