> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ruapi.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Image generation: Gemini Image and GPT Image

> 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.

<Warning>
  **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.
</Warning>

## Available models

| Model ID                         | Endpoint                 | Billing   | When to use it                                       |
| -------------------------------- | ------------------------ | --------- | ---------------------------------------------------- |
| `gemini-2.5-flash-image`         | chat                     | per call  | 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](https://www.ruapi.ai/pricing)**. 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).

## Endpoint & auth

* **Base URL**: `https://www.ruapi.ai/v1`
* **Auth**: header `Authorization: Bearer sk-YOUR_KEY` (create one in the console under "Tokens")

***

## Method 1: Gemini Image — via chat

The Gemini image models live on the **chat endpoint**. You send a request exactly as you would to a text model, and an image comes back.

<Warning>
  **The image arrives as a Markdown data URI inlined in `choices[0].message.content`** — there is no separate image field. The response looks like this:

  ```
  Here is a ginger cat in a spacesuit against Saturn!
  ![image](data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...)
  ```

  You have to pull the base64 out of the text yourself. Ready-made code below.
</Warning>

### Text to image

<CodeGroup>
  ```bash curl theme={null}
  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" }
      ]
    }'
  ```

  ```python Python (requests) theme={null}
  import base64
  import re

  import requests

  resp = 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 URIs
  images = 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 image
  caption = re.sub(r"!\[image\]\([^)]*\)", "", content).strip()
  print(caption, f"— saved {len(images)} image(s)")
  ```
</CodeGroup>

<Tip>
  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.
</Tip>

### Image to image (editing)

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.

<CodeGroup>
  ```bash curl theme={null}
  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": [
            { "type": "text", "text": "Make this image black and white. Change nothing else." },
            { "type": "image_url", "image_url": { "url": "https://example.com/photo.jpg" } }
          ]
        }
      ]
    }'
  ```

  ```python Python (requests) theme={null}
  import base64
  import re
  from pathlib import Path

  import requests

  # Option A — a public image URL
  source = {"url": "https://example.com/photo.jpg"}

  # Option B — a local file as a data URI
  # raw = Path("photo.jpg").read_bytes()
  # source = {"url": "data:image/jpeg;base64," + base64.b64encode(raw).decode()}

  resp = 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": [
                      {"type": "text", "text": "Make this image black and white. Change nothing else."},
                      {"type": "image_url", "image_url": source},
                  ],
              }
          ],
      },
  )
  content = resp.json()["choices"][0]["message"]["content"]
  mime, b64 = re.findall(r"!\[image\]\(data:(image/[^;]+);base64,([^)]+)\)", content)[0]
  Path("edited.png").write_bytes(base64.b64decode(b64))
  ```
</CodeGroup>

<Note>
  The aspect ratio of the input image is preserved — the result is not cropped to a square.
</Note>

### Multi-turn editing

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".

```python theme={null}
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})
```

<Warning>
  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.
</Warning>

### Supported input image formats

`image/png`, `image/jpeg`, `image/webp`, `image/heic`, `image/heif`. Anything else is rejected with `mime type is not supported by Gemini`.

### Native Gemini endpoint

If you already write against the Google GenAI SDK, the same key works with the native format:

```bash theme={null}
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.

***

## Method 2: GPT Image — via `/v1/images/generations`

`gpt-image-2` is the classic OpenAI image generation endpoint. No `messages`, just a `prompt`.

<CodeGroup>
  ```bash curl theme={null}
  curl https://www.ruapi.ai/v1/images/generations \
    -H "Authorization: Bearer sk-YOUR_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "model": "gpt-image-2",
      "prompt": "A ginger cat in a spacesuit against Saturn",
      "size": "1024x1024",
      "quality": "low",
      "n": 1
    }'
  ```

  ```python Python (openai SDK) theme={null}
  import base64

  from openai import OpenAI

  client = OpenAI(api_key="sk-YOUR_KEY", base_url="https://www.ruapi.ai/v1")

  result = client.images.generate(
      model="gpt-image-2",
      prompt="A ginger cat in a spacesuit against Saturn",
      size="1024x1024",
      quality="low",
      n=1,
  )

  with open("image.png", "wb") as f:
      f.write(base64.b64decode(result.data[0].b64_json))
  ```
</CodeGroup>

### Parameters

| Parameter | Values                                   | Notes                                                      |
| --------- | ---------------------------------------- | ---------------------------------------------------------- |
| `size`    | `1024x1024`, `1536x1024`, `1024x1536`, … | Both dimensions must be **multiples of 16**                |
| `quality` | `low`, `medium`, `high`                  | Drives the output token count, and therefore **the price** |
| `n`       | 1 or more                                | Each image is billed separately                            |

### Response

```json theme={null}
{
  "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 }
}
```

<Warning>
  **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.
</Warning>

<Tip>
  `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.
</Tip>

***

## FAQ

<AccordionGroup>
  <Accordion title="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".
  </Accordion>

  <Accordion title="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 `![image](data:image/png;base64,...)`. Extract it with the regex above, or switch to the native Gemini endpoint where the image is a separate field.
  </Accordion>

  <Accordion title="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`.
  </Accordion>

  <Accordion title="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`.
  </Accordion>

  <Accordion title="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.
  </Accordion>
</AccordionGroup>
