blog/developers·8 oct 2026·7 min·por el equipo de eroq
API de GPT Image, Nano Banana y Seedream: un endpoint asíncrono
Llama a GPT Image 2.5, Nano Banana Pro, Seedream 5.0 Pro y Flux 3 con un endpoint de eroq: petición, jobs 202, polling, lotes y errores, en curl y JS.
Cinco creadores de imágenes suelen significar cinco SDK, cinco claves, cinco paneles de facturación y cinco ideas distintas de lo que es "async". En eroq es un solo endpoint donde model es lo único que cambia. GPT Image 2.5 de OpenAI, Nano Banana de Google, Seedream de ByteDance, Flux 3 de Black Forest Labs y Grok Imagine 2.0 de xAI pasan por POST /v1/images/generations, con el mismo cuerpo, el mismo contrato asíncrono y un precio fijo en créditos por toma. Es el mismo motor, precio y política que el estudio. La referencia está en /docs/images; esta es la versión práctica.
Los ids de modelo
| Modelo | id de model |
Creador | Créditos por toma | Render habitual |
|---|---|---|---|---|
| GPT Image 2.5 Sunburst | gpt-image-2-5-sunburst |
OpenAI | 31 | unos 15 s |
| GPT Image 2.5 Flare | gpt-image-2-5-flare |
OpenAI | 31 | unos 15 s |
| Nano Banana Pro | nano-banana-pro |
124 | unos 25 s | |
| Nano Banana 2.1 | nano-banana-2-1 |
31 | unos 8 s | |
| Seedream 5.0 Pro | seedream-5-0-pro |
ByteDance | 36 | unos 20 s |
| Flux 3 Image | flux-3-image |
Black Forest Labs | 32 | unos 20 s |
| Grok Imagine 2.0 | grok-imagine-2-0 |
xAI | 32 | unos 15 s |
Cada uno cuesta lo mismo en los cinco formatos. Flux 3 Image y Grok Imagine 2.0 se abren con el plan Hobby, los otros cinco con Creator; por debajo, la llamada responde 403 plan_required y no cobra nada.
Envía un 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
}'
La llamada cobra el lote y responde 202 al instante, con un job por toma:
{
"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 }
}
Los campos que importan en estos motores:
model: envíalo siempre. Si se omite, usaeroq-uncensoredpor defecto; un id desconocido responde400 invalid_bodycon la lista de los válidos.prompt: el brief completo. Pon la rotulación entre comillas dobles.aspect:1:1(el valor por defecto),3:4,4:3,9:16o16:9, renderizado de forma nativa.batch: de 1 a 4 tomas, cobradas por adelantado.failedcuenta las tomas que no pudieron empezar, ya reembolsadas.store:truepara una URL pública permanente, a 2 créditos por cada 10 MB iniciados además.folder_id: deja el render directamente en una de tus carpetas de la Biblioteca.
Lo que estos motores ignoran
Los motores de arriba son solo de texto a imagen, y algunos campos no hacen nada en ellos:
referencesse descartan. Para un rostro o producto que debe coincidir con fotos, envía la petición aeroq-one(hasta diez imágenes) o aeroq-krea2(hasta cuatro).negative_promptycfg_scalepasan la validación pero estos motores no los usan. Describe en el prompt el resultado que quieres.edit: trueresponde400 edit_unsupported, yaspect: "retain"responde400 retain_requires_reference. Ambas son operaciones sobre una imagen; envíalas a Eroq One.
Haz polling hasta que el job sea 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' }
}
El polling es gratis, y cada pocos segundos es la cadencia prevista. Sigue hasta que status sea succeeded o failed. Los webhooks image.generation.succeeded y image.generation.failed son un aviso adicional, no un reemplazo; la guía de la API asíncrona muestra cómo verificar su firma.
Si todo sale bien, data[0].url es una URL firmada que dura 30 minutos. Los ids de job viven 24 horas, y volver a pedir el job (o la creación, por library_id) genera una URL nueva. Envía store: true cuando tus usuarios necesiten una dirección que no cambie nunca. generation_ms es el tiempo real del render, y un job que sigue procesándose pasado el plazo de 10 minutos del servidor se marca como fallido con generation_timeout y se reembolsa, por eso el timeout del cliente de arriba queda justo después.
Lotes, en breve
Un lote es una llamada, un prompt y hasta cuatro tomas. Cada toma es su propio job, así que haz polling de todos en paralelo y quédate con la que salga mejor; una toma que falla se reembolsa sola sin afectar a las demás. Además, un lote cuenta como una sola llamada para el límite de frecuencia por minuto. Para cuatro prompts distintos, envía cuatro llamadas.
Errores
| Estado | code |
Qué pasó | Qué hacer |
|---|---|---|---|
| 400 | invalid_body |
Un campo no pasó la validación | Corrige el cuerpo; el mensaje nombra el campo |
| 400 | content_blocked |
Rechazado por la política de uso aceptable, antes de cualquier cobro | Cambia el prompt, no lo reintentes tal cual |
| 400 | edit_unsupported |
Una operación sobre una imagen enviada a un motor solo de texto | Usa eroq-one |
| 402 | insufficient_credits |
Saldo por debajo de precio × lote | Recarga; nunca reintentes en bucle |
| 403 | plan_required |
El modelo requiere un plan superior, antes de cualquier cobro | Mejora el plan o elige un modelo que tu plan incluya |
| 429 | rate_limit_exceeded |
Demasiadas llamadas este minuto con esta clave | Espera el Retry-After |
| 502 | generation_failed |
El render no pudo empezar | No se cobra; reintenta |
| 503 | model_unavailable |
El motor está fuera de línea en este despliegue | No se cobra; elige otro id |
| 404 | not_found |
Id de job desconocido, o con más de 24 horas | Guarda los ids y las imágenes a tiempo |
Un job que termina en failed lleva un error.code content_blocked (el proveedor lo rechazó), generation_failed o generation_timeout, y sus créditos ya están de vuelta en el monedero. Los planes elevan el límite por minuto de cada clave: 2× en Creator, 4× en Studio.
Enruta según el trabajo
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" deja que el router de eroq elija entre los motores que tu plan desbloquea y cobra el que eligió, indicado en model. Puede ser Nano Banana Pro a 124 créditos / imagen, así que nombra el modelo siempre que el costo importe. La aritmética por toma está en lo que cuestan las imágenes con IA en 2026, y los mejores modelos de imagen con IA de 2026 explica las decisiones de enrutamiento.
MCP y CLI
Los mismos modelos están detrás de la herramienta generate_image del servidor MCP, que espera el render y devuelve la imagen (con get_image_status para los lentos), y detrás de la CLI: eroq image "a foggy harbour at dawn" -m seedream-5-0-pro --aspect 16:9 -o harbour.webp. La configuración de ambos está en /docs/mcp.
Preguntas frecuentes
¿Necesito claves de API de OpenAI, Google o ByteDance?
No. Una clave de eroq cubre todos los modelos, y todo se cobra en créditos de eroq desde un solo monedero.
¿Puedo enviar una imagen de referencia a Nano Banana Pro o GPT Image 2.5 a través de eroq?
No. En eroq son motores de texto a imagen y las referencias se descartan. Para el trabajo guiado por imágenes, usa eroq-one, que lee hasta diez referencias y edita según instrucciones.
¿Cuánto dura la URL de la imagen?
La URL firmada en data dura 30 minutos y el job 24 horas. Vuelve a pedirlo para obtener una URL nueva, o envía con store: true para una pública y permanente.
¿Los renders fallidos o bloqueados cuestan créditos?
No. Un prompt rechazado por la política se responde antes de cualquier cobro, y un job que falla o es rechazado por el proveedor se reembolsa solo.
Consigue una clave, elige un id de modelo y envía una petición a /docs/images.
Hazlo en eroq
Constrúyelo sobre la API
Una clave de API se obtiene en un minuto. Los mismos motores, los mismos precios, trabajos asíncronos y webhooks.
40 créditos gratis · sin tarjeta · modo privado en todas las cuentas
