# gpt-image-2 Image Generation and Editing

QCode.cc exposes a **fully OpenAI-compatible** `gpt-image-2` API covering two endpoints:

- **Text-to-image** `POST /v1/images/generations` — JSON request, generate images from text alone
- **Image edit** `POST /v1/images/edits` — multipart upload of 1–8 source images (optional mask), repaint / modify / composite per the prompt (launched May 2026)

`gpt-image-2` is OpenAI's latest image model (released April 2026) and currently has the **strongest in-image text rendering of any public model** — it can reliably render English and Chinese characters inside generated images.

What QCode.cc adds:

- **Drop-in for the OpenAI SDK** — only the `base_url` changes; request and response shapes are 100% identical (including the multipart edits endpoint)
- **Multi-region access**: HK / Japan / US / EU — pick the entry closest to your network
- **Single API key**: reuse your existing QCode.cc `cr_` key — same quota account as Claude Code, Codex, and Gemini CLI
- **Unified usage view**: image calls and chat calls are merged in your dashboard, plus a dedicated self-service usage page
- **Shared quota across endpoints**: `generations` and `edits` share the same per-key 100 images/day cap and 2-concurrency limit

Common use cases: poster generation, illustrations, product imagery, UI mockups, social-media assets, **image editing (local inpainting / multi-image composition / background swap / high-fidelity restoration)**.

---

## Quick start

### Text-to-image (`generations`)

Three lines of Python for your first image:

```python
from openai import OpenAI
import base64

client = OpenAI(
    base_url="https://api.qcode.cc/qcode-img/v1",
    api_key="cr_YOUR_QCODE_API_KEY",
    timeout=180.0,  # 30–120 s per image; set ≥ 180 s
)

result = client.images.generate(
    model="gpt-image-2",
    prompt="A cyberpunk Tokyo street at night, neon reflecting in rain puddles",
    size="1024x1024",
    quality="low",
    n=1,
)

with open("output.png", "wb") as f:
    f.write(base64.b64decode(result.data[0].b64_json))
```

### Image edit (`edits`)

Upload an existing image + prompt, optionally with a mask for local inpainting:

```python
from openai import OpenAI
import base64

client = OpenAI(
    base_url="https://api.qcode.cc/qcode-img/v1",
    api_key="cr_YOUR_QCODE_API_KEY",
    timeout=180.0,
)

result = client.images.edit(
    model="gpt-image-2",
    image=open("cat.png", "rb"),
    mask=open("mask.png", "rb"),          # optional: PNG alpha; transparent area = repaint region
    prompt="put a tiny crown on the cat",
    size="1024x1024",
    quality="high",
)

with open("edited.png", "wb") as f:
    f.write(base64.b64decode(result.data[0].b64_json))
```

> ⚠️ **Always set timeout ≥ 180 s explicitly**: the OpenAI SDK default is too short. `gpt-image-2` inference is significantly slower than legacy text-to-image (see [Latency & timeout](#latency--timeout-settings) below).

---

## Endpoints

The `gpt-image-2` service (both `generations` and `edits`) is available on every QCode.cc entry. Just append `/qcode-img/v1` to the base:

| Your location | Recommended base_url | Protocol | Notes |
|---|---|---|---|
| China (HTTPS required) | `https://api.qcode.cc/qcode-img/v1` | HTTPS | Behind global CDN; `medium` / `high` may hit 524 (see [CDN 100 s hard limit](#cdn-100-s-hard-limit-524-errors)) |
| HK / SEA | `https://asia.qcode.cc/qcode-img/v1` | HTTPS | Hong Kong PoP |
| Europe | `https://eu.qcode.cc/qcode-img/v1` | HTTPS | Frankfurt PoP |
| North America | `https://us.qcode.cc/qcode-img/v1` | HTTPS | Los Angeles PoP |

All entries route to the same billing system — usage and quota are unified. Full endpoint reference: [Endpoints & API paths](/en/docs/getting-started/endpoints-and-api-paths).

> `qcode-img` is the path prefix dedicated to image generation and editing, parallel to `/api` (Anthropic), `/openai/v1` (OpenAI chat), and `/gemini` (Gemini).

---

## API reference

### Common request headers

| Header | Required | Value |
|---|---|---|
| `Authorization` | ✓ | `Bearer cr_xxxxxxxxxxxxxxxx` (your QCode.cc API key) |
| `Content-Type` | ✓ | `application/json` (generations)<br/>`multipart/form-data` (edits) |

### Generations endpoint `/v1/images/generations`

**Endpoint**: `POST {base_url}/images/generations`
**Content-Type**: `application/json`

Request body:

```json
{
  "model": "gpt-image-2",
  "prompt": "A small ceramic vase with sunflower, photorealistic",
  "size": "1024x1024",
  "quality": "low",
  "n": 1
}
```

Fields:

| Field | Type | Required | Default | Values |
|---|---|---|---|---|
| `model` | string | ✓ | — | Must be `gpt-image-2` |
| `prompt` | string | ✓ | — | Image description, **multilingual** (incl. English & Chinese) |
| `size` | string | ✗ | `1024x1024` | `1024x1024` (square) / `1024x1536` (portrait) / `1536x1024` (landscape) |
| `quality` | string | ✗ | `medium` | `low` / `medium` / `high` |
| `n` | integer | ✗ | 1 | Images per call (1 – 4); **> 4 returns 400** (no silent truncation) |

### Edits endpoint `/v1/images/edits`

**Endpoint**: `POST {base_url}/images/edits`
**Content-Type**: `multipart/form-data` (file uploads require form-data, not JSON)

Fields:

| Field | Type | Required | Default | Values / notes |
|---|---|---|---|---|
| `model` | string | ✓ | — | Must be `gpt-image-2` |
| `image` | file (× N) | ✓ | — | Source image (PNG / JPEG / WebP); **1 – 8 images supported** for multi-image composition (repeat the same field name: `-F image=@a.png -F image=@b.png`) |
| `mask` | file | ✗ | — | PNG alpha mask; **transparent area = region to repaint**; mask resolution must match `image` (scaled automatically); applies to single-image edits |
| `prompt` | string | ✓ | — | Edit instruction (multilingual) |
| `size` | string | ✗ | `auto` | `auto` / `1024x1024` / `1024x1536` / `1536x1024` / `2048x2048`. **The output resolution is chosen adaptively by the model (measured at roughly 1254×1254 today); `size` acts as an intent hint.** Invalid values return 422 |
| `quality` | string | ✗ | `auto` | `auto` / `low` / `medium` / `high` |
| `n` | integer | ✗ | 1 | Images per call (1 – 4); **> 4 returns 400** (no silent truncation) |
| `background` | string | ✗ | `auto` | `auto` / `opaque`. **`transparent` is not supported by gpt-image-2** (the model cannot produce an alpha channel; passing it returns 400) |
| `output_format` | string | ✗ | `png` | `png` / `jpeg` / `webp` |
| `output_compression` | integer | ✗ | — | `0 – 100`, applies to JPEG / WebP (ignored for PNG); **out-of-range values return 400** |
| `input_fidelity` | string | ✗ | — | **Deprecated**: gpt-image-2 always processes input images at high fidelity; if passed, the value is ignored and no longer forwarded upstream |
| `response_format` | string | ✗ | `b64_json` | Only `b64_json` is accepted (passing `url` returns 400) |
| `user` | string | ✗ | — | End-user identifier passed through (for upstream abuse detection) |

**Upload size limits**: single file ≤ 25 MB; total across files ≤ 200 MB (over the limit returns `413 image_too_large`).

### Response (same for both endpoints)

```json
{
  "created": 1777135432,
  "data": [
    {
      "b64_json": "iVBORw0KGgo...(base64 PNG/JPEG/WebP, 1-3 MB)",
      "revised_prompt": "A small ceramic vase with sunflower..."
    }
  ]
}
```

- `b64_json`: base64-encoded image data; render directly with `<img src="data:image/<format>;base64,...">`
- `revised_prompt`: the model's improved version of your original prompt (optional to display)

### Error responses

Errors follow the standard OpenAI schema:

```json
{
  "error": {
    "type": "rate_limit_error",
    "code": "image_daily_limit",
    "message": "Daily image generation count limit reached..."
  }
}
```

| HTTP | code | Meaning |
|---|---|---|
| 401 | `invalid_api_key` | API key invalid or disabled |
| 401 | `key_expired` | API key expired |
| 413 | `image_too_large` | Single file > 25 MB, or total across files > 200 MB |
| 422 | `unsupported_size` | size not supported (see each endpoint's parameter table for valid values) |
| 429 | `crs_daily_exhausted` | Daily account budget reached |
| 429 | `crs_total_exhausted` | Lifetime account budget reached |
| 429 | `image_daily_limit` | Per-key daily 100-image cap reached (shared between `generations` and `edits`; raise on request) |
| 429 | `concurrency_exhausted` | Per-key 2-concurrency cap reached (raise on request) |
| 503 | `service_overloaded` | Global service load is high — retry later |
| 503 | `image_provider_unavailable` | Upstream temporarily unavailable — retry later |

---

## Code examples

### Python (OpenAI SDK, recommended)

**Text-to-image:**

```python
from openai import OpenAI
import base64

client = OpenAI(
    base_url="https://api.qcode.cc/qcode-img/v1",
    api_key="cr_YOUR_QCODE_API_KEY",
    timeout=180.0,
)

result = client.images.generate(
    model="gpt-image-2",
    prompt="A cyberpunk Tokyo street at night, neon reflecting in rain puddles",
    size="1024x1024",
    quality="low",
    n=1,
)

img_bytes = base64.b64decode(result.data[0].b64_json)
with open("output.png", "wb") as f:
    f.write(img_bytes)
print("Saved output.png")
```

**Image edit (single image + mask inpainting):**

```python
from openai import OpenAI
import base64

client = OpenAI(
    base_url="https://api.qcode.cc/qcode-img/v1",
    api_key="cr_YOUR_QCODE_API_KEY",
    timeout=180.0,
)

result = client.images.edit(
    model="gpt-image-2",
    image=open("cat.png", "rb"),
    mask=open("mask.png", "rb"),
    prompt="put a tiny crown on the cat",
    size="1024x1024",
    quality="high",
    extra_body={"output_format": "png"},
)

img = base64.b64decode(result.data[0].b64_json)
with open("edited.png", "wb") as f:
    f.write(img)
```

**Image edit (multi-image composition, 2 – 8 images):**

```python
result = client.images.edit(
    model="gpt-image-2",
    image=[
        open("scene.png", "rb"),       # 1st image: scene background
        open("product.png", "rb"),     # 2nd image: product to place
    ],
    prompt="Place the product naturally into the scene, match the lighting and shadows.",
    size="1536x1024",
    quality="high",
)
```

### curl

**Text-to-image:**

```bash
curl https://api.qcode.cc/qcode-img/v1/images/generations \
  -H "Authorization: Bearer cr_YOUR_QCODE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-image-2",
    "prompt": "A cyberpunk Tokyo street at night",
    "size": "1024x1024",
    "quality": "low",
    "n": 1
  }' \
  | jq -r ".data[0].b64_json" | base64 -d > output.png
```

**Image edit:**

```bash
curl https://api.qcode.cc/qcode-img/v1/images/edits \
  -H "Authorization: Bearer cr_YOUR_QCODE_API_KEY" \
  -F "model=gpt-image-2" \
  -F "image=@cat.png" \
  -F "mask=@mask.png" \
  -F "prompt=put a tiny crown on the cat" \
  -F "size=1024x1024" \
  -F "quality=high" \
  -F "output_format=png" \
  | jq -r ".data[0].b64_json" | base64 -d > edited.png
```

**Multi-image composition (just repeat `-F image=@`):**

```bash
curl https://api.qcode.cc/qcode-img/v1/images/edits \
  -H "Authorization: Bearer cr_YOUR_QCODE_API_KEY" \
  -F "model=gpt-image-2" \
  -F "image=@scene.png" \
  -F "image=@product.png" \
  -F "prompt=Place the product naturally into the scene" \
  -F "size=1536x1024" \
  -F "quality=high"
```

### JavaScript / Node.js / browser

**Text-to-image:**

```javascript
const r = await fetch("https://api.qcode.cc/qcode-img/v1/images/generations", {
  method: "POST",
  headers: {
    "Authorization": "Bearer cr_YOUR_QCODE_API_KEY",
    "Content-Type":  "application/json",
  },
  body: JSON.stringify({
    model: "gpt-image-2",
    prompt: "A cyberpunk Tokyo street at night",
    size: "1024x1024",
    quality: "low",
    n: 1,
  }),
});
const json = await r.json();
const dataUrl = "data:image/png;base64," + json.data[0].b64_json;
document.querySelector("img").src = dataUrl;
```

**Image edit (browser FormData):**

```javascript
const fd = new FormData();
fd.append("model", "gpt-image-2");
fd.append("image", imageFile);              // File object from <input type="file">
fd.append("mask", maskFile);                // optional
fd.append("prompt", "put a tiny crown on the cat");
fd.append("size", "1024x1024");
fd.append("quality", "high");
fd.append("output_format", "png");

const r = await fetch("https://api.qcode.cc/qcode-img/v1/images/edits", {
  method: "POST",
  headers: { "Authorization": "Bearer cr_YOUR_QCODE_API_KEY" },
  // Don't set Content-Type manually — FormData sets multipart/form-data with the boundary
  body: fd,
});
const json = await r.json();
const dataUrl = "data:image/png;base64," + json.data[0].b64_json;
document.querySelector("img").src = dataUrl;
```

---

## Quotas

### Defaults

| Dimension | Default | Notes |
|---|---|---|
| **Daily image count** | 100 / day / key | Resets at 00:00 Beijing time; **shared between `generations` and `edits`** |
| **Concurrency** | 2 in flight | Returns 429 `concurrency_exhausted`; shared across both endpoints |
| **Upload size** (edits only) | 25 MB single file / 200 MB total | Returns 413 `image_too_large` |
| **Account budget** | Shared with your QCode.cc account `dailyCostLimit` / `totalCostLimit` | Returns 429 `crs_daily_exhausted` |

Defaults cover the vast majority of users. Contact support to raise.

### Usage lookup

- **Dashboard** — shows chat + image calls together (last activity, today's cost, lifetime cost, model distribution)
- **Self-service page**: [https://api.qcode.cc/qcode-img/usage](https://api.qcode.cc/qcode-img/usage) — enter your API key to see the last 30 days of usage, detailed call list, ECharts trend (key is kept locally in your browser, never uploaded)

---

## Billing

### Per-image pricing

`generations` and `edits` share the same pricing table:

| size | low | medium | high |
|---|---|---|---|
| **1024×1024** | $0.08 (floor) | $0.08 (floor) | $0.211 |
| **1024×1536** | $0.08 (floor) | $0.08 (floor) | $0.165 |
| **1536×1024** | $0.08 (floor) | $0.08 (floor) | $0.165 |
| **2048×2048** | $0.08 (floor) | $0.08 (floor) | $0.285 |

### $0.08 minimum

- When actual cost is below $0.08 you pay **$0.08** (`low` / `medium` typically hit this floor)
- When actual cost is ≥ $0.08 you pay the real amount (no markup)
- **The edits endpoint is billed on the real token usage reported by the upstream**; anything below $0.08 is billed at $0.08

### Multiple outputs & multi-input edits

- `n > 1` scales linearly on **output** count, e.g. `n=2` + `1024×1024` high = 2 × $0.211 = $0.422
- For `edits` multi-input (`image=[…N]`): billing is **per output** (`n`), **not per input**
- `mask` does not affect billing

### Failures don't charge

Any 4xx / 5xx error is **not billed**; client disconnects (closed connection) are not billed either.

### Currency

Costs are denominated in USD and settled per your QCode.cc account's currency policy (CNY / USD).

---

## Latency & timeout settings

`gpt-image-2` is an inference-based model and is **significantly slower than legacy text-to-image** (DALL·E 3 / SDXL):

| quality | Typical | Complex-prompt extreme |
|---|---|---|
| `low` | 20 – 35 s | ~50 s |
| `medium` | 50 – 90 s | ~120 s |
| `high` | 70 – 120 s | ~150 s |

Measured edits latency (successful edit requests): **p50 ≈ 43 s, p95 ≈ 91 s, and about 3% of requests exceed 100 s.** Long requests going through the CDN (100 s hard limit) have a small chance of hitting 524 — prefer `quality=low` / `medium` for edits.

**Practical advice**:

- The OpenAI Python SDK's default timeout is too short — **always set `timeout=180.0` or higher explicitly**
- Browser `fetch` has no default timeout, but if you use `AbortController` give it at least 180 s
- For `medium` / `high` from mainland China, prefer `asia.qcode.cc` (direct, no CDN 100 s limit)

---

## CDN 100 s hard limit (524 errors)

HTTPS requests to `api.qcode.cc / asia.qcode.cc / eu.qcode.cc / us.qcode.cc` go through the global CDN (CloudFlare). **The CDN forcibly returns `524` for any request that waits longer than 100 s for the origin to respond.**

| quality | Safe behind CDN 100 s? |
|---|---|
| `low` | ✅ Safe (< 35 s) |
| `medium` | ⚠️ Occasionally near the ceiling (close to 100 s) |
| `high` | ❌ Frequent 524 |

**Mitigations (recommended for `medium` / `high`)**:

1. **Use the `qcode-img` direct entry**: `https://api.qcode.cc/qcode-img/v1` (no CDN, no 100 s limit)
2. Or accept occasional 524 + client-side retry

---

## Prompting tips

### Text-to-image (`generations`)

- **Multilingual**: Chinese, English, or mixed prompts all work
- **More detail is better**: environment, composition, lighting, style, lens distance / focal length / angle
- **Avoid brand names / well-known people**: the model may refuse or return blurry results (OpenAI content policy)
- **In-image text**: `gpt-image-2` is exceptional at rendering English / Chinese text inside images (poster titles, slogans, signage) — just put the literal text in the prompt, no special syntax

Example:

```
A vintage poster in Bauhaus style, bold black text "MORNING COFFEE" centered,
warm orange and cream color palette, geometric shapes, slightly textured paper background
```

### Image edit (`edits`)

- **Mask says "where", not "what"**: transparent area = region to repaint, opaque area is preserved. The prompt describes what to paint. Mask resolution must match `image` (system rescales).
- **For multi-image inputs, name each image's role in the prompt** — e.g. "First image is the scene, second image is the product to place into it" — `gpt-image-2` reads inputs in order.
- **No need to pass `input_fidelity`**: gpt-image-2 always processes input images at high fidelity, automatically preserving faces / logos / text / product details (the parameter is deprecated; if passed, it is ignored).
- **Without a mask**, the whole image is repainted per the prompt (the semantic structure is preserved, but every region may change).

Example:

```
# Local inpaint: add a crown, keep the rest unchanged
prompt = "Put a tiny golden crown on the cat's head. Keep everything else unchanged."
mask = (alpha PNG with only the head area transparent)
```

---

## Differences vs the official OpenAI API

| Dimension | OpenAI official | QCode.cc |
|---|---|---|
| SDK compatibility | — | ✅ 100% compatible, only `base_url` changes |
| Pricing | Per-token | Per the table above, $0.08 / image floor |
| `/v1/images/generations` (text-to-image) | ✅ | ✅ |
| `/v1/images/edits` (image edit) | ✅ | ✅ **Live** (first launched May 2026; restored and upgraded on 2026-07-29: billed on real upstream usage): 1–8 image composition, mask inpainting |
| `stream + partial_images` (incremental return) | ✅ | ⏳ Not yet supported; **passing `stream` / `partial_images` returns 400** |
| `/v1/images/variations` (pure variants) | ✅ (legacy DALL·E endpoint) | ⏳ Not yet supported (use `edits` + empty prompt as a workaround) |

---

## Online Playground

[https://api.qcode.cc/qcode-img/](https://api.qcode.cc/qcode-img/) — try in your browser:

- **Both Text-to-image and Image-edit** modes are wired up visually (upload, draw mask, pick background / output_format, etc.)
- Enter your API key + prompt to generate immediately
- English / Chinese UI toggle
- Defaults to `low` quality (avoids CDN 524)
- Includes full API reference inline (curl / Python / JavaScript tabs + parameter table + error codes)
- One-click download as PNG / JPEG / WebP

---

## Related docs

- [Endpoints & API paths](/en/docs/getting-started/endpoints-and-api-paths) — full list of QCode.cc entries, protocol paths, curl smoke-test
- [Billing](/en/docs/reference/billing) — plans, quotas, cost rules