# Requires bash, curl, and uuidgen. The key is created once per operation.
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": "A ceramic mug on marble, soft window light"
}'
import os
import random
import time
import requests
API = "https://api.rendergrid.io/api/public/v1"
KEY = os.environ["RENDERGRID_API_KEY"]
def submit(
payload: dict, operation_key: str, max_attempts: int = 3
) -> dict:
headers = {
"Authorization": f"Bearer {KEY}",
"X-Idempotency-Key": operation_key,
}
for attempt in range(max_attempts):
last = attempt + 1 == max_attempts
delay = 2 ** (attempt + 1) + random.random()
try:
resp = requests.post(
f"{API}/images/generate",
headers=headers, json=payload, timeout=(5, 30),
)
except (requests.Timeout, requests.ConnectionError):
if last:
raise
else:
if resp.status_code == 202:
return resp.json()
try:
details = resp.json()["error"].get("details") or {}
except (ValueError, KeyError, AttributeError):
details = {}
retryable = (
resp.status_code == 429
or resp.status_code >= 500
or details.get("retryable") is True
)
blocked = details.get("reason") == "burst_block"
if blocked or not retryable or last:
raise RuntimeError(resp.status_code, resp.text)
wait = resp.headers.get("Retry-After", "")
if wait.isascii() and wait.isdigit():
delay = max(delay, int(wait))
time.sleep(delay)
payload = {
"model": "nano-banana-pro",
"prompt": "A ceramic mug on marble, soft window light",
}
creation = submit(payload, operation_key="order-8412-cover-v1")
print(creation["id"])
// Node.js 22+, .mjs file
const API = "https://api.rendergrid.io/api/public/v1";
const KEY = process.env.RENDERGRID_API_KEY;
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function submit(payload, operationKey, maxAttempts = 3) {
const body = JSON.stringify(payload);
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const last = attempt + 1 === maxAttempts;
let delay = (2 ** (attempt + 1) + Math.random()) * 1_000;
let resp;
try {
resp = await fetch(`${API}/images/generate`, {
method: "POST",
headers: {
Authorization: `Bearer ${KEY}`,
"Content-Type": "application/json",
"X-Idempotency-Key": operationKey,
},
body,
signal: AbortSignal.timeout(30_000),
});
} catch (error) {
const network = error instanceof TypeError;
if (!network && error.name !== "TimeoutError") throw error;
if (last) throw error;
}
if (resp) {
if (resp.status === 202) return resp.json();
const text = await resp.text();
let details;
try {
details = JSON.parse(text)?.error?.details;
} catch {
details = undefined;
}
const retryable = resp.status === 429 || resp.status >= 500 || details?.retryable === true;
const blocked = details?.reason === "burst_block";
if (blocked || !retryable || last) {
throw new Error(`HTTP ${resp.status}: ${text}`);
}
const wait = resp.headers.get("Retry-After");
if (wait && /^[0-9]+$/.test(wait)) {
delay = Math.max(delay, Number(wait) * 1_000);
}
}
await sleep(delay);
}
}
const payload = {
model: "nano-banana-pro",
prompt: "A ceramic mug on marble, soft window light",
};
const creation = await submit(payload, "order-8412-cover-v1");
console.log(creation.id);