Nano Banana Pro API quickstart: your first image
Get your first Nano Banana Pro image through the API: create a key, send a request, check the status, and download the result, in curl, Python, and Node.js.
Rendergrid editorsAbout the editors
In brief
To get your first Nano Banana Pro image, create an API key, top up your balance, and send one POST request to /images/generate with "model": "nano-banana-pro" and a prompt. The response is a 202 with an id. Ask GET /creations/{id} for the status until it reads completed, then take the link from result_urls.
An image costs $0.04 at 1K, $0.04 at 2K, and $0.07 at 4K, and a request without resolution is made at 2K. A failed request is free: the amount goes straight back to your balance.
What do you need before the first request?
You need an API key, money on your balance, and any language that can send HTTPS requests.
Create the key in the portal (how keys work). It starts with rg_live_ followed by 40 more characters and is shown once, so copy it right away. Keep it in an environment variable named RENDERGRID_API_KEY and send requests from your server only: a key is the password to your balance and does not belong in website or mobile app code.
Your balance is in US dollars and is topped up in USDT, on the portal’s balance page or through an account manager (how top-ups work). A payment has its own window, so top up before you start. The model is Nano Banana Pro, a Google model, and its value for the model field is nano-banana-pro.
How do you send the first request?
Send a POST to /api/public/v1/images/generate on https://api.rendergrid.io. The model and prompt fields are required; a prompt can be up to 20,000 characters, and an empty one gets a 400 before any charge. The X-Idempotency-Key header protects you from a double charge when you retry: use a new key for every new image and the same key when you retry one.
# Requires bash, curl and uuidgen. One idempotency key per image. IDEMPOTENCY_KEY="$(uuidgen)" || exit 1 curl --fail-with-body --max-time 30 \ -X POST https://api.rendergrid.io/ api/ public/ v1/ images/ generate \ -H "Authorization: Bearer $RENDERGRID_API_KEY" \ -H "Content-Type: application/json" \ -H "X-Idempotency-Key: $IDEMPOTENCY_KEY" \ -d '{ "model": "nano-banana-pro", "prompt": "An open rectangular amber perfume bottle stands on folded charcoal paper, its rounded cap resting separately beside it. Show the bottle neck clearly. Light from the upper right creates a fine golden highlight and a shadow inside the paper fold. Include both objects completely with broad square framing. No lettering, logos, people, or watermarks.", "resolution": "1K", "aspect_ratio": "1:1" }'
import os import uuid import requests key = os.environ["RENDERGRID_API_KEY"] PROMPT = "An open rectangular amber perfume bottle stands on folded charcoal paper, its rounded cap resting separately beside it. Show the bottle neck clearly. Light from the upper right creates a fine golden highlight and a shadow inside the paper fold. Include both objects completely with broad square framing. No lettering, logos, people, or watermarks." response = requests.post( "https://api.rendergrid.io/ api/ public/ v1/ images/ generate", headers={ "Authorization": f"Bearer {key}", # a new key for every new image; the same key when you retry one "X-Idempotency-Key": str(uuid.uuid4()), }, json={ "model": "nano-banana-pro", "prompt": PROMPT, "resolution": "1K", "aspect_ratio": "1:1", }, timeout=(10, 120), ) response.raise_for_status() creation = response.json() print(creation["id"], creation["status"], creation["cost"])
// Node.js 22+, .mjs file import { randomUUID } from "node:crypto"; const API = "https://api.rendergrid.io/ api/ public/ v1"; const prompt = "An open rectangular amber perfume bottle stands on folded charcoal paper, its rounded cap resting separately beside it. Show the bottle neck clearly. Light from the upper right creates a fine golden highlight and a shadow inside the paper fold. Include both objects completely with broad square framing. No lettering, logos, people, or watermarks."; const response = await fetch(`${API}/images/generate`, { method: "POST", headers: { Authorization: `Bearer ${process.env.RENDERGRID_API_KEY}`, "Content-Type": "application/json", // a new key for every new image; the same key when you retry one "X-Idempotency-Key": randomUUID(), }, body: JSON.stringify({ model: "nano-banana-pro", prompt, resolution: "1K", aspect_ratio: "1:1", }), signal: AbortSignal.timeout(120_000), }); if (response.status !== 202) { throw new Error(`HTTP ${response.status}: ${await response.text()}`); } const creation = await response.json(); console.log(creation.id, creation.status, creation.cost);
The response is 202 Accepted: “request accepted,” not “image ready.” Its body has exactly four fields, id, status, task_id, and cost. Save the id. The cost field is the amount in dollars charged for the image, taken when the request is accepted, before the work starts.
Without aspect_ratio you get 1:1; a missing field does not mean auto. Nano Banana Pro takes 1:1, 16:9, 9:16, 4:3, 3:4, 2:3, 3:2, 5:4, 4:5, and 21:9, plus auto, which does not guarantee the shape. It has no 1:4, 1:8, 4:1, or 8:1.
How do you check the status and get the result?
Use the id from the 202 with GET /creations/{id}. A task is visible only to the account that created it. Its status moves from queued and processing to completed or failed; the last two are final, so stop polling there. A 200 response can carry status: "failed": that is the outcome of the task, not an error of the status request. Ask no more often than once every 5 seconds for one key and one task: a sooner poll gets 429 ERR_RATE_LIMIT.
CREATION_ID="5b0214ae-9e39-460a-820a-f6ae3e24400b" # the id from the 202 response curl --fail-with-body --max-time 20 \ "https://api.rendergrid.io/ api/ public/ v1/ creations/ $CREATION_ID" \ -H "Authorization: Bearer $RENDERGRID_API_KEY"
import os import time import requests API = "https://api.rendergrid.io/ api/ public/ v1" KEY = os.environ["RENDERGRID_API_KEY"] HEADERS = {"Authorization": f"Bearer {KEY}"} def wait_for(creation_id, wait_seconds=180): deadline = time.monotonic() + wait_seconds while time.monotonic() < deadline: r = requests.get(f"{API}/creations/{creation_id}", headers=HEADERS, timeout=(5, 20)) r.raise_for_status() creation = r.json() if creation["status"] in ("completed", "failed"): return creation time.sleep(5) # no more often than once every 5 seconds # time ran out: keep the id and ask again later raise TimeoutError(f"Still unresolved: {creation_id}") creation = wait_for("5b0214ae-9e39-460a-820a-f6ae3e24400b") # the id from the 202 response if creation["status"] == "completed": print(creation["result_urls"]) else: print(creation["error"], creation["failure"])
// Node.js 22+, .mjs file const API = "https://api.rendergrid.io/ api/ public/ v1"; const headers = { Authorization: `Bearer ${process.env.RENDERGRID_API_KEY}` }; const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); async function waitFor(creationId, waitMs = 180_000) { const deadline = Date.now() + waitMs; while (Date.now() < deadline) { const res = await fetch(`${API}/creations/${creationId}`, { headers, signal: AbortSignal.timeout(20_000), }); if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`); const creation = await res.json(); if (["completed", "failed"].includes(creation.status)) return creation; await sleep(5_000); // no more often than once every 5 seconds } // time ran out: keep the id and ask again later throw new Error(`Still unresolved: ${creationId}`); } // the id from the 202 response const creation = await waitFor("5b0214ae-9e39-460a-820a-f6ae3e24400b"); console.log(creation.status); console.log(creation.result_urls ?? creation.error);
The waiting limit in the loops is a setting of the example, not how long the model takes. Below are the two responses: the 202 that accepts the request and the 200 that carries the result.
{
"id": "5b0214ae-9e39-460a-820a-f6ae3e24400b",
"status": "queued",
"task_id": "5b0214ae-9e39-460a-820a-f6ae3e24400b",
"cost": 0.04
}
{
"id": "5b0214ae-9e39-460a-820a-f6ae3e24400b",
"status": "completed",
"result_urls": [
"https:// …"
]
}
When the status is completed, result_urls holds the links to the files. Use them as they are. They stay valid for a day, and polling does not extend that, so save the image on your side as soon as you see completed.
How long a task takes is not promised: it depends on the model and the load. Limit your total waiting time, and if it runs out, keep the id and ask again later.
Instead of polling, pass webhook_url, a public HTTPS address, and we send creation.completed or creation.failed when the task ends. Keep polling as a fallback, because a notification can be delayed or not arrive. The webhooks page covers the signature and the retries.
What changes between 1K, 2K, and 4K?
The resolution field takes 1K, 2K, or 4K, in any letter case. It is the model’s size level, not a number of pixels, and width and height are not accepted. Leave it out and the image is made, and paid for, at the default, which for Nano Banana Pro is 2K.
The three images below come from one prompt at the same aspect ratio, 1:1, with one attempt per tier and nothing picked from several tries. They measure 1,024 by 1,024, 2,048 by 2,048, and 4,096 by 4,096 pixels.
Prompt: the same at every tier
An open rectangular amber perfume bottle stands on folded charcoal paper, its rounded cap resting separately beside it. Show the bottle neck clearly. Light from the upper right creates a fine golden highlight and a shadow inside the paper fold. Include both objects completely with broad square framing. No lettering, logos, people, or watermarks.
Nano Banana Pro
nano-banana-pro · 1K · 1:1 · $0.04
Nano Banana Pro, 1K, 1:1, one attempt. The bottle stands upright a little left of the center with its threaded neck open, and the cap rests on the paper at its lower left; both objects are complete in the frame, as the prompt asks. A sheet of charcoal paper curls up behind the bottle at the right with a golden light along its edge, and a thin straight line, like the edge of a fold, runs across the paper at the left. A few faint marks on the glass near the foot of the bottle are too small to read at this size. A departure from the prompt: the shadows of the bottle and the cap fall to the right of them, so the light reads as coming from the left, not from the upper right. No readable lettering is visible.
Nano Banana Pro
nano-banana-pro · 2K · 1:1 · $0.04
Nano Banana Pro, 2K, 1:1, one attempt. The bottle stands upright in the right half of the frame with its threaded neck open, and the mushroom-shaped cap rests on the paper at its lower left; both objects are complete, as the prompt asks. The folded paper is dark at the left and a golden light spreads across it at the upper right, which fits the prompt’s light from the upper right, though it is a broad wash rather than the fine highlight the prompt describes. A few faint marks on the glass near the base of the bottle are too small to read at this size.
Nano Banana Pro
nano-banana-pro · 4K · 1:1 · $0.07
Nano Banana Pro, 4K, 1:1, one attempt. A departure from the prompt: the bottle lies on its side instead of standing, with its open threaded neck pointing to the lower left, and the cap stands upright at the left of it. Both objects are complete in the frame. The paper rises in a smooth wide curl at the left rather than a sharp fold, and the glass glows amber in the warm light.
A request at 2K or 4K is a new request and a new image, not an enlarged copy of the 1K one, and in these three attempts the arrangement changed. The bottle stands in the 1K and 2K images but lies on its side in the 4K one, although the prompt says it stands. The prompt asks for light from the upper right: the 2K image follows it, while in the 1K image the shadows fall to the right.
Check every result against the placement and light words of your prompt before you use it.
What does an image cost?
The price per image depends on the model and the resolution. Nano Banana Pro costs $0.04 at 1K, $0.04 at 2K, and $0.07 at 4K, and the price you pay arrives in cost. Prices checked on .
Your own prices come from GET /models, which charges nothing: the price of each resolution is in variants, and an account can have a personal price. Check your balance with GET /balance. All models are on the pricing page, and a separate article compares Rendergrid’s prices with Google’s official price.
What if the request fails?
Four things can go wrong, and the money follows each one differently.
- The request is rejected. A
400 ERR_INVALID_REQUESTmeans an empty prompt, or aresolutionoraspect_ratiothe model does not have;error.details.supportedlists the allowed values, and the balance does not change. A402 ERR_INSUFFICIENT_BALANCEmeans a balance that is too low or a key’s monthly limit that is used up. - A limit is hit. A
429carriesRetry-After: wait at least that many seconds.ERR_RATE_LIMITis 120 requests a minute for the account, or polling a task sooner than every 5 seconds;ERR_QUOTA_EXCEEDEDis 100 tasks queued or processing at once. - The task fails after the
202. The status request returns200withstatusset tofailed, anerrortext, and afailureobject withtype,code, andretryable. Build your logic onfailure.typeandfailure.code: typedeclinedmeans the cause is in the request itself, for example a prompt declined under the content rules, and typeerroris on the service side, so you can retry unlessretryableisfalse. The amount comes back to your balance automatically. - The response is lost. After a timeout or a dropped connection, retry the same request with the same
X-Idempotency-Key. If the first one was accepted, you get the same202response, with the sameid,status,task_id, andcost, and no new charge. That saved response may still readqueuedwhen the image is ready, so take the current status fromGET /creations/{id}.
Once a task has gone from 202 to failed, the same key does not restart it. A new attempt is a new request with a new key and a new paid operation, and the earlier charge comes back by itself. More: how a failed request is refunded, what a 429 means and how to retry, and the error codes.
Where does this guide not fit?
- Three images are not a benchmark. They show a request at each tier, not how often Nano Banana Pro follows a prompt, how fast it is, or how reliable.
- One attempt per tier. We did not measure the spread between attempts: the same prompt can give another arrangement.
- Only the first request. Reference images, batches, and a queue on your side are outside this guide: see reference images and batches, webhooks and idempotency.
- Another model, another guide. Nano Banana 2.1 also takes
1:4,1:8,4:1, and8:1; its walk-through is the Nano Banana 2.1 quickstart.
A failed request is free
Try your own prompt on Nano Banana Pro
Write your prompt in the playground on the Nano Banana Pro page, then send the same prompt through the API with the request above.
Try Nano Banana ProQuestions and answers
How do I send my first Nano Banana Pro request?
POST to /api/public/v1/images/generate with your key in the Authorization: Bearer header and a JSON body holding model set to nano-banana-pro and a prompt. Then ask GET /creations/{id} until the status is completed.Which resolutions does Nano Banana Pro take?
1K, 2K, and 4K, with 2K as the default, and ten aspect ratios plus auto.How much does a Nano Banana Pro image cost?
How long does a result take?
What happens when a request fails?
Sources
- Rendergrid docs: quickstartThe key, the first request, and the status check.
- Rendergrid docs: create an imageThe request fields and the
costfield. - Rendergrid docs: tasks and statusesThe statuses and polling.
- Rendergrid docs: models and parametersThe resolutions, aspect ratios, and
GET /models. - Rendergrid docs: idempotencyRetries without a double charge.
- Rendergrid pricingThe price per image.





