Unified Image API is now generally available. Read the docs →
Unified Image API

API reference

Base URL https://api.genmux.tech. All requests and responses are JSON. Every request needs an API key.

Quick start

Create a key in the dashboard, export it, then run the commands exactly as shown.

export API_KEY=nb_live_...   # from Dashboard → API keys

1. Submit a generation

curl -s -X POST https://api.genmux.tech/v1/generations \
  -H "Authorization: Bearer $API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"model":"nanobanana","prompt":"a red fox in snow","resolution":"1K","ratio":"1:1"}'
Response · 202 Accepted
{
  "id": "0d8b6a3e-2f1c-4a5b-9c1d-7e2f3a4b5c6d",
  "model": "nanobanana",
  "status": "queued",
  "params": {"prompt": "a red fox in snow", "n": 1, "resolution": "1K", "ratio": "1:1"},
  "cost": 2,
  "created_at": "2026-08-28T20:01:12.345Z"
}

2. Poll until status = succeeded (typically 1–20 s)

curl -s https://api.genmux.tech/v1/generations/0d8b6a3e-2f1c-4a5b-9c1d-7e2f3a4b5c6d \
  -H "Authorization: Bearer $API_KEY"
Response · 200 OK
{
  "id": "0d8b6a3e-2f1c-4a5b-9c1d-7e2f3a4b5c6d",
  "model": "nanobanana",
  "status": "succeeded",
  "params": {"prompt": "a red fox in snow", "n": 1, "resolution": "1K", "ratio": "1:1"},
  "cost": 2,
  "images": [
    {
      "index": 0,
      "url": "https://…/0.png?X-Amz-Expires=900&…",
      "expires_at": "2026-08-28T20:16:30Z",
      "mime": "image/png",
      "width": 1024,
      "height": 1024,
      "bytes": 1843201,
      "sha256": "9f2c…"
    }
  ],
  "created_at": "2026-08-28T20:01:12.345Z",
  "finished_at": "2026-08-28T20:01:19.802Z"
}

3. Download the image (the URL is valid for 15 minutes)

curl -s -o fox.png "https://…/0.png?X-Amz-Expires=900&…"

Authentication

Send the key as a bearer token. Keys start with nb_live_, are shown once when created, and can be rotated or revoked in the dashboard.

Authorization: Bearer $API_KEY

Optional per-key IP allowlist: requests from other addresses get 403 ip_not_allowed.

POST /v1/generations

Creates an asynchronous generation job. Credits are reserved immediately and refunded if the job fails.

Headers

HeaderRequiredDescription
AuthorizationyesBearer <API key>
Idempotency-KeyyesUnique per request (≤ 128 chars). Resending the same key returns the original job (200) without charging again. Same key with a different body → 422.
Content-Typeyesapplication/json

Body

FieldTypeDescription
modelstringRequired. A model id from GET /v1/models, e.g. "nanobanana".
promptstringRequired unless images is present. 1–2000 characters (a model may cap it lower — see max_prompt).
negative_promptstringOptional. What to steer away from. Only on models whose schema has negative_prompt: true.
nintegerImages to generate, 1–4. Default 1. Each image is billed separately.
resolutionstring1K (default) · 2K · 4K. The tier a model renders at, and the unit it is priced in. A model lists the tiers it supports in params_schema.resolutions.
ratiostringAspect ratio: auto · 1:1 (default) · 16:9 · 9:16 · 4:3 · 3:4 · 3:2 · 2:3 · 5:4 · 4:5 · 21:9. Ratio never changes the price.
imagesstring[]Optional. Reference images for image-to-image, as public https URLs. Up to the model's max_images; omitted from a model's schema means it does not accept them.
seedintegerOptional. Same seed + prompt gives a reproducible result when the model supports it.
webhook_urlstringOptional https URL called when the job finishes. See Webhooks.
sizestring (legacy)Deprecated. A WIDTHxHEIGHT string, mapped to the nearest resolution + ratio. Accepted for existing integrations only; sending it together with resolution or ratio is a 400. New code should use resolution and ratio.

Response 202 Accepted

{"id": "…", "model": "nanobanana", "status": "queued", "params": {…}, "cost": 2, "created_at": "…"}

Body size limit 64 KB. Unknown fields are rejected with 400.

GET /v1/generations/{id}

Returns the job. When status = succeeded the response includes images[] with presigned download URLs (15 minutes; call again for fresh URLs).

statusMeaning
queuedWaiting for a worker.
runningBeing generated.
succeededDone; images[] present; credits captured.
failedFailed; error{code,message} present; credits refunded.
cancelledCancelled by you before it started; credits refunded.
Failed job
{"id": "…", "status": "failed", "error": {"code": "content_policy", "message": "the prompt was rejected by the content policy"}, "cost": 2, …}

GET /v1/generations

Newest first, 50 per page. Pass cursor from the previous response to get the next page.

curl -s "https://api.genmux.tech/v1/generations?cursor=2026-08-28T20:01:12.345Z" -H "Authorization: Bearer $API_KEY"
Response
{"data": [ {…job…}, … ], "next_cursor": "2026-08-27T09:12:44.001Z"}

DELETE /v1/generations/{id}

Cancels a job that is still queued and refunds its credits. Jobs that are already running cannot be cancelled (409).

curl -s -X DELETE https://api.genmux.tech/v1/generations/<id> -H "Authorization: Bearer $API_KEY"
Response · 200 OK
{"id": "…", "status": "cancelled", …}

GET /v1/account

curl -s https://api.genmux.tech/v1/account -H "Authorization: Bearer $API_KEY"
Response · 200 OK
{
  "id": "…",
  "name": "…",
  "balance": 498,
  "inflight": 1,
  "max_inflight": 20,
  "key": {"prefix": "nb_live_AbCdEfGh", "rate_limit_rps": 10}
}

GET /v1/models

Returns the public model catalogue — ids, the resolution tiers and aspect ratios each model accepts, credits per tier, and availability. No authentication required.

curl -s https://api.genmux.tech/v1/models
Response · 200 OK
{
  "data": [
    {
      "id": "nanobanana",
      "display_name": "Nano Banana",
      "description": "Fast text-to-image and image-to-image at 1K.",
      "capability": "image.generate",
      "status": "active",
      "params_schema": {
        "resolutions": ["1K"],
        "ratios": ["auto", "1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3", "5:4", "4:5", "21:9"],
        "max_n": 4,
        "seed": true,
        "max_images": 14,
        "negative_prompt": true,
        "max_prompt": 2000
      },
      "default_params": {"resolution": "1K", "ratio": "1:1", "n": 1},
      "prices": {"image:1K": 2},
      "available": true
    },
    {
      "id": "nanobanana-pro",
      "display_name": "Nano Banana Pro",
      "description": "Higher fidelity, with 1K, 2K and 4K output.",
      "capability": "image.generate",
      "status": "active",
      "params_schema": {
        "resolutions": ["1K", "2K", "4K"],
        "ratios": ["auto", "1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3", "5:4", "4:5", "21:9"],
        "max_n": 4,
        "seed": true,
        "max_images": 14,
        "negative_prompt": true,
        "max_prompt": 2000
      },
      "default_params": {"resolution": "1K", "ratio": "1:1", "n": 1},
      "prices": {"image:1K": 4, "image:2K": 6, "image:4K": 8},
      "available": true
    }
  ]
}

Prices are keyed by resolution tier: image:1K, image:2K, image:4K. A model advertises what it accepts in params_schemaresolutions, ratios, max_images (reference images; absent means image-to-image is not supported) and negative_prompt. Catalogue entries may still carry a legacy sizes list and matching image:WIDTHxHEIGHT prices for clients that send the deprecated size field; ignore both unless you do.

Models & pricing

Job cost = credits per image (below) × n. It is reserved when you submit and refunded in full if the job fails or is cancelled.

Model
1K
long edge ~1024 px
2K
long edge ~2048 px
4K
long edge ~4096 px
Nano Banana
nanobanana
up to 4 images per request
2credits
$0.025 / image
Nano Banana Pro
nanobanana-pro
up to 4 images per request
4credits
$0.05 / image
6credits
$0.075 / image
8credits
$0.10 / image
Qwen-Image 2.0
qwen-image-2
up to 4 images per request
3credits
$0.038 / image
4credits
$0.05 / image
Qwen-Image Max
qwen-image-max
up to 4 images per request
8credits
$0.10 / image
9credits
$0.113 / image
Seedream 5.0
seedream-5
up to 4 images per request
16credits
$0.20 / image
18credits
$0.225 / image
Wan 2.7 Image
wan-2-7-image
up to 4 images per request
2credits
$0.025 / image
3credits
$0.038 / image

Pick a tier with resolution and a shape with ratio. Money figures use the best-value pack ($0.013 per credit). Cost = credits × n; failed jobs are refunded in full.

Errors

Errors use application/problem+json. Match on code; request_id is also returned in the X-Request-ID header — include it when contacting support.

{"type": "…/errors/insufficient_credits", "title": "Payment Required", "status": 402, "code": "insufficient_credits", "detail": "account balance is too low for this request", "request_id": "…"}
HTTPcodeWhen
400validation_errorBad or missing field, unknown field, missing Idempotency-Key.
401unauthorizedMissing, revoked or expired API key.
402insufficient_creditsBalance lower than the job cost. Buy credits in the dashboard.
403ip_not_allowedKey has an IP allowlist and your address is not on it.
404not_foundUnknown job id (or it belongs to another account).
404model_not_foundUnknown model id. See GET /v1/models for the current catalogue.
409conflictJob cannot be cancelled in its current state.
422idempotency_key_mismatchIdempotency-Key reused with a different body.
429rate_limitedPer-key request rate exceeded. Honour Retry-After.
429too_many_inflightMore than max_inflight jobs queued/running. Wait for some to finish.
503model_unavailableThe model is temporarily disabled — nothing is charged. Retry later or pick another model.
500internal_errorOur fault. Retry with the same Idempotency-Key — you will not be charged twice.

Rate limits

Each key has a requests-per-second limit (default 10) and each account a cap on concurrent jobs (default 20). Limit responses are 429 with a Retry-After header in seconds. Need more? Contact us from the dashboard.

Webhooks

Pass webhook_url (https only, public host) when creating a job. We POST once when the job finishes and retry with backoff for up to 8 attempts until you return 2xx.

POST <webhook_url>
Content-Type: application/json
X-Webhook-Id: 1042
X-Webhook-Timestamp: 1756411279
X-Webhook-Signature: v1=3f7a…

{"type": "generation.succeeded", "id": "…", "status": "succeeded", "error_code": null, "created_at": "…", "finished_at": "…"}

Verify the signature with your account’s webhook secret (Dashboard → Settings): HMAC-SHA256(secret, timestamp + "." + raw_body), hex-encoded, compared to the value after v1=. During a secret rotation two signatures are sent, comma-separated — accept either. Reject timestamps older than 5 minutes.

Node.js
import { createHmac, timingSafeEqual } from "node:crypto";

export function verify(secret, headers, rawBody) {
  const ts = headers["x-webhook-timestamp"];
  const expected = createHmac("sha256", secret).update(ts + "." + rawBody).digest("hex");
  return headers["x-webhook-signature"].split(",")
    .some((s) => { const v = s.replace(/^v1=/, ""); return v.length === expected.length && timingSafeEqual(Buffer.from(v), Buffer.from(expected)); });
}