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 keys1. 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"}'{
"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"{
"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_KEYOptional 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
| Header | Required | Description |
|---|---|---|
| Authorization | yes | Bearer <API key> |
| Idempotency-Key | yes | Unique per request (≤ 128 chars). Resending the same key returns the original job (200) without charging again. Same key with a different body → 422. |
| Content-Type | yes | application/json |
Body
| Field | Type | Description |
|---|---|---|
| model | string | Required. A model id from GET /v1/models, e.g. "nanobanana". |
| prompt | string | Required unless images is present. 1–2000 characters (a model may cap it lower — see max_prompt). |
| negative_prompt | string | Optional. What to steer away from. Only on models whose schema has negative_prompt: true. |
| n | integer | Images to generate, 1–4. Default 1. Each image is billed separately. |
| resolution | string | 1K (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. |
| ratio | string | Aspect 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. |
| images | string[] | 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. |
| seed | integer | Optional. Same seed + prompt gives a reproducible result when the model supports it. |
| webhook_url | string | Optional https URL called when the job finishes. See Webhooks. |
| size | string (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).
| status | Meaning |
|---|---|
| queued | Waiting for a worker. |
| running | Being generated. |
| succeeded | Done; images[] present; credits captured. |
| failed | Failed; error{code,message} present; credits refunded. |
| cancelled | Cancelled by you before it started; credits refunded. |
{"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"{"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"{"id": "…", "status": "cancelled", …}GET /v1/account
curl -s https://api.genmux.tech/v1/account -H "Authorization: Bearer $API_KEY"{
"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{
"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_schema — resolutions, 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": "…"}| HTTP | code | When |
|---|---|---|
| 400 | validation_error | Bad or missing field, unknown field, missing Idempotency-Key. |
| 401 | unauthorized | Missing, revoked or expired API key. |
| 402 | insufficient_credits | Balance lower than the job cost. Buy credits in the dashboard. |
| 403 | ip_not_allowed | Key has an IP allowlist and your address is not on it. |
| 404 | not_found | Unknown job id (or it belongs to another account). |
| 404 | model_not_found | Unknown model id. See GET /v1/models for the current catalogue. |
| 409 | conflict | Job cannot be cancelled in its current state. |
| 422 | idempotency_key_mismatch | Idempotency-Key reused with a different body. |
| 429 | rate_limited | Per-key request rate exceeded. Honour Retry-After. |
| 429 | too_many_inflight | More than max_inflight jobs queued/running. Wait for some to finish. |
| 503 | model_unavailable | The model is temporarily disabled — nothing is charged. Retry later or pick another model. |
| 500 | internal_error | Our 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.
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.
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)); });
}