ListenHubOpenAPI
API Reference

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 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',
        '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 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 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

FieldTypeRequiredDescription
providerstringYesModel provider: google or openai
modelstringNoModel name: gemini-3-pro-image (default), gemini-3.1-flash-image, or gpt-image-2 (requires provider: "openai")
promptstringYesText description of the image to generate
referenceImagesarrayNoReference images to guide generation. Max 14 for Gemini models, max 4 for GPT-Image-2
referenceImages[].fileDataobjectNoReference image as a URL
referenceImages[].fileData.fileUristringYesImage URL (http, https, or gs scheme)
referenceImages[].fileData.mimeTypestringYesMIME type: image/png, image/jpeg, image/webp, image/heic, or image/heif
referenceImages[].inlineDataobjectNoReference image 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
imageConfig.imageSizestringNoOutput resolution: 1K, 2K (default), or 4K. For GPT-Image-2 credit costs see the Image Size Tiers table
imageConfig.aspectRatiostringNoAspect 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

RatioDescription
1:1Square
2:3Portrait
3:2Landscape
3:4Portrait
4:3Landscape
9:16Vertical / mobile
16:9Widescreen
21:9Ultra-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:

SizeTierCreditsFree Trial
1KStandard4Yes (100 uses per user)
2KHD6Yes (100 uses per user)
4KUltra HD10No

Model Comparison

ModelStrengthsNotes
gemini-3-pro-imageHigher quality, more detailed outputDefault model
gemini-3.1-flash-imageFaster generation, supports extra aspect ratiosGood for iterative workflows
gpt-image-2OpenAI's latest image model, strong prompt followingMax 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 keyModel + sizeWhat it covers
gemini-3-pro-image-relax-1k-2kgemini-3-pro-image at 1K / 2KNanoBanana Pro relax — the headline free benefit
wan2.7-imageWan 2.7 at 1K / 2KBonus free quota
gpt-image-2gpt-image-2 at 1K / 2KBonus 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 (or 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 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 StatusMeaning
400Invalid request parameters (e.g., unsupported aspect ratio for the selected model)
402Insufficient credits
429Rate limit exceeded — retry after a short wait
500Image generation failed — retry the request

On this page