Image generation

Introduction

Image generation is asynchronous. Quote first, submit the task, then poll the task and read the image links.

Unified API (recommended)

New integrations call POST /v1/generations with type=image. The quote/submit endpoints below remain supported for compatibility.

BASH
curl -X POST https://api.anycap.io/v1/generations \  -H "Authorization: Bearer $ANYCAP_API_KEY" \  -H "Content-Type: application/json" \  -d '{    "type": "image",    "model": "<model from GET /v1/generations/models?type=image>",    "input": { "prompt": "a product photo on a white background" },    "parameters": { "size": "1:1", "n": 1 }  }'
JSON
{  "type": "image",  "model": "gpt-image-2.6-flare",  "input": {    "prompt": "A paper lantern gently swaying, seamless loop"  },  "parameters": {    "output_format": "gif",    "size": "512x512",    "frame_count": 9,    "frame_duration_ms": 200,    "quality": "low",    "n": 1  }}
FieldMeaning
input.prompt
Image instruction; max_prompt_bytes is a UTF-8 byte limit, not a character count.
Idempotency-Key / idempotency_key
For POST /v1/generations: optional stable opaque key of at most 255 bytes; the header takes precedence over the body. The capability execution endpoint below uses the JSON body field.
parameters.size / resolution / quality
Choose an exact priced_combinations entry. 2.5/2.6 Flare/Sunburst use lowercase 1k/2k/4k and auto/low/medium/high/xhigh/max; Ext uses uppercase 1K/2K/4K and quality=flare/sunburst.
parameters.n
1 through the discovered n_max; for GIF this is the number of animations.
parameters.frame_count / frame_duration_ms
2.6 GIF only: 2–16 frames (default 9), at least 20 ms per frame (default 200; rendered in 10 ms units). Set size to single-frame pixels or auto=512x512, never a ratio. 512x512 supports all frame counts; 768x768 supports up to 12. Reference images are supported; masks, markup, compression and transparent background are not. The complete sheet must fit within 3840 per side, 8,294,400 pixels and 3:1. Reservations use a published specification enclosing the entire sheet, and final charges use actual tokens. Reference inputs add an estimated 1536 tokens each; n charges animations, not individual frames.
parameters.output_format
PNG/JPEG/WebP for 2.5/2.6 Flare/Sunburst; 2.6 also supports gif. Ext does not accept output options.
input.image_urls
Up to 16 public HTTP(S) reference image links, combined with reference_file_ids. 2.6 also accepts validated PNG/JPEG/GIF Data URLs within the total 64 KiB request limit.
input.reference_file_ids
Owned temporary file UUIDs uploaded in the console through session-only POST /api/v1/files. Protocol-native POST /files handles are not these UUIDs. For external reference inputs, image_urls can be used directly.
input / parameters
The model fields shown by discovery (for example size, quality and n); they are merged into the model native request.
model
From GET /v1/generations/models?type=image.
Image jobs return 202 with job; poll GET /v1/generations/{id} and use ?result=true for the final links. Image jobs are not cancellable. Failed image jobs keep error_code and may include error_message with sanitized failure details (historical messages may be truncated).

A failed image job can include the following fields. error_message is optional and omitted when no detail was saved; it is sanitized and may be truncated. Use error_code for program logic and error_message for explanation.

Failed job example

JSON
{  "state": "failed",  "error_code": "upstream_call_failed",  "error_message": "Your request did not pass content safety review.",  "images": [],  "billing_status": "released",  "charged_microcredits": "0"}
StepMethod and pathSuccess
List models this key may callGET /v1/images/models200
Quote, without upstream generationPOST /api/v1/capabilities/image.generate/quotes201
Submit asynchronous taskPOST /api/v1/capabilities/image.generate/executions202
Read owned taskGET /api/v1/image-executions/{id}200
Fetch completed image URLsGET /api/v1/image-executions/{id}?result=true200
JSON
{  "native_request": {    "model": "<exact-source-model>",    "prompt": "A red apple on a white background",    "size": "1:1",    "resolution": "1k",    "quality": "medium",    "n": 1  }}
  • Use the model IDs returned by discovery. For 2.6, set model to gpt-image-2.6-flare or gpt-image-2.6-sunburst; use the same endpoints and native_request fields. The 2.5/2.6 Flare and Sunburst models accept six quality tiers (auto/low/medium/high/xhigh/max); Ext uses quality=flare/sunburst, with version as a deprecated alias. n is 1–4.
  • Choose a model from GET /v1/images/models, then take one exact priced_combinations entry; never build an unpriced combination. Omit resource_id: the platform resolves the lowest customer-priced enabled and authorized source that serves the exact specification, pins it into the quote, and execution inherits it from the quote.
  • All models use the same quality field in quotes, estimates and native requests; for Ext its values are flare/sunburst.
  • In GET /v1/images/models, billing_mode=tokens identifies token settlement; priced_combinations[].credits is an output-only estimate, not a fixed final charge. For free estimates, image.prompt_bytes is the UTF-8 prompt byte count and image.reference_images is the combined URL/upload reference count; generation still takes the original prompt and image_urls / reference_file_ids. Auto reserves at the same specification's max quality. Settlement uses actual usage; estimates are not charge caps. Higher actual charges debit the difference from available balance; lower charges release the unused hold. Missing usage or insufficient balance returns pending. 2.6 supports png/jpeg/webp/gif.
Submit the same quote body plus top-level quote_id and idempotency_key, in JSON and not headers; no resource_id is needed because execution inherits the source pinned by the quote. A quote expires five minutes after it is issued; respect expires_at.
Full image generation document →

Python: discover, quote, submit and resume

PYTHON
# Python 3 standard library; save as anycap_image.py and run on your server.import json, os, time, uuidfrom pathlib import Pathfrom urllib.request import Request, urlopenfrom urllib.error import HTTPError, URLError base = os.environ["ANYCAP_API_BASE"].rstrip("/")key = os.environ["ANYCAP_API_KEY"]state_file = Path(os.environ.get("ANYCAP_IMAGE_STATE_FILE", "anycap-image-job.json")) def call(method, path, body=None):    data = None if body is None else json.dumps(body).encode()    req = Request(base + path, data=data, method=method, headers={        "Authorization": "Bearer " + key, "Content-Type": "application/json"    })    with urlopen(req, timeout=30) as response:        return json.load(response) def poll(job_id):    for _ in range(120):        try:            job = call("GET", "/api/v1/image-executions/" + job_id)        except HTTPError as error:            if error.code == 404:                raise RuntimeError("Task not visible; retain state file and contact support. Do not resubmit.") from None            if error.code not in (429, 500, 502, 503, 504):                raise RuntimeError("Task query rejected: HTTP " + str(error.code)) from None            time.sleep(15)            continue        except (URLError, TimeoutError):            time.sleep(15)            continue        if job["state"] in ("completed", "failed", "pending"):            if job["state"] == "completed":                job = call("GET", "/api/v1/image-executions/" + job_id + "?result=true")            print(json.dumps(job))            return        time.sleep(15)    print(json.dumps({"id": job_id, "message": "Polling deadline; rerun to resume GET only."})) # An existing state file always resumes reads, including after a lost POST response.if state_file.exists():    saved = json.loads(state_file.read_text())    if saved["api_base"] != base:        raise RuntimeError("State belongs to another API origin")    poll(saved["id"])else:    models = call("GET", "/v1/images/models")["items"]    selected = os.environ.get("ANYCAP_IMAGE_MODEL")    if not selected:        print(json.dumps({"models": models}))        raise SystemExit("Set ANYCAP_IMAGE_MODEL and an exact priced combination; see the catalog above.")    model = next((m for m in models if m["model"] == selected), None)    if model is None:        raise RuntimeError("Selected model is unavailable or not authorized")    params = {k: os.environ["ANYCAP_IMAGE_" + k.upper()] for k in ("size", "resolution", "quality")}    axis = "quality"    if not any(c["size"] == params["size"] and c["resolution"] == params["resolution"] and c.get(axis, "") == params["quality"] for c in model["priced_combinations"]):        raise RuntimeError("Select an exact priced combination from the model catalog")    n = int(os.environ.get("ANYCAP_IMAGE_N", "1"))    native = {"model": selected, "prompt": os.environ["ANYCAP_IMAGE_PROMPT"], "size": params["size"], "resolution": params["resolution"], "n": n}    native[axis] = params["quality"]    body = {"native_request": native}    quote = call("POST", "/api/v1/capabilities/image.generate/quotes", body)    print(json.dumps({"quote": quote}))    limit = int(os.environ["ANYCAP_MAX_MICROCREDITS"])    if limit <= 0 or int(quote["amount_microcredits"]) > limit:        raise RuntimeError("Quote exceeds authorized budget")    if os.environ.get("ANYCAP_ALLOW_PAID_GENERATION") != "yes":        raise SystemExit("No generation submitted. Set ANYCAP_ALLOW_PAID_GENERATION=yes only after authorization.")    job_id = str(uuid.uuid4())    submission = dict(body, quote_id=quote["id"], idempotency_key=job_id)    # Exclusive private file creation; flush to disk BEFORE the billable POST.    fd = os.open(state_file, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)    with os.fdopen(fd, "w") as saved:        json.dump({"api_base": base, "id": job_id, "submission": submission}, saved)        saved.flush()        os.fsync(saved.fileno())    print(json.dumps({"id": job_id, "state_file": str(state_file)}))    try:        call("POST", "/api/v1/capabilities/image.generate/executions", submission)    except (HTTPError, URLError, TimeoutError):        raise SystemExit("Submission not confirmed. Retain state file; rerun for GET only. Do not resubmit.") from None    poll(job_id)