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

# Geração de imagens

> Gere imagens de forma síncrona ou assíncrona com o protocolo nativo do AIHubMix, consulte tarefas e baixe resultados.

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

## Início rápido

O endpoint nativo de imagens é síncrono por padrão. Defina o booleano `async` como `true` para criar uma tarefa em segundo plano. O exemplo usa `qwen-image-2.0`.

<CodeGroup>
  ```bash Criar tarefa 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 Resposta de criação 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 tarefa theme={null}
  curl https://aihubmix.com/ai/v1/images/{id} \
    -H "Authorization: Bearer $AIHUBMIX_API_KEY"
  ```

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

<h2 id="model-schema">
  Como descobrir modelos de mídia assíncronos e obter seus esquemas
</h2>

O fluxo de descoberta tem duas etapas. Primeiro, obtenha no catálogo público os modelos de texto para imagem ou texto para vídeo compatíveis com as APIs assíncronas. Depois, use o `model_id` do modelo para obter o esquema de solicitação de seus endpoints.

<h3 id="async-media-model-list">
  Listar modelos compatíveis com as APIs assíncronas
</h3>

O catálogo de modelos usa a mesma fonte de dados do Playground. Use `type=image_generation` para modelos de texto para imagem e `type=video` para modelos de texto para vídeo. Com `schema_checked=true`, a lista fica limitada a modelos cujo esquema de solicitação foi publicado e revisado.

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

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

As duas solicitações usam o mesmo endpoint. O filtro `type` aceita atualmente um único valor, então solicite cada tipo de modelo separadamente.

A resposta tem o formato `{success, message, data}`. Os campos de `data` relevantes para integrações de mídia assíncrona são os seguintes.

| Campo                     | Descrição                                                                |
| ------------------------- | ------------------------------------------------------------------------ |
| `data[].model_id`         | ID do modelo usado na geração e nas solicitações posteriores de esquema  |
| `data[].model_name`       | Nome de exibição do modelo                                               |
| `data[].types`            | Tipos separados por vírgulas, podendo incluir `image_generation` e `llm` |
| `data[].input_modalities` | Modalidades de entrada separadas por vírgulas                            |
| `data[].schema_checked`   | `true` quando o esquema de solicitação foi publicado e 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">
  Obter o esquema de solicitação de um modelo
</h3>

Os campos, enumerações e intervalos numéricos aceitos podem variar conforme o modelo. Antes de enviar uma solicitação de imagem ou vídeo, use o endpoint público abaixo para obter os endpoints disponíveis e os esquemas JSON de solicitação do modelo selecionado.

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

# Modelo de texto para vídeo
curl "https://aihubmix.com/call/schema/models/wan2.6-t2v/endpoints"
```

O valor de `modality` na resposta é `image` ou `video`. Cada item do array `endpoints` descreve um protocolo de chamada disponível.

| Campo                        | Descrição                                                                                                               |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `default_endpoint`           | Identificador do endpoint padrão do modelo                                                                              |
| `endpoints[].endpoint`       | Identificador do endpoint                                                                                               |
| `endpoints[].method`         | Método HTTP, como `POST`                                                                                                |
| `endpoints[].path`           | Caminho da solicitação                                                                                                  |
| `endpoints[].content_types`  | Tipos de conteúdo aceitos pelo endpoint                                                                                 |
| `endpoints[].lifecycle`      | Modo síncrono ou assíncrono, caminho de consulta e valores de status                                                    |
| `endpoints[].request.schema` | Esquema JSON completo da solicitação para este modelo e endpoint, incluindo campos obrigatórios e restrições de valores |

Um modelo pode retornar endpoints `/ai/v1` e endpoints `/v1` compatíveis com OpenAI. Os endpoints compatíveis com OpenAI podem ainda não oferecer suporte aos modelos mais recentes, portanto dê preferência aos endpoints `/ai/v1`. Para as APIs de tarefas assíncronas, selecione o item cujo `path` seja `/ai/v1/images/generations` ou `/ai/v1/videos` e use seu `request.schema`. Não dependa da posição do item no array `endpoints`.

Os comandos abaixo extraem diretamente o esquema de solicitação de cada endpoint de tarefa assíncrona.

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

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

O endpoint retorna `404 model_not_found` quando o modelo não existe ou não possui endpoints detectáveis. Ele retorna `500 endpoints_unavailable` quando os dados dos endpoints estão temporariamente indisponíveis.

***

<h2 id="create-async-task">
  Como criar uma tarefa de imagem
</h2>

<h3 id="create-sync-image">
  Imagem síncrona
</h3>

Quando `async` é omitido ou definido como `false`, a API aguarda a conclusão da geração e retorna o objeto de tarefa:

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

As tarefas síncronas de imagem também são registradas. Se a conexão do cliente for interrompida ou a resposta de criação for perdida, use `GET /ai/v1/images` para localizar a tarefa.

<h3 id="create-async-image">
  Imagem assíncrona
</h3>

Quando `async` é definido como o booleano `true`, a API retorna imediatamente o objeto de tarefa e a geração continua em 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
  }'
```

Consulte a tarefa assíncrona de imagem com `GET /ai/v1/images/{id}`. Após a conclusão, solicite diretamente o `content_url` de cada item de `output`. Essa URL já contém o `result_id` da imagem correspondente.

<Note>
  O campo `async` de uma requisição de imagem deve ser booleano. `webhook_url` e
  `webhook_events_filter` só podem ser usados com `async: true`.
</Note>

<h3 id="image-parameters">
  Campos padrão de imagem
</h3>

| Campo                   | Tipo          | Obrigatório | Descrição                                                          |
| ----------------------- | ------------- | ----------- | ------------------------------------------------------------------ |
| `model`                 | string        | Sim         | Nome do modelo                                                     |
| `prompt`                | string        | Sim         | Descrição da imagem, não pode estar vazia                          |
| `n`                     | integer/null  | Não         | Quantidade de imagens, mínimo `1`, padrão `1`                      |
| `size`                  | string/null   | Não         | `{width}x{height}`, por exemplo `1024x1024`                        |
| `aspect_ratio`          | string/null   | Não         | Proporção da imagem, mutuamente exclusiva com `size`               |
| `seed`                  | integer/null  | Não         | Semente aleatória                                                  |
| `negative_prompt`       | string/null   | Não         | Prompt negativo                                                    |
| `image`                 | string/object | Não         | Uma imagem de entrada como URL, Data URI, Base64 ou objeto `{url}` |
| `images`                | array/null    | Não         | Várias imagens de entrada                                          |
| `mask`                  | string/object | Não         | Máscara para edição de imagem                                      |
| `output_format`         | string/null   | Não         | `png`, `jpeg` ou `webp`, padrão `png`                              |
| `response_format`       | string/null   | Não         | `url` ou `b64_json`, padrão `url`                                  |
| `async`                 | boolean       | Não         | Executa de forma assíncrona quando definido como `true`            |
| `webhook_url`           | string        | Não         | Endereço HTTPS de callback, máximo de 512 caracteres               |
| `webhook_events_filter` | string\[]     | Não         | Subconjunto não vazio de `completed`, `failed`, `cancelled`        |
| `extra`                 | object/null   | Não         | Parâmetros estendidos específicos do modelo                        |

***

<h2 id="task-object">
  Objeto de tarefa de mídia
</h2>

As APIs específicas de imagem e vídeo retornam a seguinte estrutura:

```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         | Descrição                                                                  |
| -------------- | ------------ | -------------------------------------------------------------------------- |
| `id`           | string       | ID de tarefa da plataforma                                                 |
| `object`       | string       | `image` ou `video`                                                         |
| `model`        | string       | Nome do modelo                                                             |
| `status`       | string       | Status atual da tarefa                                                     |
| `output`       | array        | Resultados gerados, ou um array vazio quando ainda não há resultados       |
| `error`        | object/null  | Informações da falha, podendo conter `code`, `message` e `upstream_detail` |
| `created_at`   | integer      | Data de criação em segundos Unix                                           |
| `completed_at` | integer/null | Data do status final em segundos Unix                                      |
| `expires_at`   | null         | As APIs específicas de mídia atualmente retornam `null`                    |

Item de `output` de mídia:

| Campo         | Tipo        | Descrição                                |
| ------------- | ----------- | ---------------------------------------- |
| `index`       | integer     | Ordem do resultado, começando em `0`     |
| `type`        | string      | Atualmente é `file`                      |
| `content_url` | string/null | Endereço de download do resultado        |
| `b64_json`    | string/null | Resultado de imagem codificado em Base64 |

<h3 id="task-status">
  Descrição dos status
</h3>

| Status        | Final | Descrição                                          |
| ------------- | ----- | -------------------------------------------------- |
| `pending`     | Não   | A plataforma recebeu a tarefa e aguarda a execução |
| `in_progress` | Não   | A tarefa está em execução                          |
| `completed`   | Sim   | Tarefa concluída, `output` disponível              |
| `failed`      | Sim   | A tarefa falhou, consulte `error`                  |
| `cancelled`   | Sim   | A tarefa foi cancelada                             |

O cliente pode consultar a tarefa a cada 15 segundos até que o status seja `completed`, `failed` ou `cancelled`. Esse intervalo de 15 segundos é uma recomendação de polling do cliente, não uma limitação do protocolo do servidor.

***

<h2 id="query-tasks">
  Como consultar tarefas de mídia
</h2>

<h3 id="query-task-detail">
  Consultar detalhes da mídia
</h3>

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

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

As APIs de detalhes de mídia podem retornar um status atualizado. Por isso, use a API de detalhes da imagem ou do vídeo correspondente para fazer polling.

<h3 id="query-task-list">
  Consultar a lista de mídia
</h3>

Se a resposta de criação for perdida, localize o ID da tarefa na lista da mídia correspondente:

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

| Parâmetro | Tipo    | Padrão | Descrição                                                           |
| --------- | ------- | ------ | ------------------------------------------------------------------- |
| `after`   | string  | -      | Cursor de paginação, use o `next_after` da página anterior          |
| `limit`   | integer | `20`   | Quantidade por página, máximo `100`                                 |
| `order`   | string  | `desc` | `asc` para ordem crescente; outros valores são tratados 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"
}
```

A lista de mídia retorna um instantâneo da tarefa no momento da consulta e não atualiza ativamente o status.

***

<h2 id="unified-tasks">
  Como usar a API de tarefas unificadas
</h2>

A API de tarefas unificadas aceita os seguintes 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    | Padrão | Descrição                                                      |
| --------- | ------- | ------ | -------------------------------------------------------------- |
| `object`  | string  | -      | `llm`, `image` ou `video`                                      |
| `status`  | string  | -      | `pending`, `in_progress`, `completed`, `failed` ou `cancelled` |
| `model`   | string  | -      | Filtro exato pelo nome do modelo                               |
| `after`   | string  | -      | Cursor de paginação                                            |
| `limit`   | integer | `20`   | Intervalo de `1` a `100`                                       |
| `order`   | string  | `desc` | `asc` ou `desc`                                                |

Detalhes da tarefa unificada:

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

Item de `output` unificado de uma tarefa de 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"
}
```

Para um único resultado, solicite diretamente `/ai/v1/tasks/{id}/content`. Para tarefas com vários resultados, solicite `/ai/v1/tasks/{id}/content/{result_id}`. Sem o ID do resultado, a API retorna `400 result_id_required`.

<Note>
  As APIs de lista, detalhes e conteúdo das tarefas unificadas são isoladas pelo
  Bearer Token usado para criar a tarefa. Outra API Key da mesma conta não pode
  ler essa tarefa.
</Note>

***

<h2 id="get-task-results">
  Como baixar resultados de mídia
</h2>

<h3 id="multiple-artifacts">
  Baixar imagens
</h3>

Quando a imagem estiver concluída, solicite cada `output[].content_url` do objeto de tarefa de mídia:

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

O caminho de download de mídia da imagem é `/ai/v1/images/{id}/content/{result_id}`. O objeto de tarefa de mídia não expõe `result_id` separadamente. O cliente pode usar `content_url` diretamente.

Quando `b64_json` não estiver vazio, decodifique esse campo diretamente em Base64.

<h2 id="webhooks">
  Como usar Webhook
</h2>

Imagens assíncronas e vídeos aceitam Webhooks no nível da tarefa:

```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` aceita no máximo 512 caracteres e não pode apontar para a máquina local, uma rede privada ou outro endereço restrito. Quando `webhook_events_filter` é omitido, a plataforma envia `completed`, `failed` e `cancelled`. Quando informado explicitamente, o array não pode estar vazio nem conter duplicatas e deve ser usado com `webhook_url`.

Quando `webhook_url` não é enviado na requisição, imagens assíncronas e vídeos tentam usar o endereço de callback padrão configurado na conta. Um endereço padrão inválido é ignorado e não impede a criação da tarefa.

<h3 id="webhook-payload">
  Requisição de callback
</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                | Descrição                                                     |
| -------------------- | ------------------------------------------------------------- |
| `event_id`           | ID do evento de callback, usado para desduplicação            |
| `event_type`         | `completed`, `failed` ou `cancelled`                          |
| `created_at`         | Data e hora do evento no formato RFC 3339                     |
| `data.task_id`       | ID de tarefa da plataforma                                    |
| `data.status`        | Status final atual                                            |
| `data.model`         | Nome do modelo                                                |
| `data.results[].url` | Endereço de download que pode ser retornado na conclusão      |
| `data.error`         | Informações de erro que podem ser retornadas em caso de falha |

`results` só aparece quando o resultado foi arquivado. O download ainda exige um Bearer Token.

<h3 id="webhook-retry">
  Novas tentativas e desduplicação
</h3>

A plataforma usa entrega pelo menos uma vez. O mesmo evento pode ser enviado mais de uma vez:

* HTTP `2xx` indica recebimento bem-sucedido.
* HTTP `5xx`, erros de rede ou timeout acionam uma nova tentativa.
* HTTP `3xx` e `4xx` não acionam novas tentativas.
* São feitas no máximo 6 entregas, com intervalos de 1, 4, 16, 64 e 256 segundos.

O destinatário deve salvar `event_id` e retornar imediatamente `2xx` ao receber novamente o mesmo evento.

<Warning>
  O Webhook no nível da tarefa não possui uma chave de assinatura independente.
  Se precisar verificar a assinatura, configure uma assinatura de Webhook no
  nível da conta e mantenha a consulta dos detalhes da tarefa como forma de
  confirmar o resultado.
</Warning>

***

<h2 id="error-codes">
  Respostas e códigos de erro
</h2>

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

| Status HTTP | Código de erro                  | Descrição                                                                                     |
| ----------- | ------------------------------- | --------------------------------------------------------------------------------------------- |
| 400         | `invalid_request`               | Tipo ou valor de parâmetro incorreto                                                          |
| 400         | `result_id_required`            | A tarefa unificada possui vários resultados, mas nenhum `result_id` foi informado no download |
| 400         | `webhook_invalid`               | URL de Webhook inválida ou filtro sem URL                                                     |
| 400         | `webhook_events_filter_invalid` | Lista de eventos de Webhook inválida                                                          |
| 401         | `authentication_failed`         | API Key ausente ou inválida                                                                   |
| 403         | `async_not_enabled`             | Tarefas assíncronas não ativadas para a conta                                                 |
| 404         | `task_not_found`                | Tarefa ou cursor de paginação inexistente                                                     |
| 404         | `result_not_found`              | Resultado inexistente ou indisponível para download no momento                                |
| 410         | `artifact_expired`              | Resultado expirado                                                                            |
| 413         | `request_too_large`             | Corpo da requisição maior que 32 MiB                                                          |
| 429         | `too_many_downloads`            | Limite de downloads do resultado excedido                                                     |
| 503         | `async_unavailable`             | Serviço de imagens assíncronas temporariamente indisponível                                   |

`error.tid` é o ID de rastreamento da requisição. Informe-o ao suporte técnico durante a investigação.

***

## Exemplo completo

Estes exemplos criam uma tarefa assíncrona, consultam seu estado e salvam todas as imagens retornadas.

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