curl --request GET \
--url https://apis.viggle.ai/v1/images/{image_id} \
--header 'Authorization: Bearer <token>'import requests
url = "https://apis.viggle.ai/v1/images/{image_id}"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://apis.viggle.ai/v1/images/{image_id}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://apis.viggle.ai/v1/images/{image_id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://apis.viggle.ai/v1/images/{image_id}"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://apis.viggle.ai/v1/images/{image_id}")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://apis.viggle.ai/v1/images/{image_id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"id": "<string>",
"status": "queued",
"model": "<string>",
"progress": 50,
"images": [
{
"url": "<string>",
"width": 2,
"height": 2,
"seed": 1
}
],
"created_at": "2023-11-07T05:31:56Z",
"completed_at": "2023-11-07T05:31:56Z",
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}Get Image
Retrieve the current state of an image generation job, and its PNG download URLs once ready.
curl --request GET \
--url https://apis.viggle.ai/v1/images/{image_id} \
--header 'Authorization: Bearer <token>'import requests
url = "https://apis.viggle.ai/v1/images/{image_id}"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://apis.viggle.ai/v1/images/{image_id}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://apis.viggle.ai/v1/images/{image_id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://apis.viggle.ai/v1/images/{image_id}"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://apis.viggle.ai/v1/images/{image_id}")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://apis.viggle.ai/v1/images/{image_id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"id": "<string>",
"status": "queued",
"model": "<string>",
"progress": 50,
"images": [
{
"url": "<string>",
"width": 2,
"height": 2,
"seed": 1
}
],
"created_at": "2023-11-07T05:31:56Z",
"completed_at": "2023-11-07T05:31:56Z",
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}{
"error": {
"code": "UNAUTHENTICATED",
"message": "<string>",
"retryable": true,
"request_id": "<string>",
"details": {},
"remediation": {
"action": "<string>",
"retry_after_ms": 1
}
}
}GET /v1/images/{image_id} returns the current state of a job created by Generate Image — the same Image object the create request answered with. Poll every 2–3 seconds until status is ready or failed, then download each PNG from images[].url.
You can only read jobs created with the same API key. An unknown ID, or one created by a different key, answers 404 NOT_FOUND — the two cases are deliberately indistinguishable.
Request parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
image_id | string | Yes | Full image job ID returned by Generate Image, img_ prefix. |
Response parameters
Returns200 OK with an Image object.
| Field | Type | Description |
|---|---|---|
id | string | Image job ID, img_ prefix. |
status | string | queued, processing, ready, or failed — see Status and errors. |
model | string | Model used; currently qwen. |
progress | integer or null | Share of the requested images completed: 0 while queued, 0–99 while processing, 100 when ready, null when failed. |
images | array | Always empty before ready. Once ready, all n images in request order. |
images[].url | string or null | Signed download URL of the PNG, valid for 1 hour. Fetch the job again for a fresh one. null once the file has been deleted at the end of the retention period. |
images[].width / images[].height | integer | Actual size in pixels — see Output sizes. |
images[].seed | integer | Seed this image was actually generated with. |
created_at | string or null | Creation time, RFC 3339. |
completed_at | string or null | End time; set once the job is ready or failed. |
error | object or null | Failure details, only when status is failed. |
Ready example
Signature query parameters shortened:{
"id": "img_b16a190d-2d44-41a0-8056-ef68183c3b9e",
"status": "ready",
"model": "qwen",
"progress": 100,
"images": [
{"url": "https://storage.googleapis.com/viggle/image-generation/b16a190d-2d44-41a0-8056-ef68183c3b9e/0.png?X-Goog-Signature=...", "width": 1376, "height": 768, "seed": 0},
{"url": "https://storage.googleapis.com/viggle/image-generation/b16a190d-2d44-41a0-8056-ef68183c3b9e/1.png?X-Goog-Signature=...", "width": 1376, "height": 768, "seed": 1}
],
"created_at": "2026-10-12T08:20:00Z",
"completed_at": "2026-10-12T08:20:22Z",
"error": null
}
Failed example
{
"id": "img_b16a190d-2d44-41a0-8056-ef68183c3b9e",
"status": "failed",
"model": "qwen",
"progress": null,
"images": [],
"created_at": "2026-10-12T08:20:00Z",
"completed_at": "2026-10-12T08:35:00Z",
"error": {
"code": "SERVICE_BUSY",
"message": "The image service is busy; try again later.",
"retryable": true,
"request_id": null,
"details": {},
"remediation": { "action": "retry", "retry_after_ms": null }
}
}
Status and errors
A job only moves forward:queued → processing → ready or failed. It is never cancelled. A job either produces all n images or fails as a whole — it never returns a partial set.
status | Meaning |
|---|---|
queued | Accepted and waiting for a GPU. At peak times a job can stay here for a while. |
processing | Generating, one image at a time; progress rises as each image finishes. |
ready | All images are done and images is filled in. |
failed | Failed; see error. |
error.code on a failed job:
code | retryable | When | What to do |
|---|---|---|---|
SERVICE_BUSY | true | The generation service is busy or temporarily unavailable, or the job didn’t finish within 15 minutes. | Create a new job with the same parameters later. |
TASK_FAILED | true | Any other generation or upload failure. | Create a new job. If it keeps happening, contact us with the job id. |
INVALID_REQUEST | false | The model rejected the request — for example, a reference image it can’t use. | Change the parameters or reference images, then try again. |
| HTTP | code | Meaning |
|---|---|---|
401 | UNAUTHENTICATED | Missing or invalid API key. |
404 | NOT_FOUND | The job doesn’t exist, or belongs to a different key. |
429 | RATE_LIMITED | Too many requests; retry later. |
500 | INTERNAL_ERROR | Internal server error. Retryable. |
503 | SERVICE_BUSY | Service temporarily unavailable. Retryable. |
error, wrapped as {"error": {...}}. See Errors and recovery.
Timing
Measured in production on 2026-10-12: two1K images took about 17–22 seconds; one 2K image with a reference image took about 37 seconds. Images are generated one at a time, so a larger n takes longer, and at peak times a job first waits in queued. A job that hasn’t finished within 15 minutes fails with SERVICE_BUSY.
Data retention
- Generated images are deleted 24 hours after the job finishes — download them before then. Afterwards this endpoint still returns the job, still
ready, but everyimages[].urlisnull. - Reference images you upload, or that Viggle downloads from a
reference_image_url, are deleted within 24 hours. - Customer data is never used to train models.
Examples
curl https://apis.viggle.ai/v1/images/img_b16a190d-2d44-41a0-8056-ef68183c3b9e \
-H "Authorization: Bearer $VIGGLE_API_KEY"
import os, requests
response = requests.get(
"https://apis.viggle.ai/v1/images/img_b16a190d-2d44-41a0-8056-ef68183c3b9e",
headers={"Authorization": f"Bearer {os.environ['VIGGLE_API_KEY']}"},
)
response.raise_for_status()
image = response.json()
const response = await fetch("https://apis.viggle.ai/v1/images/img_b16a190d-2d44-41a0-8056-ef68183c3b9e", {
headers: { Authorization: `Bearer ${process.env.VIGGLE_API_KEY}` },
});
const image = await response.json();
req, _ := http.NewRequest("GET", "https://apis.viggle.ai/v1/images/img_b16a190d-2d44-41a0-8056-ef68183c3b9e", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("VIGGLE_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
const image = await viggle.images.get("img_b16a190d-2d44-41a0-8056-ef68183c3b9e");
console.log(image.status, image.images.map((generated) => generated.url));
Quick Start: Viggle Image
Authorizations
Server-side SDK clients use a project API key. Remote MCP clients use an OAuth access token. Never expose a project API key in browser code.
Headers
Optional caller-supplied correlation ID, up to 128 characters. The service returns the effective value in the response header for tracing and support.
1 - 128Optional source-channel label, up to 128 characters, used to attribute traffic to an SDK, integration, product surface, or internal workflow.
1 - 128Path Parameters
Public image job ID (img_ prefix) returned by POST /v1/images. It must have been created with the calling API key.
1Response
Current image generation state.
An image generation job (img_ prefix), returned by both
POST /v1/images and GET /v1/images/{image_id}. It only moves
forward — queued → processing → ready or failed — and is
never cancelled. images stays empty until the job is ready,
then lists every generated image in request order. A job either
produces all n images or fails; it never returns a partial set.
Public image job ID, img_ prefix.
1Lifecycle state. ready and failed are terminal.
queued, processing, ready, failed Model used for generation; currently always qwen.
Share of the requested images generated so far — 0 while queued, 0–99 while processing, 100 when ready, null when failed.
0 <= x <= 100Empty until ready, then all n generated images in request order.
Show child attributes
Show child attributes
Creation timestamp, RFC 3339.
Terminal-state timestamp; set once the job is ready or failed, null before that.
Failure details, only when status is failed; otherwise null.
code is SERVICE_BUSY (retryable — the generation service is
busy or unavailable, or the job did not finish within 15
minutes), TASK_FAILED (retryable — any other generation or
upload failure), or INVALID_REQUEST (not retryable — the model
rejected the request, for example an unusable reference image).
Show child attributes
Show child attributes

