How to Generate Nano Banana, Nano Banana Pro & Imagen 4 Images via the Google Flow API
6 min read • June 15, 2026
Table of contents
- Introduction
- Supported models
- Pricing
- Generate an image in two steps
- Image references & characters
- Batch-generate with a script
- Examples
- Frequently asked questions
- Conclusion
Introduction
Nano Banana, Nano Banana Pro, and Imagen 4 generate from a single curl through useapi.net. You drive your own Google Flow account — no Google Cloud project, no API key, no per-image metering. Image generation is included on any Google AI plan, even a free one, where the official Gemini API bills $0.039–$0.134 per image.
This guide covers the models you get, the price difference, the single-call image workflow with copy-paste curl, and a runnable Node.js script that batch-generates from a list of prompts. Need video instead? See the sibling Veo 3.1 tutorial.
Supported models
Pick a model per request with the model field (default imagen-4). All three models work on any Google AI subscription — including a free account.
| Model id | Marketing name | References (I2I) | Aspect ratios | Notes |
|---|---|---|---|---|
imagen-4 (default) | Imagen 4 | max 3 | 16:9, 4:3, 1:1, 3:4, 9:16 | Default model when no references are sent |
nano-banana-2 | Nano Banana 2 / Gemini 3.1 Flash Image | max 10 | 16:9, 4:3, 1:1, 3:4, 9:16, auto† | Default when references are sent — supports auto aspect ratio and upscaling |
nano-banana-pro | Nano Banana Pro / Gemini 3 Pro Image | max 10 | 16:9, 4:3, 1:1, 3:4, 9:16, auto† | Highest-quality image model — supports auto and upscaling |
† auto is valid only in image-to-image mode (at least one reference_* supplied) on nano-banana-2 / nano-banana-pro — the backend derives the ratio from the first reference image.
All three accept count (1–4) and seed for reproducibility. The legacy alias nano-banana is still accepted and maps to nano-banana-2. If you omit model, the API auto-selects: nano-banana-2 when references are present, otherwise imagen-4. Details and per-model capabilities are in the POST /images reference.
Upscaling to 2K/4K
Images generated with nano-banana-2 or nano-banana-pro can be upscaled with POST /images/upscale: pass the image’s mediaGenerationId and a resolution of 2k (default) or 4k. The response is base64 in encodedImage. 4k requires a paid Google account, and free accounts are limited to 2k. imagen-4 and the legacy nano-banana alias cannot be upscaled.
Pricing
Image generation is included. You keep your normal Google AI account — even a free one works for images (a paid plan is only needed for video and 4k upscales) — plus a flat $15/month to useapi.net for API access to every service. No per-image metering, no per-call surcharge.
Third-party Google Flow API by useapi.net vs. the official Gemini API — drive your own Google Flow subscription instead of metered, per-call API billing:
| Model | Official Gemini API | useapi.net (Flow Pro) | useapi.net (Flow Ultra) |
|---|---|---|---|
| Veo 3.1 Fast — 8s clip | $0.80 | ~$0.40 | ~$0.10 |
| Veo 3.1 Quality — 8s clip | $3.20 | ~$2.00 | ~$1.00 |
| Veo 3.1 Lite — 8s clip | $0.40 | ~$0.20 | ~$0.05 |
| Veo 3.1 Lite, lower priority | — | — | $0 (Ultra $199) |
| Gemini Omni Flash — 8s clip | — (Flow only) | ~$0.50 | ~$0.25 |
| Nano Banana Pro — per image | $0.134 | included | included |
| Nano Banana — per image | $0.039 | included | included |
| Imagen 4 — per image | metered | included | included |
Expected daily output — Ultra ($199/mo) plan. Daily averages observed on top real accounts. These are expected, not guaranteed: Google governs the underlying Flow allowances and they vary with demand.
| Free workload (no Flow credits spent) | Avg generations / day* |
|---|---|
| Veo 3.1 Lite — lower priority video | ~1,000 |
| Images — Nano Banana, Nano Banana Pro, Imagen 4 | up to ~500 |
*Averages from the busiest real accounts — not guarantees. They are set by Google’s own allowances and fluctuate with demand. Applies only to the free lower-priority video queue and image generation. Credit-metered models (Veo 3.1 Fast / Quality and Gemini Omni Flash) are not shown here — their volume is bounded by your plan’s monthly Flow credits (see the credit table), not a free allowance.
Veo is billed per second on the Gemini API (an 8-second 720p clip shown) and images are priced per image. Through useapi.net you spend your own Flow plan credits and pay a flat $15/month — image generation is included on any Google AI plan, and there is no Google Cloud project, API key, or per-call metering.
Full plan and credit details are on the Google Flow overview. See the setup guide to connect your account.
Generate an image in two steps
You need a useapi.net API token and a connected Google Flow account. Image generation is synchronous — one POST blocks until the images are ready (typically 10–20 seconds) and returns them in the response, so there’s no jobid to poll. Step two is just downloading the result from the signed URL.
- Submit the prompt —
POST https://api.useapi.net/v1/google-flow/images:
curl -X POST "https://api.useapi.net/v1/google-flow/images" \
-H "Authorization: Bearer $USEAPI_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Cat dressed like a pirate fencing with a real pirate, pirate looks absolutely terrified, this is happening on a deck of the Disney cruise ship",
"model": "nano-banana-pro",
"aspectRatio": "16:9",
"count": 4,
"seed": 123456
}' > response.json
The call returns 200 OK with a jobId and a media array — one entry per image (count is 1–4, default 4). Each image’s signed download URL is at media[].image.generatedImage.fifeUrl:
{
"jobId": "j1731859345678i-u12345-email:jo***@gmail.com-bot:google-flow",
"media": [
{
"image": {
"generatedImage": {
"seed": 123456,
"mediaGenerationId": "user:12345-email:6a6f...-image:...",
"prompt": "Cat dressed like a pirate fencing with a real pirate, pirate looks absolutely terrified, this is happening on a deck of the Disney cruise ship",
"fifeUrl": "https://storage.googleapis.com/...",
"aspectRatio": "IMAGE_ASPECT_RATIO_LANDSCAPE"
}
}
}
]
}
- Download the image from
fifeUrl(the signed URL is valid for a limited time, so download promptly):
curl -o image_1.jpg "$(jq -r '.media[0].image.generatedImage.fifeUrl' response.json)"
Each generated image’s mediaGenerationId can be reused as a reference in a later request — see Image references & characters below.
Prefer to fire-and-forget? Pass a replyUrl in the create body to receive a webhook callback when the job completes. The payload matches the GET /jobs/jobId response, where a completed image lives at response.media[].image.generatedImage.fifeUrl.
Image references & characters
To steer a generation with an existing picture, upload it first with POST /assets/email (raw bytes, an image Content-Type, PNG/JPEG/WebP up to 20 MB). The upload response nests the reference id at mediaGenerationId.mediaGenerationId — that nested string is what you pass back as reference_1 (Imagen 4 accepts up to 3 references, Nano Banana 2 / Pro up to 10):
# 1. Upload a reference image
curl -X POST "https://api.useapi.net/v1/google-flow/assets/john%40gmail.com" \
-H "Authorization: Bearer $USEAPI_TOKEN" \
-H "Content-Type: image/jpeg" \
--data-binary @reference.jpeg
# Response — the reference id is nested one level deep:
# {
# "mediaGenerationId": {
# "mediaGenerationId": "user:12345-email:6a6f...-image:ff9aa5cc-..."
# },
# "width": 1024, "height": 1024, "email": "jo***@gmail.com"
# }
# 2. Generate using that reference
curl -X POST "https://api.useapi.net/v1/google-flow/images" \
-H "Authorization: Bearer $USEAPI_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"model": "nano-banana-pro",
"prompt": "Make this photograph look modern",
"reference_1": "user:12345-email:6a6f...-image:ff9aa5cc-..."
}'
For consistent identity across many images, bundle 1–2 reference images into a reusable character with POST /characters, then pass its character id as character_1…character_7. Characters mix freely with reference_* and share the same per-model image-ref budget. Imagen models that don’t accept references also don’t accept characters. You can also drop @reference_1 / @character_1 markers inline in the prompt for positional grounding — each inline marker must have a matching body param.
Batch-generate with a script
Finding the right image takes many attempts, and running them by hand is tedious. The Node.js script below reads a list of prompts from prompts.json, uploads any reference images, submits each request, and downloads every returned image — so you can queue a batch and come back to the winners. Because POST /images is synchronous, the script writes each result as soon as the call returns.
You need Node.js v21 or newer. Put prompts.json and google-flow-images.mjs in the same folder and run node ./google-flow-images.mjs API_TOKEN EMAIL, where API_TOKEN is your useapi.net API token and EMAIL is your connected Google Flow account email. The script looks the account up by email automatically and checks its health field before submitting.
Prefer to clone and run it locally? The complete google-flow-images.mjs and prompts.json are on GitHub in useapi/google-flow-api.
Expand prompts.json
[
{
"prompt": "By default the imagen-4 model generates 4 image variations at 16:9."
},
{
"model": "nano-banana-pro",
"aspectRatio": "1:1",
"count": 2,
"seed": 123456,
"prompt": "Nano Banana Pro (Gemini 3 Pro Image), two square variations. For all parameters see https://useapi.net/docs/api-google-flow-v1/post-google-flow-images"
},
{
"model": "nano-banana-2",
"aspectRatio": "auto",
"reference_1": "./reference_image.jpeg",
"prompt": "Image-to-image: pass a local reference file as reference_1 (uploaded automatically). auto aspect ratio derives from the first reference. nano-banana-2 / nano-banana-pro accept up to 10 references."
},
{
"model": "imagen-4",
"aspectRatio": "9:16",
"count": 4,
"prompt": "Imagen 4 portrait. Other models (nano-banana-2, nano-banana-pro) are selectable via the model field — see the POST /images docs."
}
]
Expand google-flow-images.mjs script
/*
Script version 1.0, June 15, 2026
Script to batch-generate images using prompts with the Google Flow API v1 by useapi.net 🚀
Uses the synchronous POST /images endpoint (default model: imagen-4) and downloads each fifeUrl.
For more details visit https://useapi.net/docs/api-google-flow-v1/post-google-flow-images
Installation Instructions:
==========================
You need Node.js v21 or newer installed to run this script. Download and install Node.js from:
- Windows, macOS, Linux: https://nodejs.org/
After installation, verify by running the following command in a terminal:
node -v
Running the Script:
===================
Usage: node google-flow-images.mjs <API_TOKEN> <EMAIL> [PROMPTS_FILE]
Replace API_TOKEN with your actual useapi.net API token, see https://useapi.net/docs/start-here/setup-useapi
Replace EMAIL with configured Google Flow email account, see https://useapi.net/docs/start-here/setup-google-flow
If optional PROMPTS_FILE not provided prompts.json will be used.
Example:
--------
node google-flow-images.mjs user:1234-abcdefhijklmnopqrstuv [email protected]
This command executes the script using API token user:1234-abcdefhijklmnopqrstuv with [email protected] Google Flow account email.
Changelog:
==========
- June 15, 2026: Initial release.
*/
import fs from 'fs/promises';
import { writeFile } from 'node:fs/promises';
import { Readable } from 'node:stream';
// Constants
const ERRORS_FILE = 'google-flow-images_errors.txt';
const DEFAULT_PROMPTS_FILE = 'prompts.json';
const DEFAULT_MODEL = 'imagen-4';
const SLEEP_429 = 30 * 1000; // in milliseconds
const urlAccounts = 'https://api.useapi.net/v1/google-flow/accounts';
const urlImages = 'https://api.useapi.net/v1/google-flow/images';
const urlUploadAsset = 'https://api.useapi.net/v1/google-flow/assets/';
// Google Flow accepts png, jpeg and webp for reference images.
const supportedFileExtensions = ['png', 'jpeg', 'webp'];
// reference_1 .. reference_10 are accepted by POST /images.
const referenceParams = Array.from({ length: 10 }, (_, i) => `reference_${i + 1}`);
// { filename: mediaGenerationId }
const uploadedFiles = {};
// Utility to sleep for given milliseconds
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// Function to fetch configured Google Flow API accounts
async function fetchAccounts(apiToken) {
const response = await fetch(urlAccounts, {
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${apiToken}`
}
});
if (!response.ok) {
console.error(`⛔ Error fetching accounts (HTTP ${response.status}): ${response.statusText}`);
process.exit(1);
}
return response.json();
}
const elapsedTimeSec = (start) => (Date.now() - start) / 1000;
// Map a file extension to the Content-Type required by POST /assets
const contentTypeForExt = (ext) => ext === 'png' ? 'image/png' : ext === 'webp' ? 'image/webp' : 'image/jpeg';
async function uploadAsset(apiToken, email, filename) {
// Check if already uploaded
if (uploadedFiles.hasOwnProperty(filename))
return uploadedFiles[filename];
const startTime = Date.now();
console.log(`⬆️ Account ${email} uploading file…`, filename);
const body = new Blob([await fs.readFile(filename)]);
const fileExt = filename.split('.').pop();
const response = await fetch(`${urlUploadAsset}${encodeURIComponent(email)}`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${apiToken}`,
'Content-Type': contentTypeForExt(fileExt)
},
body
});
if (response.ok) {
const json = await response.json();
// POST /assets returns the reference id nested as mediaGenerationId.mediaGenerationId
const mediaGenerationId = json?.mediaGenerationId?.mediaGenerationId;
console.log(`🆗 mediaGenerationId (${elapsedTimeSec(startTime)} sec)`, mediaGenerationId);
uploadedFiles[filename] = mediaGenerationId;
}
else {
console.error(`❗ Unable to upload file HTTP ${response.status} (${elapsedTimeSec(startTime)} sec)`, await response.text());
// Do not attempt to upload failed file again
uploadedFiles[filename] = undefined;
}
return uploadedFiles[filename];
}
// Download a single image from its signed fifeUrl.
async function downloadImage(url, filename) {
try {
await fs.access(filename);
console.log(`⚠️ ${filename} already exists. Skipping download.`);
return;
} catch {
// File does not exist, proceed with downloading
}
if (!url) {
console.error(`🛑 No fifeUrl for ${filename}`);
return;
}
console.log(`✅ Downloading ${url} to ${filename}`);
try {
const imageResponse = await fetch(url);
if (!imageResponse.ok) {
console.error(`⛔ Unable to download ${filename} (HTTP ${imageResponse.status})`, url);
return;
}
const stream = Readable.fromWeb(imageResponse.body);
await writeFile(filename, stream);
} catch (err) {
console.error(`⛔ Error during download: ${err}`);
}
}
// Submit a single prompt to the synchronous POST /images endpoint and download the results.
async function submitImage(apiToken, email, prompt, index) {
const { model, prompt: text } = prompt;
const useModel = model ?? DEFAULT_MODEL;
console.log(`🚀 ${useModel} » Prompt #${index} • account ${email} …`);
// Build the request body, uploading any local reference_* file to a mediaGenerationId first.
const body = { model: useModel, email, prompt: text };
for (const key of ['aspectRatio', 'count', 'seed'])
if (prompt[key] !== undefined) body[key] = prompt[key];
for (const refKey of referenceParams) {
const value = prompt[refKey];
if (value)
body[refKey] = await uploadAsset(apiToken, email, value);
}
while (true) {
const response = await fetch(urlImages, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiToken}`
},
body: JSON.stringify(body)
});
const responseText = await response.text();
if (response.status == 200) {
const json = JSON.parse(responseText);
const media = json?.media ?? [];
if (media.length == 0)
console.error(`🛑 200 OK but no media for prompt #${index}:\n${text}\n`);
// count > 1 returns multiple images in the media array.
for (let i = 0; i < media.length; i++) {
const img = media[i]?.image?.generatedImage;
const filename = `google-flow_${index}_${i + 1}.jpg`;
await downloadImage(img?.fifeUrl, filename);
}
return 200;
}
switch (response.status) {
case 429:
console.log(`🔄️ Retry on HTTP ${response.status}`, responseText);
await sleep(SLEEP_429);
break;
case 503:
console.log(`🔄️ Service unavailable, retry on HTTP ${response.status}`, responseText);
await sleep(SLEEP_429);
break;
case 402:
console.log(`🛑 No subscription / insufficient credits`, responseText);
await fs.appendFile(ERRORS_FILE, `${response.status},#${index}:${text}\n`);
process.exit(1);
case 400:
case 500:
console.log(`🛑 Rejected (validation or content moderation)`, responseText);
await fs.appendFile(ERRORS_FILE, `${response.status},#${index}:${text}\n`);
return response.status;
default:
console.log(`❗ FAILED with HTTP ${response.status}`, responseText);
await fs.appendFile(ERRORS_FILE, `${response.status},#${index}:${text}\n`);
return response.status;
}
}
}
// Main function
async function main() {
const apiToken = process.argv[2];
const email = process.argv[3];
const promptFile = process.argv[4] || DEFAULT_PROMPTS_FILE;
if (!apiToken || !email) {
console.error('Usage: node google-flow-images.mjs <API_TOKEN> <EMAIL> [PROMPTS_FILE]');
process.exit(1);
}
console.info('Script v1.0');
console.info('Node version is: ' + process.version);
const start = new Date();
try {
console.info('START EXECUTION', start);
await execute(apiToken, email, promptFile);
} catch (error) {
console.error('⛔ Error during execution:', error.stack || error);
} finally {
console.info('COMPLETED', new Date());
console.info('EXECUTION ELAPSED', diffInMinutesAndSeconds(start, new Date()));
}
}
async function execute(apiToken, email, promptFile) {
const accounts = await fetchAccounts(apiToken);
console.info(`Configured Google Flow API accounts (${Object.keys(accounts).length}):`, Object.keys(accounts).join(', '));
if (Object.keys(accounts).length <= 0) {
console.error(`⛔ No configured Google Flow accounts found. Please refer to https://useapi.net/docs/start-here/setup-google-flow`);
process.exit(1);
}
if (!accounts[email]) {
console.error(`⛔ Account ${email} not found. Please refer to https://useapi.net/docs/start-here/setup-google-flow`);
process.exit(1);
}
if (accounts[email].health && accounts[email].health !== 'OK') {
console.error(`⛔ Account ${email} health is '${accounts[email].health}'. Please resolve and update the account, see https://useapi.net/docs/start-here/setup-google-flow`);
process.exit(1);
}
const promptData = await fs.readFile(promptFile, 'utf8');
const prompts = JSON.parse(promptData);
console.log(`Total number of prompts to process`, prompts.length);
let warnings = [];
// Parameters accepted by this script for the POST /images endpoint.
// See https://useapi.net/docs/api-google-flow-v1/post-google-flow-images for every model's full parameter set.
const supportedParams = ['model', 'prompt', 'aspectRatio', 'count', 'seed', ...referenceParams];
const invalidKeys = (prompt) => Object.keys(prompt).filter(key => !key.startsWith('__') && !supportedParams.includes(key))
for (let i = 1; i <= prompts.length; i++) {
const prompt = prompts[i - 1];
const { prompt: text } = prompt;
const validateImage = async (file) => {
if (file) {
try {
await fs.access(file);
} catch {
warnings.push(`⚠️ Image '${file}' does not exist. Prompt ${i}`);
}
const ext = file.split('.').pop();
if (!supportedFileExtensions.includes(ext))
warnings.push(`⚠️ Image ${file} extension ${ext} not supported. Prompt ${i}`);
}
};
const notSupported = invalidKeys(prompt);
if (notSupported.length)
warnings.push(`⚠️ Following params not supported: ${notSupported.join(',')}. Prompt ${i}`);
if (!text)
warnings.push(`⚠️ prompt is required. Prompt ${i}`);
await Promise.all(referenceParams.map(refKey => validateImage(prompt[refKey])));
}
if (warnings.length > 0) {
warnings.forEach(warning => console.warn(warning));
console.error(`⛔ Execution stopped due to warnings.`);
process.exit(1);
}
for (let i = 0; i < prompts.length; i++)
await submitImage(apiToken, email, prompts[i], i + 1);
}
function diffInMinutesAndSeconds(date1, date2) {
const diffInSeconds = Math.floor((date2 - date1) / 1000);
return `${Math.floor(diffInSeconds / 60)} minutes ${diffInSeconds % 60} seconds`;
};
main();
Examples
Real images generated through this same Google Flow API with Nano Banana Pro and Imagen 4 — taken straight from our changelog and model comparisons.
Nano Banana Pro — text-to-image (source): “Cat dressed like a pirate fencing with a real pirate… on a Disney cruise ship.”
Nano Banana Pro — 5 reference images (source): five individual portraits composited into one “K-Pop group victory celebration” group shot.
Imagen 4 (source): a toned-down version of the stress-test prompt (Google’s content moderation required softening the original) run head-to-head across 16+ image models — here generated with imagen-4 via the Google Flow API.

Frequently asked questions
- Is there a Nano Banana API? Yes — two ways. Google offers Nano Banana (Gemini 2.5 Flash Image) directly through the official Gemini API, which meters per image on a Google Cloud project. Or use useapi.net’s Google Flow API, which drives your own Google Flow account through a standard REST endpoint — no Cloud project, and image generation works on any Google AI plan including the free tier. See Pricing.
- Is there a Nano Banana Pro API? Yes. Nano Banana Pro is Gemini 3 Pro Image, available on Google’s official Gemini API, which meters per image. useapi.net exposes the same model as
nano-banana-proon the POST /images endpoint, driving your Flow account instead of metering per image — see Pricing. - What’s the difference between Nano Banana and Nano Banana Pro?
nano-banana-pro(Gemini 3 Pro Image) is the highest-quality image model.nano-banana-2(Gemini 3.1 Flash Image) is the model that the legacynano-bananaalias now maps to. Both accept up to 10 reference images, support theautoaspect ratio in image-to-image mode, and can be upscaled to 2K/4K. See Supported models above. - Can I use Imagen 4? Yes —
imagen-4is the default model. It accepts up to 3 reference images and the fixed aspect ratios (16:9, 4:3, 1:1, 3:4, 9:16). It does not support theautoratio or upscaling — use a Nano Banana model for those. - How much does it cost? Image generation works with any Google AI subscription or a free account, plus a flat monthly subscription to useapi.net for API access to all services. The official Gemini API meters per image, while useapi.net includes generation in the flat subscription — see Pricing above.
- Do I need a captcha or a Google Cloud project? No Google Cloud project. Image generation requires reCAPTCHA, but you do not solve it yourself — the useapi.net worker solves it automatically, and your first Google Flow account comes with 100 free captcha credits as a one-time grant (powered by CapSolver). After that you configure your own provider keys via POST /accounts/captcha-providers. The script in this guide sends no
captchaTokenand relies on this automatic solving.
Conclusion
Visit our Discord Server or
Telegram Channel for any support questions and concerns.
We regularly post guides and tutorials on the YouTube Channel.
Check our GitHub repo with code examples.