Multimodal
Image generation
Create images from a text prompt and optional reference images with POST /images, returned as base64 in one response or as a stream.
POST /images generates one or more images from a prompt, optionally guided by reference images. Images always come back as base64 data with their media type; there are no hosted URLs. Image generation is available on models whose endpoints offer it: find them with GET /models?output_modalities=image.
Image prompts preserve JSON-escaped control characters without stripping. Prompt and request size limits still apply.
Generate an image
curl -X POST "https://api.numes.ai/api/v1/images" \ -H "Authorization: Bearer $NUMES_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "author/image-model", "prompt": "A lighthouse on a basalt cliff at dusk, watercolor, soft light", "size": "1024x1024", "output_format": "png" }'{ "created": 1790000000, "data": [ { "b64_json": "iVBORw0KGgoAAAANSUhEUgAABAAAAAQACAIAAADwf7zU...", "media_type": "image/png" } ], "usage": { "input_images": 0, "output_images": 1, "input_audio_seconds": 0, "output_audio_seconds": 0, "input_characters": 0, "prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0, "cost": 0.04 }}The response carries an X-Generation-Id header; use it with GET /generation to look up the request later.
Request fields
Request body
modelstringRequiredCanonical ID of an image-generation model, such as
author/image-model.promptstringRequiredWhat to draw. Must not be blank; at most 1 MiB.
nintegerDefault1Number of images, from 1 to 10.
sizestringauto, orWIDTHxHEIGHTin pixels with each side from 64 to 8192, such as1024x1536. Generated images may not exceed 16,777,216 pixels (4096 × 4096).resolutionstringResolution label, at most 16 bytes. Accepted values depend on the model.
aspect_ratiostringAspect ratio label such as
16:9, at most 16 bytes. Accepted values depend on the model.qualitystringauto,low,mediumorhigh.output_formatstringpng,jpegorwebp.backgroundstringauto,opaqueortransparent.output_compressionintegerCompression level from 0 to 100, typically for
jpegandwebp.seedinteger64-bit seed for models that support reproducible sampling.
streambooleanDefaultfalseStream Server-Sent Events instead of one JSON response.
input_referencesarrayUp to 16 reference images. See Reference images.
providerobjectRouting preferences, as for chat. See Provider routing.
Unknown fields, including response_format and user, are rejected with 400. The request body is limited to 10 MiB.
Controls and routing
Image models differ in the controls they support. Every control you set, including n and size, is forwarded, and an endpoint that can't apply one rejects the request rather than ignoring it. To route only to endpoints that declare every control you set, add provider.require_parameters: true:
curl -X POST "https://api.numes.ai/api/v1/images" \ -H "Authorization: Bearer $NUMES_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "author/image-model", "prompt": "A flat vector icon of a paper airplane", "size": "1024x1024", "background": "transparent", "output_format": "png", "provider": { "require_parameters": true } }'If no endpoint declares them, the request fails with 503 provider_unavailable. Each endpoint lists its controls in supported_parameters of GET /models/{author}/{slug}/endpoints.
Reference images
Each item in input_references is a URL string or an object {"image_url": "..."}. Unlike chat, the object holds the URL directly, not in a nested url field:
{ "model": "author/image-model", "prompt": "Place the product from the first image on the table in the second image", "input_references": [ "https://example.com/images/product.png", { "image_url": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ..." } ]}References follow the same rules as image inputs: HTTPS or base64 data: URLs, PNG, JPEG, GIF or WebP, at most 8 MiB each and 24 MiB in total. A request with references needs an endpoint that accepts image input, and each reference counts as one input_images unit.
Stream progress events
With "stream": true, the response is an SSE stream. Each frame is one data: line holding a JSON event, and the stream ends with data: [DONE]:
data: {"type":"image_generation.partial","index":0,"b64_json":"iVBORw0KGgoAAAANSUhEUgAA...","media_type":"image/png"}data: {"type":"image_generation.completed","index":0,"b64_json":"iVBORw0KGgoAAAANSUhEUgAB...","media_type":"image/png"}data: {"type":"image_generation.usage","usage":{"input_images":0,"output_images":1,"input_audio_seconds":0,"output_audio_seconds":0,"input_characters":0,"prompt_tokens":0,"completion_tokens":0,"total_tokens":0,"cost":0.04}}data: [DONE]Event type | Fields | Meaning |
|---|---|---|
image_generation.partial | index, b64_json, media_type | A preview of image index, if the model produces previews |
image_generation.completed | index, b64_json, media_type | A final image |
image_generation.usage | usage | Final usage, sent once after all images |
image_generation.error | error | The request failed after the stream started |
index identifies which of the n images an event belongs to, from 0. A failure after the stream starts keeps HTTP status 200 and sends one error event, then [DONE], with no usage event:
data: {"type":"image_generation.error","error":{"code":"provider_unavailable","message":"provider became unavailable","status":503}}data: [DONE]error.code is an error type such as provider_unavailable, rate_limit_exceeded, timeout or cancelled, and error.status the matching HTTP status. Look up what was delivered and charged with GET /generation. Failures before the first event return an ordinary JSON error.
Frames that carry full images can be several megabytes long; read them with a line reader that has no small line-length limit.
Save the images
Decode b64_json and write the bytes with the extension that matches media_type:
import base64import osimport requestsEXTENSIONS = {"image/png": "png", "image/jpeg": "jpg", "image/webp": "webp"}response = requests.post( "https://api.numes.ai/api/v1/images", headers={"Authorization": f"Bearer {os.environ['NUMES_API_KEY']}"}, json={"model": "author/image-model", "prompt": "A lighthouse at dusk, watercolor", "n": 2}, timeout=630,)response.raise_for_status()result = response.json()for index, image in enumerate(result["data"]): path = f"lighthouse-{index}.{EXTENSIONS[image['media_type']]}" with open(path, "wb") as file: file.write(base64.b64decode(image["b64_json"]))print("cost", result["usage"]["cost"], "generation", response.headers["X-Generation-Id"])When streaming, save each image_generation.completed event as it arrives:
import base64import jsonimport osimport requestsEXTENSIONS = {"image/png": "png", "image/jpeg": "jpg", "image/webp": "webp"}with requests.post( "https://api.numes.ai/api/v1/images", headers={"Authorization": f"Bearer {os.environ['NUMES_API_KEY']}"}, json={"model": "author/image-model", "prompt": "A red fox in fresh snow", "stream": True}, stream=True, timeout=630,) as response: response.raise_for_status() for line in response.iter_lines(decode_unicode=True): if not line or not line.startswith("data: "): continue payload = line[len("data: "):] if payload == "[DONE]": break event = json.loads(payload) if event["type"] == "image_generation.completed": with open(f"fox-{event['index']}.{EXTENSIONS[event['media_type']]}", "wb") as file: file.write(base64.b64decode(event["b64_json"])) elif event["type"] == "image_generation.usage": print("cost", event["usage"]["cost"]) elif event["type"] == "image_generation.error": raise RuntimeError(f"{event['error']['code']}: {event['error']['message']}")Billing
| Unit | Charged for |
|---|---|
output_images | Each final image delivered to you, at the endpoint's output image rate |
input_images | Each reference image, at the endpoint's input image rate |
| Request | The endpoint's per-request price, if it has one |
Before the request runs, funds for all n images are held, so your balance must cover n images even if fewer are delivered. GET /generation is the authoritative record of what was charged. See Usage accounting.
Errors
| Status | error_type | Cause |
|---|---|---|
| 400 | invalid_request | Blank or oversized prompt, n outside 1–10, malformed size, unknown enum value, more than 16 references, invalid reference image, unknown field |
| 402 | insufficient_credits | Balance or key limit doesn't cover n images |
| 408 | timeout | The request didn't complete in time |
| 413 | payload_too_large | Body larger than 10 MiB |
| 429 | rate_limited | Key or account rate limit; wait for Retry-After |
| 502 | provider_unavailable | The provider returned an invalid image |
| 503 | provider_unavailable | No eligible endpoint for the model, or none declares a required control |
| 503 | provider_overloaded | Every eligible endpoint is at capacity |
See Errors for the envelope and retry guidance.