ListenHubOpenAPI
API Reference

Image Generation

Generate AI images from text prompts and reference images, synchronously or as async tasks, with credit estimation.

The Image Generation API turns a text prompt (optionally guided by reference images) into one or more images. You can call it three ways:

  • SynchronousPOST /v1/images/generation blocks until the image is ready and returns the raw model output (base64 image data) in the response body.
  • AsynchronousPOST /v1/images/generation/async returns a taskId immediately; poll GET /v1/images/generation/tasks/{taskId} for the result (hosted image URLs).
  • Estimate firstPOST /v1/images/generation/estimate-credits returns the credit cost and whether your account can generate, without spending anything.

All endpoints require an API key (Authorization: Bearer $LISTENHUB_API_KEY). Create keys at listenhub.ai/settings/api-keys.

The two generation endpoints return data differently. The synchronous endpoint returns the raw model JSON directly in the body (not wrapped in the standard { code, message, data } envelope). The async and estimate endpoints use the standard wrapped envelope. See Response Formats.

Generate Image (synchronous)

POST /v1/images/generation

Generate an image from a text prompt and block until it is ready. Optionally supply reference images to guide the style or content. The response body is 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",
    "model": "gemini-3-pro-image",
    "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',
      model: 'gemini-3-pro-image',
      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 holds the generated image
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',
        'model': 'gemini-3-pro-image',
        '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'] holds the generated image

To 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 Provider and Model Matrix.

Generation with reference images

Supply reference images to guide the output. Each reference image is either a URL (fileData) or base64-encoded inline data (inlineData). You can mix both formats in one 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",
    "model": "gemini-3-pro-image",
    "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',
      model: 'gemini-3-pro-image',
      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',
        'model': 'gemini-3-pro-image',
        '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",
    "model": "gemini-3-pro-image",
    "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',
      model: 'gemini-3-pro-image',
      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',
        'model': 'gemini-3-pro-image',
        'prompt': 'Create a cartoon version of this portrait',
        'referenceImages': [
            {
                'inlineData': {
                    'data': image_base64,
                    'mimeType': 'image/png',
                }
            }
        ],
    },
)
data = response.json()

Request parameters

These parameters apply to POST /v1/images/generation, POST /v1/images/generation/async, and POST /v1/images/generation/estimate-credits — the three endpoints share one request schema.

FieldTypeRequiredDescription
providerstringYes¹Model provider: google, openai, or bytedance
modelstringNoModel name. Defaults to gpt-image-2. See Provider and Model Matrix
promptstringYes²Text description of the image to generate
referenceImagesarrayNoReference images to guide generation. See Reference image limits
referenceImages[].fileDataobjectNoReference image supplied as a URL
referenceImages[].fileData.fileUristringYesImage URL — scheme must be http, https, or gs
referenceImages[].fileData.mimeTypestringYesMIME type: image/png, image/jpeg, image/webp, image/heic, or image/heif
referenceImages[].inlineDataobjectNoReference image supplied as base64-encoded data
referenceImages[].inlineData.datastringYesBase64-encoded image data
referenceImages[].inlineData.mimeTypestringYesMIME type: image/png, image/jpeg, image/webp, image/heic, or image/heif
imageConfigobjectNoImage output configuration. Defaults to { "imageSize": "2K" }
imageConfig.imageSizestringNoOutput resolution: 1K, 2K (default), or 4K
imageConfig.aspectRatiostringNoAspect ratio. Defaults to 1:1. See Aspect ratios
imageConfig.qualitystringNoRender quality: low, medium, or high. Applies to GPT-Image-2; omit to let the model decide

¹ provider is required on the two generation endpoints and optional on estimate-credits. ² prompt is required on the two generation endpoints; on estimate-credits it may be empty or omitted (it only affects the input-token estimate).

Each item in referenceImages must contain exactly one of fileData or inlineData, not both.

Provider and Model Matrix

provider selects the vendor; model selects the specific model. The default model is gpt-image-2.

providermodelNotes
googlegemini-3-pro-imageHigher quality, more detailed output. NanoBanana Pro
googlegemini-3.1-flash-imageFaster generation. Supports the extra 1:4 / 4:1 / 1:8 / 8:1 ratios
openaigpt-image-2Strong prompt following. At most 4 reference images. aspectRatio optional
bytedanceseedream-5-0-proPrecise editing (coordinates / color codes go in prompt). Up to 10 reference images. 1K / 2K only

Legacy preview model IDs gemini-3-pro-image-preview and gemini-3.1-flash-image-preview are still accepted as input and normalized to their GA IDs above. Send the GA IDs for new integrations.

Aspect ratios

imageConfig.aspectRatio defaults to 1:1. The following ratios are accepted by the schema:

RatioDescription
1:1Square
2:3Portrait
3:2Landscape
3:4Portrait
4:3Landscape
9:16Vertical / mobile
16:9Widescreen
21:9Ultra-wide
1:4Flash only
4:1Flash only
1:8Flash only
8:1Flash only

1:4, 4:1, 1:8, and 8:1 are accepted only by gemini-3.1-flash-image and seedream-5-0-pro. GPT-Image-2 supports the eight standard ratios (1:1, 2:3, 3:2, 3:4, 4:3, 9:16, 16:9, 21:9) and lets you omit aspectRatio to choose automatically. A ratio unsupported by the selected model returns 400.

Image sizes

imageConfig.imageSize accepts 1K, 2K (default), and 4K. Larger sizes cost more credits and, for GPT-Image-2, 4K (and high quality) requires an active paid subscription. Seedream 5.0 Pro supports 1K and 2K only; 4K returns 400. Do not hardcode credit costs — call estimate-credits for the exact figure.

Reference image limits

ModelMax reference images
gemini-3-pro-image14
gemini-3.1-flash-image14
gpt-image-24
seedream-5-0-pro10

The schema caps referenceImages at 14 items overall. GPT-Image-2 enforces a tighter limit of 4 and Seedream 5.0 Pro a limit of 10 — exceeding it returns 400. Accepted MIME types for both fileData and inlineData are image/png, image/jpeg, image/webp, image/heic, and image/heif.

Seedream 5.0 Pro precise editing

Seedream 5.0 Pro supports precise editing: changing a specific region of an image instead of regenerating the whole thing.

There is no separate edit endpoint and no mask or region parameter — editing uses the same generation endpoint, with the source image in referenceImages and the "what to change, and how" written into prompt. The model reads absolute pixel coordinates (origin at the top-left corner of the image) and industry color codes directly.

{
  "provider": "bytedance",
  "model": "seedream-5-0-pro",
  "prompt": "Treating the top-left corner as the coordinate origin, change the content inside top-left:(376,363) bottom-right:(701,638) to green, and leave everything else unchanged",
  "referenceImages": [
    {
      "fileData": {
        "fileUri": "https://assets.listenhub.ai/your-source-image.png",
        "mimeType": "image/png"
      }
    }
  ],
  "imageConfig": { "imageSize": "2K", "aspectRatio": "1:1" }
}

If your product offers visual editing gestures (box select, lasso, arrows), translate them into coordinate strings in your own frontend — or bake the annotations into the reference image — before writing them into prompt. The server passes the prompt through verbatim and does not parse coordinates. Keep aspectRatio matching the source image's real ratio; a mismatch can make the model reconstruct the whole image instead of editing one region.

Estimate credits

POST /v1/images/generation/estimate-credits

Returns the credit cost for a given configuration and whether your account can generate it, without spending credits or calling the model. Use it to show a price before committing, or to check whether a 4K / high request needs a subscription. Accepts the same body as the generation endpoints; provider and prompt are optional here.

curl -X POST "https://api.marswave.ai/openapi/v1/images/generation/estimate-credits" \
  -H "Authorization: Bearer $LISTENHUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-image-2",
    "imageConfig": {
      "imageSize": "2K",
      "aspectRatio": "1:1",
      "quality": "medium"
    }
  }'
const response = await fetch(
  'https://api.marswave.ai/openapi/v1/images/generation/estimate-credits',
  {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.LISTENHUB_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      model: 'gpt-image-2',
      imageConfig: { imageSize: '2K', aspectRatio: '1:1', quality: 'medium' },
    }),
  },
)
const { data } = await response.json()
console.log(data.credits, data.canGenerate)
import os
import requests

response = requests.post(
    'https://api.marswave.ai/openapi/v1/images/generation/estimate-credits',
    headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
    json={
        'model': 'gpt-image-2',
        'imageConfig': {'imageSize': '2K', 'aspectRatio': '1:1', 'quality': 'medium'},
    },
)
data = response.json()['data']
print(data['credits'], data['canGenerate'])

Estimate response

Wrapped in the standard envelope. The data object:

FieldTypeDescription
modelstringNormalized GA model ID used for the estimate
imageSizestringResolved output size
aspectRatiostringResolved aspect ratio (absent when the model auto-selects)
qualitystringResolved quality (absent when not applicable)
pixelsobject{ "width": number, "height": number, "size": "WxH" } when resolved
creditsnumberCredits this configuration would cost
canGeneratebooleanWhether the account has enough effective credit balance
requiresSubscriptionbooleantrue when the config needs an active paid plan (e.g. 4K, high)
pricingobjectPricing metadata: pricingVersion, mode (token-estimate/fixed)
warningsarrayAdvisory strings, e.g. reference_image_input_tokens_not_included
{
  "code": 0,
  "message": "",
  "data": {
    "model": "gpt-image-2",
    "imageSize": "2K",
    "aspectRatio": "1:1",
    "quality": "medium",
    "pixels": { "width": 2048, "height": 2048, "size": "2048x2048" },
    "credits": 6,
    "canGenerate": true,
    "requiresSubscription": false,
    "pricing": { "pricingVersion": "...", "mode": "token-estimate" },
    "warnings": []
  }
}

Asynchronous generation

For long-running or high-resolution jobs, submit the task and poll for the result instead of holding a request open.

Create an async task

POST /v1/images/generation/async

Same request body as the synchronous endpoint. Returns 202 with a taskId immediately; generation runs in the background and the resulting images are persisted as hosted URLs.

curl -X POST "https://api.marswave.ai/openapi/v1/images/generation/async" \
  -H "Authorization: Bearer $LISTENHUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "google",
    "model": "gemini-3-pro-image",
    "prompt": "An astronaut riding a horse on Mars, photorealistic",
    "imageConfig": { "imageSize": "4K", "aspectRatio": "16:9" }
  }'
const res = await fetch(
  'https://api.marswave.ai/openapi/v1/images/generation/async',
  {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.LISTENHUB_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      provider: 'google',
      model: 'gemini-3-pro-image',
      prompt: 'An astronaut riding a horse on Mars, photorealistic',
      imageConfig: { imageSize: '4K', aspectRatio: '16:9' },
    }),
  },
)
const { data } = await res.json()
const taskId = data.taskId
import os
import requests

res = requests.post(
    'https://api.marswave.ai/openapi/v1/images/generation/async',
    headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
    json={
        'provider': 'google',
        'model': 'gemini-3-pro-image',
        'prompt': 'An astronaut riding a horse on Mars, photorealistic',
        'imageConfig': {'imageSize': '4K', 'aspectRatio': '16:9'},
    },
)
task_id = res.json()['data']['taskId']

Response (202):

{
  "code": 0,
  "message": "",
  "data": { "taskId": "65f0...", "status": "pending" }
}

Get a task

GET /v1/images/generation/tasks/{taskId}

Poll for the status and result of a single task. status is one of pending, generating, success, or fail. On success, images holds the hosted result URLs.

curl "https://api.marswave.ai/openapi/v1/images/generation/tasks/65f0abc..." \
  -H "Authorization: Bearer $LISTENHUB_API_KEY"
{
  "code": 0,
  "message": "",
  "data": {
    "taskId": "65f0abc...",
    "status": "success",
    "images": [
      { "url": "https://.../0.png", "mimeType": "image/png" }
    ],
    "createdAt": 1750000000000,
    "completedAt": 1750000020000
  }
}
FieldTypeDescription
taskIdstringTask identifier
statusstringpending, generating, success, or fail
imagesarrayPresent on success; each item is { url, mimeType }
failMsgstringFailure message when status is fail
createdAtnumberCreation time (epoch milliseconds)
completedAtnumberCompletion time (epoch milliseconds), when finished

List tasks

GET /v1/images/generation/tasks

List your image tasks, newest first.

Query paramTypeDefaultDescription
pagenumber1Page number, minimum 1
pageSizenumber20Items per page, 1100
statusstringFilter by pending, generating, success, or fail
curl "https://api.marswave.ai/openapi/v1/images/generation/tasks?page=1&pageSize=20&status=success" \
  -H "Authorization: Bearer $LISTENHUB_API_KEY"
{
  "code": 0,
  "message": "",
  "data": {
    "items": [
      { "taskId": "65f0...", "status": "success", "images": [/* ... */], "createdAt": 1750000000000, "completedAt": 1750000020000 }
    ],
    "page": 1,
    "pageSize": 20,
    "total": 1
  }
}

Background tasks left in pending or generating for more than 30 minutes are swept to fail with a timeout failMsg. Treat a non-terminal status older than that as failed and retry.

Response formats

EndpointWrapped envelope?Body
POST /v1/images/generationNo — raw model JSONBase64 image data (see below)
POST /v1/images/generation/asyncYes{ taskId, status }
POST /v1/images/generation/estimate-creditsYesEstimate object
GET /v1/images/generation/tasksYesPaginated { items, page, pageSize, total }
GET /v1/images/generation/tasks/{taskId}YesTask object

The synchronous endpoint returns the raw model output directly (not wrapped). A successful body 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. The async path persists the image for you and returns hosted urls on the task object — no base64 decoding needed.

NanoBanana Pro free quota

API key calls draw on the same account-level free-quota (freeUsages) balance as the web and Labnana apps. You 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 and read its freeUsages map.

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 applies only 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; it does not put you on a throttled lane.
  • Pure-free accounts (never paid anything) run NanoBanana Pro relax on a 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 — retry later (it is faster late at night).

A pure-free relax failure returns machine-readable metadata so you can detect it without parsing localized text:

  • failReason is free_relax_busy or free_relax_timeout.
  • retryable is true.
  • freeUsageRolledBack is true once the free quota has been refunded.
  • userMessage carries friendly, localizable copy.

For synchronous requests this appears in the error body; for async requests it appears on the failed task detail (and in the task list). Treat both reasons as "retry later, nothing was charged".

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 periods, base64 requests may be throttled more aggressively. On a 429, read the Retry-After header and back off before retrying. Implement exponential backoff in your client.

Error codes

Errors use the standard envelope (code non-zero) or, for the synchronous endpoint, the raw error body documented in NanoBanana Pro free quota.

HTTP statusMeaning
400Invalid request parameters (e.g. an aspect ratio unsupported by the selected model)
402Insufficient credits
429Rate limited or service busy — read Retry-After and retry
500Image generation failed — retry the request

On this page