Generate and edit images with RuAPI: Nano Banana (Gemini 2.5/3 Image) via the chat endpoint, GPT Image via /v1/images/generations — curl and Python examples
RuAPI gives you two families of image models: Gemini Image (widely known as Nano Banana) and GPT Image. They are called in completely different ways — that is the one thing to understand before you start. Billed in USDT, no foreign card required.
Gemini image models do not use /v1/images/generations. They go through the regular chat endpoint /v1/chat/completions, and the generated image arrives inside the reply text. See “Method 1” below.
Cheapest. Especially good at editing existing images
gemini-3.1-flash-image-preview
chat
per call
Faster, better quality than 2.5
gemini-3-pro-image-preview
chat
per call
Highest quality (Nano Banana Pro)
gpt-image-2
/v1/images/generations
per token
OpenAI ecosystem compatibility
For exact prices, see the pricing page. The Gemini models are billed per call (one flat price per image); gpt-image-2 is billed per token (bigger and higher-quality images cost more).
curl https://www.ruapi.ai/v1/chat/completions \ -H "Authorization: Bearer sk-YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gemini-2.5-flash-image", "messages": [ { "role": "user", "content": "Draw a ginger cat in a spacesuit against Saturn" } ] }'
import base64import reimport requestsresp = requests.post( "https://www.ruapi.ai/v1/chat/completions", headers={"Authorization": "Bearer sk-YOUR_KEY"}, json={ "model": "gemini-2.5-flash-image", "messages": [ {"role": "user", "content": "Draw a ginger cat in a spacesuit against Saturn"} ], },)content = resp.json()["choices"][0]["message"]["content"]# Pull every image out of the Markdown data URIsimages = re.findall(r"!\[image\]\(data:(image/[^;]+);base64,([^)]+)\)", content)for i, (mime, b64) in enumerate(images): ext = mime.split("/")[-1] with open(f"image_{i}.{ext}", "wb") as f: f.write(base64.b64decode(b64))# The caption the model wrote next to the imagecaption = re.sub(r"!\[image\]\([^)]*\)", "", content).strip()print(caption, f"— saved {len(images)} image(s)")
A single response may contain several images, which is why the example uses findall rather than search. The model almost always adds a short caption alongside the picture.
To edit an existing image, pass it the same way you would to a vision model: a content array with a text part and an image_url part. The url accepts either a public link or a data URI.
You can feed the model’s reply straight back into messages as an assistant message — the Markdown data URI is recognised and the image becomes input for the next turn. That is how you chain “make it black and white” → “now add a border”.
messages = [{"role": "user", "content": "Draw a ginger cat"}]for instruction in ["Make the image black and white", "Add a white border"]: resp = requests.post(URL, headers=HEADERS, json={"model": MODEL, "messages": messages}).json() reply = resp["choices"][0]["message"]["content"] messages.append({"role": "assistant", "content": reply}) # the image stays in context messages.append({"role": "user", "content": instruction})
Every turn is a separate billable call. Also, a history full of base64 images inflates the request body fast — do not carry more than the last two or three frames in context.
If you already write against the Google GenAI SDK, the same key works with the native format:
curl "https://www.ruapi.ai/v1beta/models/gemini-2.5-flash-image:generateContent" \ -H "Authorization: Bearer sk-YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "contents": [{ "parts": [{ "text": "Draw a ginger cat in a spacesuit" }] }] }'
Here the image comes back structured — in candidates[0].content.parts[].inlineData.data (base64), with no Markdown to parse. If you do not need OpenAI SDK compatibility, this path is simpler.
{ "data": [ { "b64_json": "iVBORw0KGgoAAAANSUhEUg...", "url": "https://.../image.png", "revised_prompt": "A ginger cat in a spacesuit against Saturn" } ], "usage": { "input_tokens": 17, "output_tokens": 425, "total_tokens": 442 }}
Use b64_json, not url. The url field points at external storage; its lifetime is not guaranteed and it may stop resolving at any time. b64_json is the image itself — once you have it, it is yours.
usage.output_tokens counts image tokens, and that is what you pay for. Dropping quality to low cuts it several-fold — handy for drafts and tests.
Why doesn't gemini-2.5-flash-image work on /v1/images/generations?
Because in OpenAI’s terms it is not an image-generation model — it is a multimodal chat model that happens to emit images. Call it through /v1/chat/completions; see “Method 1”.
What is that huge base64 blob in content?
That is the image. When served through the OpenAI-compatible chat endpoint, the picture is inlined into the reply as . Extract it with the regex above, or switch to the native Gemini endpoint where the image is a separate field.
Which model should I use to edit photos?
gemini-2.5-flash-image gives the best price-to-quality ratio for edits. For maximum detail, use gemini-3-pro-image-preview. gpt-image-2 can edit too, but through a different endpoint, /v1/images/edits.
How long are results kept?
The Gemini models store nothing — the base64 comes straight back in the response. gpt-image-2 includes a url field, but do not rely on it; save the b64_json.
Gemini responses include usage with token counts — am I billed on those?
No. Gemini image models are billed per call: one flat price per image. The usage field is informational and does not affect what you are charged. Only gpt-image-2 is billed per token.