Image Generation
Generate AI images from text prompts via API, with optional reference images provided as URLs or base64-encoded inline data.
Generate Image
POST /v1/images/generation
Generate an AI image from a text prompt. Optionally supply reference images to guide the style or content of the output. The response streams the raw model output (JSON containing base64 image data).
Basic Generation
curl -X POST "https://api.marswave.ai/openapi/v1/images/generation" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"provider": "google",
"prompt": "A serene mountain landscape at sunset with a reflective lake",
"imageConfig": {
"aspectRatio": "16:9",
"imageSize": "2K"
}
}'const response = await fetch(
'https://api.marswave.ai/openapi/v1/images/generation',
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LISTENHUB_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
provider: 'google',
prompt: 'A serene mountain landscape at sunset with a reflective lake',
imageConfig: {
aspectRatio: '16:9',
imageSize: '2K',
},
}),
},
)
const data = await response.json()
// data.candidates[0].content.parts[0].inlineData contains the generated imageimport os
import requests
response = requests.post(
'https://api.marswave.ai/openapi/v1/images/generation',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
json={
'provider': 'google',
'prompt': 'A serene mountain landscape at sunset with a reflective lake',
'imageConfig': {
'aspectRatio': '16:9',
'imageSize': '2K',
},
}
)
data = response.json()
# data['candidates'][0]['content']['parts'][0]['inlineData'] contains the generated imageTo use GPT-Image-2, set provider to "openai" and model to
"gpt-image-2". The request and response format is the same — only
provider, model, and the imageConfig differ. See the Model
Comparison table for details.
Generation with Reference Images
Supply reference images to guide the output. Each reference image can be provided as either a URL (fileData) or base64-encoded inline data (inlineData). You can mix both formats in a single request.
Using Image URLs
curl -X POST "https://api.marswave.ai/openapi/v1/images/generation" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"provider": "google",
"prompt": "Transform this scene into a watercolor painting style",
"referenceImages": [
{
"fileData": {
"fileUri": "https://example.com/my-photo.jpg",
"mimeType": "image/jpeg"
}
}
],
"imageConfig": {
"aspectRatio": "1:1",
"imageSize": "2K"
}
}'const response = await fetch(
'https://api.marswave.ai/openapi/v1/images/generation',
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LISTENHUB_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
provider: 'google',
prompt: 'Transform this scene into a watercolor painting style',
referenceImages: [
{
fileData: {
fileUri: 'https://example.com/my-photo.jpg',
mimeType: 'image/jpeg',
},
},
],
imageConfig: {
aspectRatio: '1:1',
imageSize: '2K',
},
}),
},
)
const data = await response.json()import os
import requests
response = requests.post(
'https://api.marswave.ai/openapi/v1/images/generation',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
json={
'provider': 'google',
'prompt': 'Transform this scene into a watercolor painting style',
'referenceImages': [
{
'fileData': {
'fileUri': 'https://example.com/my-photo.jpg',
'mimeType': 'image/jpeg',
}
}
],
'imageConfig': {
'aspectRatio': '1:1',
'imageSize': '2K',
},
}
)
data = response.json()Using Base64 Inline Data
curl -X POST "https://api.marswave.ai/openapi/v1/images/generation" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"provider": "google",
"prompt": "Create a cartoon version of this portrait",
"referenceImages": [
{
"inlineData": {
"data": "<BASE64_ENCODED_IMAGE>",
"mimeType": "image/png"
}
}
]
}'import { readFileSync } from 'fs'
const imageBase64 = readFileSync('reference.png').toString('base64')
const response = await fetch(
'https://api.marswave.ai/openapi/v1/images/generation',
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LISTENHUB_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
provider: 'google',
prompt: 'Create a cartoon version of this portrait',
referenceImages: [
{
inlineData: {
data: imageBase64,
mimeType: 'image/png',
},
},
],
}),
},
)
const data = await response.json()import os
import base64
import requests
with open('reference.png', 'rb') as f:
image_base64 = base64.b64encode(f.read()).decode('utf-8')
response = requests.post(
'https://api.marswave.ai/openapi/v1/images/generation',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
json={
'provider': 'google',
'prompt': 'Create a cartoon version of this portrait',
'referenceImages': [
{
'inlineData': {
'data': image_base64,
'mimeType': 'image/png',
}
}
],
}
)
data = response.json()Request Parameters
| Field | Type | Required | Description |
|---|---|---|---|
provider | string | Yes | Model provider: google or openai |
model | string | No | Model name: gemini-3-pro-image (default), gemini-3.1-flash-image, or gpt-image-2 (requires provider: "openai") |
prompt | string | Yes | Text description of the image to generate |
referenceImages | array | No | Reference images to guide generation. Max 14 for Gemini models, max 4 for GPT-Image-2 |
referenceImages[].fileData | object | No | Reference image as a URL |
referenceImages[].fileData.fileUri | string | Yes | Image URL (http, https, or gs scheme) |
referenceImages[].fileData.mimeType | string | Yes | MIME type: image/png, image/jpeg, image/webp, image/heic, or image/heif |
referenceImages[].inlineData | object | No | Reference image as base64-encoded data |
referenceImages[].inlineData.data | string | Yes | Base64-encoded image data |
referenceImages[].inlineData.mimeType | string | Yes | MIME type: image/png, image/jpeg, image/webp, image/heic, or image/heif |
imageConfig | object | No | Image output configuration |
imageConfig.imageSize | string | No | Output resolution: 1K, 2K (default), or 4K. For GPT-Image-2 credit costs see the Image Size Tiers table |
imageConfig.aspectRatio | string | No | Aspect ratio (see table below). Optional for GPT-Image-2 — omit to let the model choose automatically |
Each item in referenceImages must contain exactly one of fileData or
inlineData, not both.
Supported Aspect Ratios
| Ratio | Description |
|---|---|
1:1 | Square |
2:3 | Portrait |
3:2 | Landscape |
3:4 | Portrait |
4:3 | Landscape |
9:16 | Vertical / mobile |
16:9 | Widescreen |
21:9 | Ultra-wide |
The gemini-3.1-flash-image model supports additional aspect ratios (1:4,
4:1, 1:8, 8:1) that are not available with the default
gemini-3-pro-image model.
Image Size Tiers
GPT-Image-2 uses a tiered credit system based on output resolution:
| Size | Tier | Credits | Free Trial |
|---|---|---|---|
1K | Standard | 4 | Yes (100 uses per user) |
2K | HD | 6 | Yes (100 uses per user) |
4K | Ultra HD | 10 | No |
Model Comparison
| Model | Strengths | Notes |
|---|---|---|
gemini-3-pro-image | Higher quality, more detailed output | Default model |
gemini-3.1-flash-image | Faster generation, supports extra aspect ratios | Good for iterative workflows |
gpt-image-2 | OpenAI's latest image model, strong prompt following | Max 4 reference images, supports all standard aspect ratios, aspectRatio is optional (auto mode) |
NanoBanana Pro Free Quota
API key calls consume the same account-level free-quota (freeUsages) balance as the web and Labnana apps. You can keep earning quota through sign-up, invites, and check-ins, then spend it through the API. Query the live balance via GET /v1/user/subscription — see its freeUsages map.
| Resource key | Model + size | What it covers |
|---|---|---|
gemini-3-pro-image-relax-1k-2k | gemini-3-pro-image at 1K / 2K | NanoBanana Pro relax — the headline free benefit |
wan2.7-image | Wan 2.7 at 1K / 2K | Bonus free quota |
gpt-image-2 | gpt-image-2 at 1K / 2K | Bonus free quota |
When the matching balance is greater than 0, a 1K/2K request spends one free generation instead of credits. Once the balance hits 0, the same request falls back to normal credit billing.
Free quota only applies to 1K and 2K sizes. A 4K request never draws
from freeUsages and is always billed in credits.
How a NanoBanana Pro relax call runs depends on your account type:
- Paid or charged accounts (active subscription, recharge, or credit-pack purchase) get the normal paid generation experience even while spending free quota — full priority, normal capacity, normal fallback. The free quota only changes billing (no credit deduction); it does not put you on a throttled lane.
- Pure-free accounts (never paid anything) run NanoBanana Pro relax on a PT-only, lowest-priority free lane with a fixed throughput cap. At peak times a request may be queued or rejected with a retryable busy/timeout response. When that happens, no credits are spent and the free quota is not consumed — just retry later (it is faster late at night).
The NanoBanana Pro free quota keeps refilling through eligible actions; it is not unlimited instant generation, unlimited concurrency, or unlimited OpenAPI calls. The pure-free relax lane is PT-only and does not fall back to shared/paygo capacity when busy.
A pure-free relax failure returns machine-readable metadata so you can detect it without parsing localized text:
reason(orfailReason) isfree_relax_busyorfree_relax_timeout.retryableistrue.freeUsageRolledBackistrueonce the free quota has been refunded.userMessagecarries friendly, localizable copy.
For sync requests this appears in the error response; for async requests it appears on the failed task detail. Treat both free_relax_busy and free_relax_timeout as "retry later, nothing was charged".
The 618 campaign is separate from free quota: from 2026-06-18 to 2026-06-24 (Asia/Shanghai) active Pro/Max subscribers get +50% monthly credits for one week. That bonus is ordinary monthly credit value, not NanoBanana Pro free quota and not a permanent plan change. See the Subscription API.
Response
The response streams the raw model output as JSON. A successful response contains the generated image as base64 data:
{
"candidates": [
{
"content": {
"parts": [
{
"inlineData": {
"mimeType": "image/png",
"data": "<BASE64_ENCODED_IMAGE>"
}
}
]
}
}
]
}Decode the data field from base64 to obtain the image file.
Rate Limiting and Reference Image Mode
Standard text-to-image requests are subject to per-user and global rate limits.
Reference image mode (referenceImages with inlineData) is subject to
additional server-side resource constraints. During peak usage periods,
requests may be throttled more aggressively. If you receive a 429 Too Many Requests response, wait a short period before retrying. We recommend
implementing exponential backoff in your client.
Error Codes
| HTTP Status | Meaning |
|---|---|
400 | Invalid request parameters (e.g., unsupported aspect ratio for the selected model) |
402 | Insufficient credits |
429 | Rate limit exceeded — retry after a short wait |
500 | Image generation failed — retry the request |