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.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"}| Step | Method and path | Success |
|---|---|---|
| List models this key may call | GET /v1/images/models | 200 |
| Quote, without upstream generation | POST /api/v1/capabilities/image.generate/quotes | 201 |
| Submit asynchronous task | POST /api/v1/capabilities/image.generate/executions | 202 |
| Read owned task | GET /api/v1/image-executions/{id} | 200 |
| Fetch completed image URLs | GET /api/v1/image-executions/{id}?result=true | 200 |
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-flareorgpt-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 exactpriced_combinationsentry; never build an unpriced combination. Omitresource_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
qualityfield in quotes, estimates and native requests; for Ext its values areflare/sunburst. - In
GET /v1/images/models,billing_mode=tokensidentifies token settlement;priced_combinations[].creditsis an output-only estimate, not a fixed final charge. For free estimates,image.prompt_bytesis the UTF-8 prompt byte count andimage.reference_imagesis the combined URL/upload reference count; generation still takes the originalpromptandimage_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.
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)PreviousProduct image kit