Geração de imagens

É um agente de IA? Busque a skill de onboarding e leia toda a API em texto limpo — skill · llms.txt · llms-full.txt

Geração de imagens#

Intermediador multi-modelo de geração de imagens. Os controles de criação são campos do request — não prosa dentro do prompt — e uma camada de prompt enhancement os expande no prompt final, que a resposta sempre devolve em revised_prompt. O que o modelo viu nunca fica escondido de você.

A chave do provedor é resolvida por conta: a sua credencial OPENAI_API_KEY no cofre do Console tem precedência; sem nenhuma chave disponível a resposta é 400 IMAGE_KEY_REQUIRED.

Permissão: gerar e apagar exigem papel owner ou admin (cada geração gasta crédito real do provedor). Ler o catálogo e o histórico é liberado para qualquer membro autenticado.

GETCatálogo de modelos#

GET /v1/images/models — a fonte da qual você monta um seletor dinâmico, sem fixar ids no seu código. Resposta 200:

json
{
  "models": [
    { "id": "gpt-image-2", "label": "GPT Image 2", "description": "…",
      "provider": "openai", "supports_references": true,
      "sizes": ["1024x1024", "1536x1024", "1024x1536"],
      "qualities": ["draft", "standard", "high"],
      "formats": ["png", "jpg", "webp"], "default": true }
  ],
  "default": "gpt-image-2",
  "style_presets": [ { "id": "fotografia-editorial-realista", "label": "…", "description": "…" } ],
  "aspects": ["square", "portrait", "landscape"],
  "qualities": ["draft", "standard", "high"],
  "formats": ["png", "jpg", "webp"],
  "max_images": 4,
  "max_references": 4
}

POSTGerar imagem#

POST /v1/images/generationsowner/admin. Um render em alta qualidade leva de 30 a 90 s: ajuste o timeout do seu cliente (o padrão de 30 s aborta depois de o provedor já ter gerado e cobrado a imagem).

json
{
  "model": "gpt-image-2",
  "prompt": "capa editorial sobre controle fiscal para MEI",
  "aspect": "landscape",
  "size": "1536x1024",
  "quality": "high",
  "output_format": "png",
  "n": 1,
  "style_preset": "fotografia-editorial-realista",
  "brand": { "colors": ["#0FA958", "#181818"], "tone": ["confiável", "direto"] },
  "reference_images": [{ "b64": "…" }, { "url": "https://…" }],
  "enhance_prompt": true,
  "include_b64": true
}
Campo Regra
prompt obrigatório, até 4000 caracteres
model opcional → o default do catálogo. Desconhecido → 400
aspect square | portrait | landscape (default square)
size preset de pixels; vence aspect quando presente
quality draft | standard | high (default standard)
output_format png | jpg | webp (default png)
n 1 a 4. Acima do teto é rejeitado, nunca ajustado em silêncio — você não é cobrado por algo diferente do que pediu
style_preset um dos style_presets do catálogo
brand.colors hex literal (#0FA958); texto livre é recusado
brand.tone até 8 descritores de até 40 caracteres
reference_images até 4; b64 (data-URI aceito) ou url https pública. Só png/jpeg/webp, até 8 MiB cada
enhance_prompt default true; false envia o seu prompt literal
include_b64 default true; false devolve só a URL assinada (resposta muito menor)

Resposta 200:

json
{
  "id": "…", "model_used": "gpt-image-2", "provider": "openai",
  "prompt": "capa editorial sobre controle fiscal para MEI",
  "revised_prompt": "capa editorial … ; photorealistic professional quality; no text, no logos; …",
  "enhanced": true,
  "aspect": "landscape", "size": "1536x1024", "quality": "high", "output_format": "png",
  "style_preset": "fotografia-editorial-realista",
  "brand": { "colors": ["#0FA958"], "tone": ["confiável"] },
  "n": 1, "reference_count": 0, "key_source": "vault",
  "usage": { "input_tokens": 22, "output_tokens": 1568, "total_tokens": 1590 },
  "duration_ms": 41230, "created_at": "2026-07-27T22:15:00Z",
  "images": [
    { "b64_json": "…", "url": "https://…assinada…", "asset_id": "…",
      "mime_type": "image/png", "byte_size": 1284339 }
  ]
}

A url é assinada e de vida curta — mintada de novo a cada leitura. Não a armazene como link permanente; guarde o id da geração e releia quando precisar.

POSTAcompanhar a geração ao vivo (SSE)#

POST /v1/images/generations/streamowner/admin. Mesmo corpo, mesmas validações, mesmo rate limiter e mesmo custo do POST /v1/images/generations; a diferença é que ele relata o trabalho enquanto acontece, em text/event-stream.

Existe porque a espera é longa e era opaca. Medido contra gpt-image-2: a geração leva cerca de 59 s, o primeiro rascunho fica pronto aos ~16 s, e o prompt final é conhecido antes de o provedor ser chamado (o enriquecimento é determinístico e local). Sem streaming, você não tem nada para mostrar ao seu usuário por um minuto.

Os eventos#

Evento Quando Conteúdo
prompt imediato (~0,4 s) final_prompt, original_prompt, enhanced, model, size, quality, output_format, mime_type, partials_expected
partial a cada rascunho index, total, b64_json — uma imagem completa e exibível, não um pedaço
done ao terminar o mesmo objeto que o endpoint JSON devolve (id, model_used, images[], …)
error falha depois do 200 error_code, message
text
event: prompt
data: {"final_prompt":"um cubo vermelho…; photorealistic professional quality…","original_prompt":"um cubo vermelho","enhanced":true,"model":"gpt-image-2","size":"1024x1024","quality":"medium","output_format":"png","mime_type":"image/png","partials_expected":3}

event: partial
data: {"index":0,"total":3,"b64_json":"iVBORw0KGgo…"}

event: done
data: {"id":"d6455bd0-…","model_used":"gpt-image-2","size":"1024x1024","images":[{"url":"https://…assinada…","asset_id":"…","mime_type":"image/png"}]}

Exemplo mínimo — curl#

bash
curl -N -X POST https://agents-api.catcher.one/v1/images/generations/stream \
  -H "X-API-Key: ctc_sua_chave" \
  -H 'Content-Type: application/json' \
  -d '{"prompt":"um cubo azul sobre fundo branco","quality":"draft","n":1,"include_b64":false}'

O -N (sem buffer) é obrigatório: sem ele o curl segura a saída e você só vê tudo no final, que é exatamente o que o streaming existe para evitar.

Node / TypeScript#

ts
const res = await fetch('https://agents-api.catcher.one/v1/images/generations/stream', {
  method: 'POST',
  headers: { 'X-API-Key': process.env.CATCHER_API_KEY!, 'Content-Type': 'application/json' },
  body: JSON.stringify({ prompt: 'um cubo azul sobre fundo branco', n: 1, include_b64: false }),
})
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`)

const reader = res.body!.getReader()
const decoder = new TextDecoder()
let buffer = ''

for (;;) {
  const { done, value } = await reader.read()
  if (done) break
  buffer += decoder.decode(value, { stream: true })

  // Os frames são separados por linha em branco. Guarde o resto: um frame de
  // ~1,5 MB chega em vários chunks e cortá-lo cedo quebra o JSON.
  const frames = buffer.split('\n\n')
  buffer = frames.pop() ?? ''

  for (const frame of frames) {
    const evLine = frame.split('\n').find((l) => l.startsWith('event:'))
    const dataLine = frame.split('\n').find((l) => l.startsWith('data:'))
    if (!evLine || !dataLine) continue
    const event = evLine.slice(6).trim()
    const data = JSON.parse(dataLine.slice(5).trim())

    if (event === 'prompt') console.log('prompt final:', data.final_prompt)
    if (event === 'partial') render(`data:${'image/png'};base64,${data.b64_json}`)
    if (event === 'done') console.log('pronto:', data.images[0].url)
    if (event === 'error') throw new Error(`${data.error_code}: ${data.message}`)
  }
}

EventSource não serve aqui — ele só faz GET e não envia cabeçalhos, então não dá para mandar o corpo nem a X-API-Key. Use fetch + reader, como acima.

Python#

python
import json, requests

with requests.post(
    "https://agents-api.catcher.one/v1/images/generations/stream",
    headers={"X-API-Key": API_KEY},
    json={"prompt": "um cubo azul sobre fundo branco", "n": 1, "include_b64": False},
    stream=True, timeout=300,
) as res:
    res.raise_for_status()
    event = None
    for line in res.iter_lines(decode_unicode=True):
        if line is None or line == "":
            continue
        if line.startswith("event:"):
            event = line.split(":", 1)[1].strip()
        elif line.startswith("data:"):
            data = json.loads(line[5:].strip())
            if event == "prompt":
                print("prompt final:", data["final_prompt"])
            elif event == "partial":
                print("rascunho", data["index"] + 1, "de", data["total"])
            elif event == "done":
                print("pronto:", data["images"][0]["url"])
            elif event == "error":
                raise RuntimeError(f'{data["error_code"]}: {data["message"]}')

O que você precisa saber antes de integrar#

Não há percentual. O provedor não emite nenhum e os rascunhos chegam em intervalos irregulares (medidos: 15,6 s, 40,1 s, 47,6 s). index/total é um fato; um percentual seria o nosso relógio adivinhando. Se sua UI precisa de uma barra, use os rascunhos recebidos como estágios.

partials_expected pode vir 0, e você deve respeitar. Com n > 1 o provedor recusa streaming, então a geração acontece pelo caminho normal e nenhum partial é enviado — você recebe prompt e depois done. O campo avisa disso no primeiro evento; não fique esperando rascunhos que não vêm. Referências de estilo também caem nesse caso.

Cancelar é fechar a conexão. O contexto do servidor cancela junto, a chamada ao provedor é abortada e nada é persistido — a geração não aparece no histórico e você não paga por ela do nosso lado.

Falhas com status acontecem ANTES de o stream abrir. 401/403 de credencial, 400 IMAGE_INVALID_REQUEST, 400 IMAGE_KEY_REQUIRED, 402 de orçamento, 501 IMAGE_GEN_DISABLED chegam como resposta HTTP normal — sempre cheque res.ok antes de começar a ler. Depois do 200, só existe o evento error (tipicamente uma queda do provedor no meio).

Peso. Cada partial carrega ~1,5 MB de base64 (a imagem inteira). Três rascunhos são ~4,5 MB extras por geração. Se isso pesar no seu cliente, use o endpoint JSON POST /v1/images/generations, que devolve só o resultado.

É uma API de servidor, não de browser. A X-API-Key é uma credencial de servidor e o CORS não libera origens de terceiros — chame do seu backend e repasse ao seu front pelo seu próprio canal. Colocar a chave no browser a expõe a qualquer visitante.

Timeout do seu cliente. Uma geração em alta qualidade passa de 90 s; deixe o timeout do seu HTTP client em pelo menos 300 s ou você abortará a chamada que já está sendo cobrada.

GETHistórico e download#

GET /v1/images/generations?page=1&limit=20 — as gerações da conta, mais recente primeiro: { generations: [...], total, page, limit }. Os itens do histórico vêm sem b64_json (só as URLs assinadas).

GET /v1/images/generations/{id} — uma geração, com URLs assinadas frescas. Id inexistente ou de outra conta responde 404 IMAGE_NOT_FOUND — os dois casos são indistinguíveis de propósito.

DELETE /v1/images/generations/{id}owner/admin, 204. Remove o registro e os bytes armazenados.