Image generation

POST/v1/images/generations

Images come back as base64 in the same answer; a long generation returns a job id (202).

Request

modelstringbodyrequired
an image model id from the catalog
promptstringbodyrequired
what to draw; we check it is there, the model checks length (EMPTY_PROMPT)
nintegerbody= 1
how many images, 1–10; it sizes the reserve, the charge follows the result
aspect_ratiostringbody= 1:1
aspect ratio; the accepted set depends on the model
1:1 · 16:9 · 9:16 · 4:3 · 3:4
resolutionstringbody= 1K
resolution; not every model has it (nano-banana-pro does)
1K · 2K · 4K
orientationstringbody= square
orientation — for the models that do not take aspect_ratio
landscape · portrait · square
imagestring|arraybody
a reference: a URL, a data-URL or bare base64; an array — several
response_formatstringbody
accepted for compatibility and ignored

Responses

200

{
  "created": 0,
  "cost_usd": "0.027",
  "data": [ { "b64_json": "iVBORw0KGgoAAAANSUhEUgAA…" } ]
}

202

{ "id": "img_9f2c1b7e…", "status": "processing", "estimated_cost_usd": "0.027" }

body is not JSON, no prompt, model/value/n outside its set, reference over 80 MB

FILE_DOWNLOAD_FAILED — the submit was rejected: the provider's 4xx is echoed with its code, the hold released

no key in the request, or the key is not ours

the balance does not cover this request's worst-case price

502

{
  "error": { "message": "The request was blocked by the content safety filter.",
             "type": "generation_error", "code": "GEMINI_RAI_MEDIA_FILTERED" },
  "cost_usd": "0"
}

the provider accepted the job and the generation failed; its code is in error.code

the submit failed on a 5xx or the network: whether the job was accepted is unknown

Details

Needs a key in Authorization: Bearer or x-api-key.

The gateway creates a job, reserves the money for it (under a per-user lock, so two concurrent requests cannot both pass on the same balance), submits it to the provider and polls for up to 150s. If that is enough you get 200 with the images in b64_json; if not, 202 with { id, status, estimated_cost_usd }, and the result is fetched from GET /v1/images/jobs/{job_id}. The reserve stays in place, so the money is not released while the generation runs.

The reply never contains a provider CDN link: when the provider answers with a URL, the gateway downloads the image itself and returns base64. That is why response_format is accepted and ignored — the result is always b64_json. Unknown body fields are dropped silently too: only what the model's allowlist knows is forwarded.

About the fields. An unknown or admin-disabled model, a missing prompt, an n that is not an integer or falls outside 1–10, and a parameter value outside its own model's set are all a 400 on our side, before the provider. The aspect_ratio set depends on the model: some take more (plus 3:2, 2:3, 21:9), and some take orientation instead; a field the model does not have is dropped silently. Neither resolution nor the framing affects the price: the tariff is per image. n is not forwarded to the provider at all — it sizes the reserve, while the charge follows the number of images that came back (none came back — then n). A provider submit failure on a 5xx or the network leaves the money reserved and returns the id in the error body: the reconciler finishes the job, or releases the reserve if the provider never took it. A provider 4xx, by contrast, is a definitive rejection: the job fails at once and the reserve is released without waiting for the reconciler.

Money: a finished generation reports cost_usd, exactly what was charged; a running one reports estimated_cost_usd, the size of the reserve. A failed generation is not billed and comes back with "cost_usd": "0". Media billing is ours, kept apart from text. Errors answer in one shape; code is there when the provider gave one, and a submit rejection adds the job's id: { "error": { "message": "...", "type": "...", "code": "PROVIDER_CODE" } }.

Reference images (image-to-image, same character or style) go in the body as image — a string or an array; images, image_url, image_urls, input_image, init_image and ref_images are accepted too. A value may be a public URL, a data-URL or bare base64, and mixing the forms in one request is fine: a URL is not fetched by us and reaches the provider as a link, while base64 is decoded and sent as bytes. The gateway recognises bytes by their signature (PNG, JPEG, GIF, WebP) and caps each input at 80 MB — anything larger is a 400 before the provider is called. An input that passed our cap may still be refused by the model under its own rules: its code then arrives in error.codeFILE_TOO_LARGE for size, FILE_TYPE_NOT_ALLOWED for type. The gateway puts no limit on the number of references; how many the model actually uses is up to it. A value that parses neither as a URL nor as base64 is dropped silently: the request goes out without that reference and without an error.

A result stays retrievable for 7 days. After that the bytes move out of the database into the archive, result_url is cleared, and the same GET /v1/images/jobs/{job_id} answers { "status": "completed", "archived": true, "data": [] }. That means expired, not "the generation produced nothing" — store the images on your side as soon as you get them. The job row and its money are never deleted: retention moves the payload only.

Code examples
curl https://api.teamtoken.store/v1/images/generations \
  -H "Authorization: Bearer sk-…" \
  -H "Content-Type: application/json" \
  -d '{ "model": "nano-banana-pro", "prompt": "a red cube on white", "aspect_ratio": "1:1", "resolution": "1K" }'
curl https://api.teamtoken.store/v1/images/generations \
  -H "Authorization: Bearer sk-…" \
  -H "Content-Type: application/json" \
  -d '{ "model": "nano-banana-pro",
        "prompt": "the same person, in a forest, golden hour",
        "images": ["https://example.com/ref1.jpg", "data:image/jpeg;base64,/9j/4AAQ..."],
        "aspect_ratio": "1:1" }'
import requests

r = requests.post(
    "https://api.teamtoken.store/v1/images/generations",
    headers={"Authorization": "Bearer sk-…"},
    json={"model": "nano-banana-pro", "prompt": "a red cube on white",
          "aspect_ratio": "1:1", "resolution": "1K"},
    timeout=180,  # the gateway polls the provider for up to 150s
)
body = r.json()

if r.status_code == 202:
    # too slow for one request: the job keeps running and the money stays reserved
    print(body["id"], body["estimated_cost_usd"])
else:
    print(body["cost_usd"], body["data"][0]["b64_json"][:32])
Request
https://api.teamtoken.store/v1

The panel calls this domain; in your own code use the address above.

The key is never stored: it lives in this tab until you reload the page.

an image model id from the catalog

what to draw; we check it is there, the model checks length (EMPTY_PROMPT)

how many images, 1–10; it sizes the reserve, the charge follows the result

aspect ratio; the accepted set depends on the model

resolution; not every model has it (nano-banana-pro does)

orientation — for the models that do not take aspect_ratio

a reference: a URL, a data-URL or bare base64; an array — several

accepted for compatibility and ignored

This request really goes out. A generation is billed, and its price is reserved the moment the job is queued.

Response

Press “Send request” above and the answer shows up here.