Generate an image

POST/images/generate

Submits an asynchronous image generation. The request is validated, priced, and charged up front, then accepted with 202 and a creation id; if generation later fails, the charge is refunded in full. Available models: nano-banana-2 and nano-banana-pro — see Models.

Request

Send a JSON body with your API key and Content-Type: application/json. An optional X-Idempotency-Key header makes the request safe to retry — see Idempotency.

ParameterTypeRequiredDescription
modelstringrequiredModel id: nano-banana-2 or nano-banana-pro. Unknown models return 404 ERR_MODEL_NOT_FOUND.
promptstringoptionalText prompt describing the image. Max 4000 characters.
aspect_ratiostringoptionalOutput aspect ratio — one of 1:1, 16:9, 9:16, 4:3, 3:4, 2:3, 3:2, 5:4, 4:5, 21:9. Matched exactly; any other value is rejected with 400 ERR_INVALID_REQUEST before anything is charged. Defaults to 1:1 when omitted — set it explicitly whenever you want a non-square image.
resolutionstringoptionalResolution tier: one of 1K, 2K, 4K (case-insensitive). Any other value is rejected with 400 ERR_INVALID_REQUEST before anything is charged. Defaults to 2K when omitted. Each resolution is billed at the model's matching variant price (1k / 2k / 4k) — see Models.
output_formatstringoptionalOutput file format for nano-banana-pro: png or jpg.
image_file_idstringoptionalA file id from POST /uploads used as the single input image for image-to-image generation.
image_file_idsstring[]optionalMultiple reference-image file ids from POST /uploads — up to 14 per request for both models; an over-cap request is rejected with 400 before anything is charged.
image_urlsstring[]optional1–4 source-image URLs fetched server-side — an alternative to uploading first. Mutually exclusive with image_file_id / image_file_ids. See Input images for URL requirements and limits.
google_searchbooleanoptionalEnables Google Search grounding for the prompt. nano-banana-2 only.
webhook_urlstringoptionalHTTPS URL (max 2048 chars) to receive a signed callback when the creation finishes. Validated at request time — a refused target fails with 400 before anything is charged. See Webhooks.

Output geometry is set by exactly two parameters: aspect_ratio chooses the shape (default 1:1) and resolution chooses the size tier (default 2K). A value outside the lists above — for either parameter — is rejected with 400 ERR_INVALID_REQUEST before anything is charged, never quietly substituted: a request is never served on terms other than the ones it asked for.

Each request produces exactly one image. To generate several images, send parallel requests — one per image — within your account limits. A request with n greater than 1 is rejected with 400 ERR_INVALID_REQUEST before anything is charged.

Text-to-image

The minimal request is a model and a prompt:

curl -X POST https://api.rendergrid.io/api/public/v1/images/generate \
  -H "Authorization: Bearer rg_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "nano-banana-2",
    "prompt": "a lighthouse on a cliff at golden hour, dramatic clouds",
    "aspect_ratio": "16:9"
  }'

Response — 202 Accepted

Success is 202 Accepted. id is the creation id to poll (task_id carries the same UUID — it is a compatibility alias, accepted interchangeably by GET /creations/{id}), status starts at queued, and cost is the exact amount charged, in USD, with up to 4 decimal places — sub-cent unit prices (e.g. a 0.025 contract price) are charged and reported exactly, never rounded to whole cents. Note the types: cost is a JSON number, while catalog prices in GET /models are decimal strings (e.g. "0.1000"):

202 Accepted
{
  "id": "5b0214ae-9e39-460a-820a-f6ae3e24400b",
  "status": "queued",
  "task_id": "5b0214ae-9e39-460a-820a-f6ae3e24400b",
  "cost": 0.1
}

When the creation completes, polling it returns the output URLs:

200 OK — completed creation
{
  "id": "c0ea4d5b-c965-4943-aaad-76fda0c53ca9",
  "status": "completed",
  "result_urls": [
    "https://cdn.rendergrid.io/images/2026/07/02/d13e03c7-7897-48b5-8571-8cd1e78e5f45/8b5f7dcb_20260702_100059_0f9a5d99_req_c0ea4d5b.jpg"
  ]
}

Image-to-image

Upload input images first via POST /uploads — a multipart form with a file part and kind=image (max 25 MiB per image). The returned file_id is valid for 24 hours and is passed as image_file_id (single input) or image_file_ids (multiple references). If your images are already hosted at public URLs you can skip the upload and pass image_urls instead — see Input images:

# 1. Upload the reference image
curl -X POST https://api.rendergrid.io/api/public/v1/uploads \
  -H "Authorization: Bearer rg_live_xxx" \
  -F "file=@product.png" \
  -F "kind=image"
# -> {"file_id": "ea7b3c89972d3657", "url": "https://cdn.rendergrid.io/…", "expires_at": "…"}

# 2. Generate, referencing the file id
curl -X POST https://api.rendergrid.io/api/public/v1/images/generate \
  -H "Authorization: Bearer rg_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "nano-banana-pro",
    "prompt": "place this product on a marble pedestal in soft studio light",
    "image_file_ids": ["ea7b3c89972d3657"]
  }'

Input images: file ids or URLs

There are two ways to name the input image(s), and a request must use exactly one of them:

  • image_file_id / image_file_ids — ids returned by POST /uploads (the upload-first flow above).
  • image_urls — 1–4 URLs your system already hosts. We download each one server-side, run the same validation an upload gets, and store it exactly as an upload — the generation then proceeds identically to the file-id flow. One URL behaves like image_file_id; several behave like image_file_ids (order preserved).

Sending both families in one request fails with 400 ERR_INVALID_REQUEST.

curl -X POST https://api.rendergrid.io/api/public/v1/images/generate \
  -H "Authorization: Bearer rg_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "nano-banana-pro",
    "prompt": "place this product on a marble pedestal in soft studio light",
    "image_urls": ["https://cdn.yourapp.com/products/lamp.png"]
  }'

URL requirements

  • Public http(s) URLs on ports 80/443 only. URLs resolving to private, loopback, link-local, or cloud-metadata addresses are refused.
  • At most 4 URLs per request; each file at most 25 MiB (the same cap as POST /uploads).
  • The response must carry an image content type and the bytes must actually be an image (PNG, JPEG, WEBP, GIF, BMP, ICO) — the content is verified, not just the header.
  • Each URL is fetched with a ~10-second connect+download budget (DNS resolution is outside it); redirects are not followed. Slow or redirecting origins should use the upload-first flow instead.

Failure behavior

Every URL is fetched and validated before anything is charged: a blocked, oversized, wrong-type, or timed-out URL fails the whole request with 400 ERR_INVALID_REQUEST and details.url naming the offending URL — your wallet is never touched:

400 Bad Request
{
  "error": {
    "code": "ERR_INVALID_REQUEST",
    "message": "URL refused by egress policy",
    "details": {
      "reason": "blocked_address_range",
      "url": "http://169.254.169.254/x.png"
    }
  }
}

Costs

Both models bill per image, in USD. Each resolution bills its own variant, selected by resolution (e.g. resolution: "2K" bills the 2k variant; omitting it bills 2k). Your effective per-variant prices — including any per-contract overrides — come from GET /models. The charged amount is returned as cost — a USD amount — in the 202 response.

Webhooks

Pass a webhook_url to receive a signed POST callback the moment the creation reaches a terminal status instead of polling. The URL is set per request, must be HTTPS, and every delivery is HMAC-signed — see Webhooks for headers, signature verification, and retry behavior.

Common errors

Validation failures return 400 ERR_INVALID_REQUEST with the field-level problems in details.errors. Other errors you should handle explicitly:

CodeHTTPWhen
ERR_CONTENT_POLICY400The prompt or input image was rejected by content policy.
ERR_INSUFFICIENT_BALANCE402Your wallet can't cover the request — details carry required (the required USD amount) and a top-up URL — or a per-key monthly budget was exceeded.
ERR_RATE_LIMIT / ERR_QUOTA_EXCEEDED429A rate, concurrency, or capacity limit was hit. Honor the Retry-After header.
ERR_GENERATION_FAILED502Generation failed after acceptance. The charge has already been refunded in full — safe to retry.

See Errors for the error envelope and the complete code table.

Safety filter

Prompts and input images are additionally screened by the model's safety filter at generation time. Specific, descriptive scene descriptions pass; abstract or meaningless phrases (e.g. test test) are declined noticeably more often — if a prompt is rejected, rewrite it as a concrete description of the scene you want.

A declined prompt ends the creation with status failed (the reason appears in the error field when polling — see the error values on a failed creation) — it is not charged: the up-front charge is automatically refunded in full.