Un descuento en los planes premium al registrarteConsigue tu descuento

blog/developers·8 oct 2026·6 min·por el equipo de eroq

GPT Image, Nano Banana, Seedream API — one endpoint, async jobs

Call GPT Image 2.5, Nano Banana Pro, Seedream 5.0 Pro and Flux 3 through one eroq endpoint: request, 202 jobs, polling, batches and errors, in curl and JS.


Five image makers usually means five SDKs, five keys, five billing dashboards and five ideas of what "async" means. On eroq it is one endpoint where model is the only thing that changes. OpenAI's GPT Image 2.5, Google's Nano Banana, ByteDance's Seedream, Black Forest Labs' Flux 3 and xAI's Grok Imagine 2.0 all go through POST /v1/images/generations, with the same body, the same asynchronous contract and a flat credit price per take. It is the same engine, price and policy as the studio. The reference lives at /docs/images; this is the practical version.

The model ids

Model model id Maker Credits per take Typical render
GPT Image 2.5 Sunburst gpt-image-2-5-sunburst OpenAI 31 about 15 s
GPT Image 2.5 Flare gpt-image-2-5-flare OpenAI 31 about 15 s
Nano Banana Pro nano-banana-pro Google 124 about 25 s
Nano Banana 2.1 nano-banana-2-1 Google 31 about 8 s
Seedream 5.0 Pro seedream-5-0-pro ByteDance 36 about 20 s
Flux 3 Image flux-3-image Black Forest Labs 32 about 20 s
Grok Imagine 2.0 grok-imagine-2-0 xAI 32 about 15 s

Every one is open to every account, pay-as-you-go included, at the same price for all five formats.

Submit a render

curl https://eroq.ai/v1/images/generations \
  -H "Authorization: Bearer $EROQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "nano-banana-pro",
    "prompt": "An aerial view of a harbour city at blue hour, lit windows and light trails along the quays, layered haze between the towers, crisp architectural detail, cinematic wide shot",
    "aspect": "16:9",
    "batch": 2
  }'

The call charges the batch and answers 202 at once, with one job per take:

{
  "object": "image.generation",
  "jobs": [
    { "id": "b7e6c2d4-…", "poll": "/v1/images/generations/b7e6c2d4-…" },
    { "id": "4f19a0e3-…", "poll": "/v1/images/generations/4f19a0e3-…" }
  ],
  "created": 1791450000,
  "model": "nano-banana-pro",
  "failed": 0,
  "usage": { "credits_spent": 248, "credits_remaining": 752 }
}

The fields that matter on these engines:

  • model: always send it. Omitted, it defaults to eroq-uncensored; an unknown id answers 400 invalid_body with the list of valid ones.
  • prompt: the whole brief. Put lettering in double quotes.
  • aspect: 1:1 (the default), 3:4, 4:3, 9:16 or 16:9, rendered natively.
  • batch: 1 to 4 takes, charged upfront. failed counts takes that could not start, already refunded.
  • store: true for a permanent public URL, at 2 credits per started 10 MB on top.
  • folder_id: drops the render straight into one of your Library folders.

What these engines ignore

The engines above are text-to-image only, and a few fields do nothing on them:

  • references are dropped. For a face or product that must match photos, send the request to eroq-one (up to ten pictures) or eroq-krea2 (up to four).
  • negative_prompt and cfg_scale pass validation but are not used by these engines. Describe the result you want in the prompt.
  • edit: true answers 400 edit_unsupported, and aspect: "retain" answers 400 retain_requires_reference. Both are picture operations; route them to Eroq One.

Poll until the job is terminal

const BASE = 'https://eroq.ai/v1'
const headers = {
  Authorization: `Bearer ${process.env.EROQ_API_KEY}`,
  'Content-Type': 'application/json',
}
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))

export async function generate(body) {
  const res = await fetch(`${BASE}/images/generations`, { method: 'POST', headers, body: JSON.stringify(body) })
  const json = await res.json()
  if (!res.ok) throw Object.assign(new Error(json.error.message), { status: res.status, code: json.error.code })
  // One job per take: each succeeds, or fails and refunds, on its own
  return Promise.all(json.jobs.map(job => waitFor(job.id)))
}

async function waitFor(id, { every = 2500, timeout = 11 * 60_000 } = {}) {
  const started = Date.now()
  while (Date.now() - started < timeout) {
    const res = await fetch(`${BASE}/images/generations/${id}`, { headers })
    const job = await res.json()
    if (!res.ok) return { id, error: job.error?.code ?? `http_${res.status}` }
    if (job.status === 'succeeded') {
      const [image] = job.data
      return { id, url: image.url, libraryId: image.library_id, ms: job.generation_ms }
    }
    if (job.status === 'failed') return { id, error: job.error.code } // credits already back
    await sleep(every) // processing: phase is "queued" or "generating"
  }
  return { id, error: 'client_timeout' }
}

Polls are free, and every few seconds is the intended cadence. Keep going until status is succeeded or failed. The image.generation.succeeded and image.generation.failed webhooks are a notification on top, not a replacement; the async API guide shows how to verify their signature.

On success, data[0].url is a signed URL that lasts 30 minutes. Job ids live 24 hours, and re-fetching the job (or the creation, by library_id) mints a fresh URL. Send store: true when your users need an address that never changes. generation_ms is the wall-clock time of the render, and a job still processing past the 10-minute server deadline is failed with generation_timeout and refunded, which is why the client timeout above sits just past it.

Batches, briefly

A batch is one call, one prompt and up to four takes. Each take is its own job, so poll them in parallel and keep whichever comes back best; a take that fails refunds itself without touching the others. A batch also counts as a single call against the per-minute rate limit. For four different prompts, send four calls.

Errors

Status code What happened What to do
400 invalid_body A field failed validation Fix the body; the message names the field
400 content_blocked Refused by the acceptable-use policy, before any charge Change the prompt, do not retry as is
400 edit_unsupported A picture operation sent to a text-only engine Use eroq-one
402 insufficient_credits Balance below price × batch Top up; never retry in a loop
429 rate_limit_exceeded Too many calls this minute on this key Wait for Retry-After
502 generation_failed The render could not start Not charged; retry
503 model_unavailable The engine is offline on this deployment Not charged; pick another id
404 not_found Unknown job id, or older than 24 hours Store ids and images promptly

A job that ends failed carries error.code content_blocked (the provider refused it), generation_failed or generation_timeout, and its credits are already back in the wallet. Plans raise the per-minute cap on every key: 2× on Creator, 4× on Studio.

Route by job

const ENGINE = {
  text: 'gpt-image-2-5-sunburst', // posters, packaging, UI mockups
  keyArt: 'gpt-image-2-5-flare', // dramatic, cinematic frames
  detail: 'seedream-5-0-pro', // texture, architecture, landscapes
  hero: 'nano-banana-pro', // the final frame, top fidelity
  draft: 'nano-banana-2-1', // fast iteration
  explore: 'grok-imagine-2-0', // many directions, characterful
  daylight: 'flux-3-image', // natural light and typography
}

model: "auto" lets eroq's router pick among the engines your plan unlocks and bills the one it chose, reported back in model. That can be Nano Banana Pro at 124 credits / image, so name the model whenever cost matters. The per-take arithmetic is in what AI images cost in 2026, and the best AI image models of 2026 explains the routing choices.

MCP and CLI

The same models sit behind the MCP server's generate_image tool, which waits for the render and returns the picture (with get_image_status for slow ones), and behind the CLI: eroq image "a foggy harbour at dawn" -m seedream-5-0-pro --aspect 16:9 -o harbour.webp. Setup for both is in /docs/mcp.

FAQ

Do I need OpenAI, Google or ByteDance API keys?

No. One eroq key covers every model, and everything bills in eroq credits from one wallet.

Can I send a reference image to Nano Banana Pro or GPT Image 2.5 through eroq?

No. On eroq they are text-to-image engines and references are dropped. For picture-driven work, use eroq-one, which reads up to ten references and edits by instruction.

How long does the image URL last?

The signed URL in data lasts 30 minutes and the job 24 hours. Re-fetch for a fresh URL, or submit with store: true for a permanent public one.

Do failed or blocked renders cost credits?

No. A prompt refused by policy is answered before any charge, and a job that fails or is refused upstream refunds itself.

Grab a key, pick a model id and send one request to /docs/images.

Etiquetasimage-apigpt-imagenano-bananaseedreamflux-3async

Crea esto con los modelos detrás del artículo: empieza con 50 créditos gratis o explora todos los motores y sus precios.