B
BananaHub
Authentication
All requests require an API key passed in the Authorization header.
Authorization: Bearer YOUR_API_KEY
Idempotency: all generation endpoints accept an optional Idempotency-Key header. Replaying the same key returns the original job_id without creating a duplicate job or charging again.
Idempotency-Key: 9f1c8b1e-7c2a-4a86-9b6e-2c5fa0b7d8e1
Generate images by editing existing ones. Pass 1–14 reference images along with a prompt — the model will transform them according to your instructions. Use aspect_ratio: "auto" on image-input endpoints to match the first input image.
POST /api/v1/generations Base64 images → edited result
Request Body (JSON)
Field
Type
Required
Description
input_images_base64
array[string]
Yes
1–14 input images as base64 strings (raw or data:image/...;base64, prefix)
prompt
string
Yes
Text description of the desired output image
aspect_ratio
string
Yes
One of: 1:1 16:9 9:16 4:3 3:4 3:2 2:3 5:4 4:5 21:9 1:4 4:1 1:8 8:1 auto
resolution
string
Yes
One of: 1K 2K 4K
callback_url
string
No
Webhook URL to notify when the job completes
metadata
object
No
Arbitrary JSON metadata, returned as-is in job status
Request Example
{
  "input_images_base64": ["iVBORw0KGgoAAAANSUhEUg..."],
  "prompt": "Transform into oil painting style",
  "aspect_ratio": "1:1",
  "resolution": "2K",
  "callback_url": "https://yoursite.com/webhook",
  "metadata": {"order_id": "abc123"}
}
Response 202 Accepted
{
  "job_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "queued",
  "status_url": "/api/v1/jobs/550e8400-e29b-41d4-a716-446655440000"
}
Error Responses
402Insufficient funds
401Unauthorized — missing or invalid API key
422Validation error — check image count (max 14) or total size (max 40 MB decoded)
502Failed to upload input images to storage
503Project or NanoBanana Pro is paused — try again later
POST /api/v1/url-generations Image URLs → edited result
Requires URL endpoint permission. Returns 403 if not enabled. Contact support to request access.
Request Body (JSON)
Field
Type
Required
Description
input_images_urls
array[string]
Yes
1–14 image URLs (must start with http:// or https://). Images are downloaded server-side.
prompt
string
Yes
Text description of the desired output image
aspect_ratio
string
Yes
One of: 1:1 16:9 9:16 4:3 3:4 3:2 2:3 5:4 4:5 21:9 1:4 4:1 1:8 8:1 auto
resolution
string
Yes
One of: 1K 2K 4K
callback_url
string
No
Webhook URL to notify when the job completes
metadata
object
No
Arbitrary JSON metadata
Request Example
{
  "input_images_urls": [
    "https://example.com/photo.jpg"
  ],
  "prompt": "Transform into oil painting style",
  "aspect_ratio": "1:1",
  "resolution": "2K",
  "callback_url": "https://yoursite.com/webhook"
}
Response 202 Accepted
{
  "job_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "queued",
  "status_url": "/api/v1/jobs/550e8400-e29b-41d4-a716-446655440000"
}
Error Responses
403URL endpoint not enabled for your account
422Invalid URL, fetch timeout, server returned non-200, or total image size exceeds 40 MB
402Insufficient funds
502Failed to upload fetched images to storage
503Project or NanoBanana Pro is paused — try again later
POST /api/v1/text-generations Prompt → generated image
Request Body (JSON)
Field
Type
Required
Description
prompt
string
Yes
Text description of the image to generate
aspect_ratio
string
Yes
One of: 1:1 16:9 9:16 4:3 3:4 3:2 2:3 5:4 4:5 21:9 1:4 4:1 1:8 8:1 auto
resolution
string
Yes
One of: 1K 2K 4K
callback_url
string
No
Webhook URL to notify when the job completes
metadata
object
No
Arbitrary JSON metadata, returned as-is in job status
Request Example
{
  "prompt": "A futuristic city at sunset, cyberpunk style, highly detailed",
  "aspect_ratio": "16:9",
  "resolution": "2K",
  "callback_url": "https://yoursite.com/webhook",
  "metadata": {"order_id": "abc123"}
}
Response 202 Accepted
{
  "job_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "queued",
  "status_url": "/api/v1/jobs/550e8400-e29b-41d4-a716-446655440000"
}
Error Responses
402Insufficient funds
401Unauthorized — missing or invalid API key
422Validation error — check aspect_ratio or resolution values
503Project or NanoBanana Pro is paused — try again later
GET /api/v1/balance Current account balance
Response Fields
Field
Type
Description
balance
string (decimal)
Total deposited balance, as a decimal string
reserved
string (decimal)
Sum of reservations on jobs that haven't yet completed
available
string (decimal)
balance - reserved
Request
GET /api/v1/balance
Authorization: Bearer YOUR_API_KEY
Response 200
{
  "balance": "100.00",
  "reserved": "5.50",
  "available": "94.50"
}
GET /api/v1/jobs/{job_id} Get job status and result
Path Parameters
Parameter
Type
Required
Description
job_id
string (UUID)
Yes
The job_id returned by any generation endpoint
Response Fields
Field
Type
Description
status
string
queuedprocessingdone or failed
created_at
string (ISO 8601)
When the job was created
started_at
string (ISO 8601) | null
When a worker picked up the job; null while still queued
finished_at
string (ISO 8601) | null
When the job reached done or failed
result.image_url
string
Presigned URL to the generated image (only when status = done). Valid for 24 hours.
error
string
Error message (only when status = failed)
metadata
object
Your custom metadata passed at creation
Response — done 200
{
  "job_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "done",
  "created_at": "2026-03-03T12:00:00Z",
  "started_at": "2026-03-03T12:00:02Z",
  "finished_at": "2026-03-03T12:00:18Z",
  "result": {"image_url": "https://..."},
  "error": null,
  "metadata": {"order_id": "abc123"}
}
Response — failed 200
{
  "job_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "failed",
  "created_at": "2026-03-03T12:00:00Z",
  "started_at": "2026-03-03T12:00:02Z",
  "finished_at": "2026-03-03T12:00:05Z",
  "result": null,
  "error": "Provider returned no image",
  "metadata": null
}
Content Safety Filter (NSFW)
If Google's safety filters block the generated content, the job will return status: "failed" with error: "Content was blocked by safety filters". This is not a billing error — no charge is applied for blocked generations.
{
  "job_id": "c5de4e58-7787-436b-b37a-20e2e08d70ca",
  "status": "failed",
  "created_at": "2026-03-14T17:24:05Z",
  "started_at": "2026-03-14T17:24:08Z",
  "finished_at": "2026-03-14T17:24:29Z",
  "result": null,
  "error": "Content was blocked by safety filters",
  "metadata": null
}
Polling Strategy
Poll GET /api/v1/jobs/{job_id} until status is done or failed. Recommended interval: 2–5 seconds. Generation typically takes 10–30 seconds. Alternatively, use callback_url for webhooks.
Webhooks
Real-time notifications when a job completes or fails
How it works
Pass a callback_url when creating a job. When the job finishes (done or failed), we POST the result to that URL. Your server must respond with HTTP 2xx. If it doesn't, we retry automatically.
POST your callback_url Delivered when a job finishes
Request Headers
Header
Description
Content-Type
application/json
X-Signature
HMAC-SHA256 signature: sha256=<hex>
X-Timestamp
Unix timestamp of when the request was sent
Payload — done
{
  "job_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "done",
  "result": {"image_url": "https://..."},
  "error": null,
  "metadata": {"order_id": "abc123"}
}
Payload — failed
{
  "job_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "failed",
  "result": null,
  "error": "Provider returned no image",
  "metadata": null
}
Signature Verification Confirm the webhook came from us
Your Webhook Secret is shown on your Dashboard. We compute: HMAC-SHA256("{X-Timestamp}.{json_body}", secret)X-Signature: sha256=<hex>.
Steps
1 Serialize JSON body with sorted keys: json.dumps(payload, sort_keys=True, separators=(",",":"))
2 Build message: "{X-Timestamp}.{serialized_body}"
3 Compute HMAC-SHA256(message, webhook_secret) and hex-encode
4 Compare "sha256={result}" with X-Signature. Also check |now - X-Timestamp| < 300.
Python example
import hmac, hashlib, json, time

WEBHOOK_SECRET = "your_secret_here"

def verify_webhook(body, x_signature, x_timestamp):
    if abs(time.time() - int(x_timestamp)) > 300:
        return False
    payload = json.loads(body)
    body_str = json.dumps(
        payload, separators=(",", ":"), sort_keys=True)
    message = f"{x_timestamp}.{body_str}"
    expected = "sha256=" + hmac.new(
        WEBHOOK_SECRET.encode(),
        message.encode(),
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, x_signature)
Node.js example
const crypto = require("crypto");
const WEBHOOK_SECRET = "your_secret_here";

function verifyWebhook(rawBody, xSignature, xTimestamp) {
  const age = Math.abs(Date.now()/1000 - Number(xTimestamp));
  if (age > 300) return false;

  const payload = JSON.parse(rawBody);
  const bodyStr = JSON.stringify(
    Object.fromEntries(Object.entries(payload).sort()),
    null, 0
  );
  const message = `${xTimestamp}.${bodyStr}`;
  const expected = "sha256=" + crypto
    .createHmac("sha256", WEBHOOK_SECRET)
    .update(message).digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected), Buffer.from(xSignature)
  );
}
Retry Schedule
If your server doesn't respond with HTTP 2xx, we retry with exponential backoff. Up to 6 retries over ~1 hour total. Make sure your endpoint responds within 10 seconds.
1m 2m 5m 10m 15m 20m