Skip to content
docs/imagesCore API

Images

Synchronous image generation: the connection is held until the images exist.

POST /v1/images/generations takes a prompt and answers with URLs. It is OpenAI's Images API shape, so an existing images.generate call works against this base URL unchanged, and it holds the connection open until the image is ready, which suits a call of tens of seconds and nothing longer. Video and anything else measured in minutes goes through the queue instead.

POST/v1/images/generationsAvailable

Generate images and return their URLs. The connection is held until the images exist.

AUTHENTICATION
Bearer token, Authorization header
REQUIRED SCOPE
api-key

The same route, OpenRouter's path

POST /v1/images reaches the identical handler under OpenRouter's spelling. One implementation, one bill; which path you post to is a matter of whichever client you already have.

POST/v1/imagesAvailable

The same image generation under OpenRouter's path. One handler, one bill.

AUTHENTICATION
Bearer token, Authorization header
REQUIRED SCOPE
api-key

Collecting the bytes

Every URL these routes return points at GET /v1/files/{id} on this domain, never at an upstream host. Present your API key on that route the way you would on any other. The URL is scoped to the account that generated it, so it is not a public link. Generated media is short-lived either way: read expires_at on the response rather than assuming a window, because how long a URL keeps working depends on how this deployment is configured. Past it the route answers 410 gone, a permanent code chosen deliberately: labelling it with a retryable one would have well-behaved clients retrying for ever against something that cannot succeed. Download anything you mean to keep.

GET/v1/files/{id}Available

Fetch generated media by reference. Proxied at request time, never redirected upstream.

AUTHENTICATION
Bearer token, Authorization header
REQUIRED SCOPE
api-key

Generating an image

cURL
curl https://api.routehook.ai/v1/images/generations \
  -H "Authorization: Bearer $ROUTEHOOK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "google/nano-banana-2",
    "prompt": "isometric factory floor, technical illustration",
    "size": "1024x1024"
  }'

The response

JSON200 OK
{
  "created": 1786312455,
  "model": "google/nano-banana-2",
  "data": [
    { "url": "https://cdn.routehook.ai/img/9d21f4-0.png" },
    { "url": "https://cdn.routehook.ai/img/9d21f4-1.png" }
  ]
}

created is unix seconds. data is one entry per image, in the order they were generated, and an entry may also carry revised_prompt where the model rewrote what you asked for. The response header x-request-id holds the req_… id of the call, which is what GET /v1/generation takes if you want to know what it cost.

Parameters

PARAMETERTYPEREQUIREDDESCRIPTION
modelstringrequiredA slug from GET /v1/models. It must be an image model. Anything else is refused before money is reserved.
promptstringrequiredWhat to generate. Up to 32,000 characters.
nintegeroptionalHow many images, 1 to 10. Defaults to 1. Each one bills at the model's rate.
sizestringoptional1024x1024, or an aspect ratio like 16:9. Advisory. See below.
qualitystringoptionalResolution tier, honoured by models that have one and ignored by the rest.
stylestringoptionalStyle hint, honoured by models that have one and ignored by the rest.
reference_urlsstring[]optionalUp to 8 publicly reachable input images, for models that take references. Ignored by models that do not, rather than refused.
userstringoptionalOpaque end-user identifier, forwarded upstream. Up to 128 characters here. Chat and completions allow 256, so a value that fits there can still be refused on this route.

The model id names a product

model is a gateway slug (google/nano-banana-2) not one of OpenAI's four image model names. The slug names the product you are buying and we pick who serves it, which is the whole point: restricting the field to one vendor's names would defeat it. The model that comes back in the response is always the slug you asked for, never an upstream's own id, and no route reports which host ran it.

size is advisory

Every upstream behind this expresses shape differently (an aspect ratio, an orientation, a resolution tier), and several reject values the others accept. So size is accepted in either form, 1024x1024 or 16:9, and translated for whatever runs it: pixels are reduced to a ratio for a route that speaks ratios, and a value one route refuses is snapped to the nearest it allows rather than 400-ing after you have already been quoted. Do not build a layout that assumes exact pixel dimensions. Read them off the image you were handed.

n multiplies the bill

The reservation taken before the upstream call is exact here: n at the model's per-image rate, plus the model's flat per-request price where it carries one. An image is priced per image and you have already said how many you want, so there is no estimate in it and nobody is refused for headroom they did not need. You are then charged for what arrived, capped at what you asked for, three of four delivered is three paid for, and an upstream that returns more than n cannot bill past the ceiling you were gated on. Batching with n saves a round trip, not money.

When it fails

A failed generation releases the reservation and charges nothing. Failover is not free on this route (where an upstream has probably already started work, its timeout is treated as non-retryable so the same prompt is not paid for twice), so a 503 upstream_unavailable can arrive without every target in the chain having been tried. Retry it; the hold is already gone. A model that is listed but has no per-image and no per-request rate is refused with 409 model_unavailable rather than served unbilled, which is an operator's job to fix and not something a retry will clear.