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

# Quick Start: Viggle Image

> Generate PNG images from a text prompt, optionally guided by reference images.

Describe an image in text, submit it to `POST /v1/images`, poll the returned job until it's `ready`, and download the PNGs. One request generates 1–6 images with the `qwen` model; add up to 8 reference images to guide them.

Set `VIGGLE_API_KEY` and run the example below as-is — it needs no other input.

## What you need

* An API key from the [Viggle Dashboard](https://portal.viggle.ai/keys), exported as `VIGGLE_API_KEY`
* A text prompt, up to 4000 characters
* Credits: **1 credit ($0.01) per image** at `1K`, **2 credits ($0.02) per image** at `2K`. The example below generates two `1K` images for 2 credits.

## Generate, poll, and download

The script creates a job for two 16:9 images, polls every 3 seconds, and saves each image as `image_0.png`, `image_1.png`, and so on.

<CodeGroup>
  ```bash cURL theme={null}
  #!/usr/bin/env bash
  set -euo pipefail

  response=$(curl -s -X POST "https://apis.viggle.ai/v1/images" \
    -H "Authorization: Bearer $VIGGLE_API_KEY" \
    -F prompt="A watercolor fox reading a book under a maple tree" \
    -F aspect_ratio=16:9 \
    -F resolution=1K \
    -F n=2)
  image_id=$(echo "$response" | jq -r '.id')

  # A job that hasn't finished within 15 minutes fails on its own.
  for _ in $(seq 1 300); do
    job=$(curl -s "https://apis.viggle.ai/v1/images/$image_id" \
      -H "Authorization: Bearer $VIGGLE_API_KEY")
    status=$(echo "$job" | jq -r '.status')

    case "$status" in
      ready)
        i=0
        for url in $(echo "$job" | jq -r '.images[].url'); do
          curl -s -o "image_$i.png" "$url"
          echo "saved image_$i.png"
          i=$((i + 1))
        done
        exit 0
        ;;
      failed)
        echo "Image job failed: $(echo "$job" | jq -c '.error')" >&2
        exit 1
        ;;
    esac
    sleep 3
  done

  echo "Timed out waiting for image job $image_id" >&2
  exit 1
  ```

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

  const baseUrl = "https://apis.viggle.ai/v1";
  const headers = { Authorization: `Bearer ${process.env.VIGGLE_API_KEY}` };

  async function api(path, init = {}) {
    const response = await fetch(`${baseUrl}${path}`, { ...init, headers });
    const body = await response.json().catch(() => ({}));
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${JSON.stringify(body.error ?? body)}`);
    }
    return body;
  }

  const form = new FormData();
  form.append("prompt", "A watercolor fox reading a book under a maple tree");
  form.append("aspect_ratio", "16:9");
  form.append("resolution", "1K");
  form.append("n", "2");

  let job = await api("/images", { method: "POST", body: form });

  // A job that hasn't finished within 15 minutes fails on its own.
  while (job.status === "queued" || job.status === "processing") {
    await new Promise((resolve) => setTimeout(resolve, 3000));
    job = await api(`/images/${job.id}`);
  }

  if (job.status !== "ready") {
    throw new Error(`Image job failed: ${JSON.stringify(job.error)}`);
  }

  for (const [i, image] of job.images.entries()) {
    const png = await fetch(image.url);
    await writeFile(`image_${i}.png`, Buffer.from(await png.arrayBuffer()));
    console.log(`saved image_${i}.png (${image.width}x${image.height}, seed ${image.seed})`);
  }
  ```

  ```python Python theme={null}
  import os
  import time
  import requests

  base_url = "https://apis.viggle.ai/v1"
  headers = {"Authorization": f"Bearer {os.environ['VIGGLE_API_KEY']}"}

  fields = {
      "prompt": "A watercolor fox reading a book under a maple tree",
      "aspect_ratio": "16:9",
      "resolution": "1K",
      "n": "2",
  }
  # files= makes requests send multipart/form-data, which the endpoint requires.
  response = requests.post(
      f"{base_url}/images",
      headers=headers,
      files={name: (None, value) for name, value in fields.items()},
  )
  response.raise_for_status()
  job = response.json()

  # A job that hasn't finished within 15 minutes fails on its own.
  while job["status"] in ("queued", "processing"):
      time.sleep(3)
      response = requests.get(f"{base_url}/images/{job['id']}", headers=headers)
      response.raise_for_status()
      job = response.json()

  if job["status"] != "ready":
      error = job.get("error") or {}
      raise RuntimeError(f"Image job failed: {error.get('code')} {error.get('message')}")

  for i, image in enumerate(job["images"]):
      png = requests.get(image["url"])
      png.raise_for_status()
      with open(f"image_{i}.png", "wb") as f:
          f.write(png.content)
      print(f"saved image_{i}.png ({image['width']}x{image['height']}, seed {image['seed']})")
  ```

  ```typescript @viggle/sdk theme={null}
  import { Viggle } from "@viggle/sdk";

  const viggle = new Viggle({ apiKey: process.env.VIGGLE_API_KEY! });

  let job = await viggle.images.create({
    prompt: "A watercolor fox reading a book under a maple tree",
    aspectRatio: "16:9",
    resolution: "1K",
    n: 2,
  });
  while (job.status === "queued" || job.status === "processing") {
    await new Promise((resolve) => setTimeout(resolve, 3000));
    job = await viggle.images.get(job.id);
  }
  console.log(job.images.map((image) => image.url));
  ```
</CodeGroup>

The `@viggle/sdk` example requires an SDK version that includes the `images` resource.

## Response

The create request answers immediately with a `queued` job:

```json theme={null}
{
  "id": "img_b16a190d-2d44-41a0-8056-ef68183c3b9e",
  "status": "queued",
  "model": "qwen",
  "progress": 0,
  "images": [],
  "created_at": "2026-10-12T08:20:00Z",
  "completed_at": null,
  "error": null
}
```

Poll `GET /v1/images/{image_id}` until `status` is `ready` or `failed`. While `processing`, `progress` rises as each image finishes. Once `ready`, `images` lists all `n` images in request order, each with a `url`, its `width` and `height` in pixels, and the `seed` it used. In production, two `1K` images take about 17–22 seconds.

Each `url` is a signed link, valid for **1 hour** — if it expires, fetch the job again for a fresh one. Generated images are deleted **24 hours** after the job finishes, so download them before then.

## Add reference images

Upload files with `reference_image`, pass public `http(s)` URLs with `reference_image_url`, or mix both. Either field can be repeated, up to 8 reference images in total:

```bash theme={null}
curl -X POST https://apis.viggle.ai/v1/images \
  -H "Authorization: Bearer $VIGGLE_API_KEY" \
  -F prompt="The same fox, now in winter, snow falling" \
  -F aspect_ratio=1:1 \
  -F resolution=2K \
  -F reference_image=@fox.png \
  -F reference_image_url=https://example.com/style.jpg
```

Poll and download the same way. With reference images, `2K` produces a smaller size than text-only `2K` — here 1536 × 1536 instead of 2048 × 2048 — at the same price. One `2K` image with a reference image takes about 37 seconds. Each reference image can be up to 24 MiB; see [Generate Image](/v1/api-reference/images/create#reference-image-limits) for formats and limits.

## If a job fails

Read `error.code`. `SERVICE_BUSY` and `TASK_FAILED` are retryable — create a new job with the same parameters. `INVALID_REQUEST` means the model rejected the request, for example an unusable reference image; change the inputs first. A failed job is refunded in full. See [Get Image](/v1/api-reference/images/get#status-and-errors).

<CardGroup cols={2}>
  <Card title="Generate Image" icon="image" href="/v1/api-reference/images/create">
    Every field, output size, and limit.
  </Card>

  <Card title="Get Image" icon="magnifying-glass" href="/v1/api-reference/images/get">
    The full Image object, statuses, and error codes.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.