Texto e streaming
Chame modelos de texto nos formatos oficiais da OpenAI, da Anthropic ou do Google Gemini e trate corretamente conversas, ferramentas, streaming e custos.
Escolha um modelo de texto disponível
Consulte /api/v1/models?modality=text&includeSchema=1 com autenticação e confirme enabled e available. Ferramentas, imagens, campos de raciocínio e saída estruturada precisam ser aceitos pelo inputSchema. Compatibilidade de protocolo não garante todos os recursos em cada modelo.
O mesmo ID de modelo pode ser chamado por qualquer um dos protocolos abaixo, não importa quem desenvolveu o modelo: dá para chamar modelos de outras empresas no formato da Anthropic ou modelos que não são do Google no formato Gemini.
Protocolos suportados
Os quatro protocolos de texto compartilham a mesma chave de API, o mesmo catálogo de modelos e as mesmas regras de limite de requisições, validação de parâmetros e cobrança. Só muda o formato da requisição e da resposta. Escolha o protocolo que o seu código ou SDK já usa.
| Protocolo | Endpoint | Cabeçalho da chave |
|---|---|---|
| OpenAI Chat Completions | POST /v1/chat/completions | Authorization: Bearer |
| OpenAI Responses | POST /v1/responses | Authorization: Bearer |
| Anthropic Messages | POST /v1/messages | x-api-key ou Authorization: Bearer |
| Google Gemini | POST /v1beta/models/{model}:generateContent | x-goog-api-key ou Authorization: Bearer |
| Google Gemini com streaming | POST /v1beta/models/{model}:streamGenerateContent | x-goog-api-key ou Authorization: Bearer |
| Streaming nativo | POST /api/v1/jobs/stream | Authorization: Bearer |
Em /v1 e /v1beta, respostas e erros seguem o respectivo protocolo, sem o envelope nativo {code,msg,data,request_id}; não use um cliente que só lê body.data para interpretá-los. No formato Gemini, o ID do modelo vai no caminho da URL; mantenha as barras do ID como estão, sem codificá-las.
A chave vai somente nos cabeçalhos da requisição. x-api-key e x-goog-api-key só valem em /v1 e /v1beta; a API nativa /api/v1 aceita apenas Authorization: Bearer. O parâmetro de consulta ?key= nunca é aceito: uma chave que entra na URL fica gravada no histórico do navegador, em servidores proxy e em logs de acesso. Os SDKs oficiais enviam a chave por cabeçalho e não são afetados.
Use os SDKs oficiais
Troque apenas a URL base e a chave; o resto segue o uso normal de cada SDK.
| SDK | URL base |
|---|---|
OpenAI (openai) | https://api.spicyapi.ai/v1 |
Anthropic (anthropic) | https://api.spicyapi.ai |
Google GenAI (google-genai) | https://api.spicyapi.ai |
Os SDKs da Anthropic e do Google acrescentam /v1 ou /v1beta por conta própria, por isso a URL base deles não inclui o caminho da versão; no SDK da OpenAI, a URL base precisa incluir /v1. Os três exemplos em Python abaixo leem das variáveis de ambiente a SPICY_API_KEY e o SPICY_MODEL escolhido no catálogo atual.
import os
from openai import OpenAI
client = OpenAI(api_key=os.environ["SPICY_API_KEY"], base_url="https://api.spicyapi.ai/v1")
reply = client.chat.completions.create(
model=os.environ["SPICY_MODEL"],
messages=[{"role": "user", "content": "Explain a rainbow in one sentence."}],
)
print(reply.choices[0].message.content)import os
import anthropic
client = anthropic.Anthropic(api_key=os.environ["SPICY_API_KEY"], base_url="https://api.spicyapi.ai")
message = client.messages.create(
model=os.environ["SPICY_MODEL"],
max_tokens=256,
messages=[{"role": "user", "content": "Explain a rainbow in one sentence."}],
)
print(message.content[0].text)import os
from google import genai
from google.genai import types
client = genai.Client(
api_key=os.environ["SPICY_API_KEY"],
http_options=types.HttpOptions(base_url="https://api.spicyapi.ai"),
)
response = client.models.generate_content(
model=os.environ["SPICY_MODEL"],
contents="Explain a rainbow in one sentence.",
)
print(response.text)Para que novas tentativas de rede não gerem cobrança duplicada, gere um Idempotency-Key para cada ação do usuário, salve-o de forma persistente e envie-o pela opção de cabeçalhos personalizados do SDK (nos SDKs da OpenAI e da Anthropic, é extra_headers).
Mapeamento de parâmetros por protocolo
Os campos de cada protocolo são primeiro convertidos para os nomes de parâmetro unificados da plataforma e depois validados pelo inputSchema do modelo escolhido. Um parâmetro que o modelo não suporta retorna 400, em vez de ser descartado em silêncio enquanto a requisição é cobrada normalmente. A tabela abaixo mostra só a correspondência, e “—” indica que o protocolo não tem campo equivalente. Se um modelo aceita ou não um parâmetro, e em que faixa de valores, quem define é o inputSchema dele.
| Parâmetro unificado | Chat Completions | Responses | Messages | Gemini |
|---|---|---|---|---|
messages | messages | instructions, input | system, messages | systemInstruction, contents |
max_tokens | max_tokens, max_completion_tokens | max_output_tokens | max_tokens | generationConfig.maxOutputTokens |
temperature | temperature | temperature | temperature | generationConfig.temperature |
top_p | top_p | top_p | top_p | generationConfig.topP |
tools | tools | tools (tipo function) | tools (ferramentas personalizadas) | tools[].functionDeclarations |
tool_choice | tool_choice | tool_choice | tool_choice | toolConfig.functionCallingConfig |
response_format | response_format | text.format | — | generationConfig.responseMimeType com responseSchema ou responseJsonSchema |
reasoning_effort | reasoning_effort | reasoning.effort | — | generationConfig.thinkingConfig |
Campos fora da tabela também são convertidos para parâmetros unificados e validados pelo modelo; o que não estiver no inputSchema retorna 400. Por exemplo, se o modelo não aceita stop, retornam 400 tanto stop no Chat quanto stop_sequences no Messages e generationConfig.stopSequences no Gemini. Hoje nenhum modelo de texto inclui seed no inputSchema, então seed no Chat e generationConfig.seed no Gemini também retornam 400.
Algumas exceções:
- Campos que só fazem sentido na plataforma de origem de cada protocolo são ignorados e não afetam a geração:
user,metadataestoreno Chat;user,metadata,storeeincludeno Responses;metadatano Messages. - Responses não suporta
previous_response_id; envie o histórico completo eminput. - No Gemini,
safetySettingsé aceito, mas não tem efeito;cachedContente ferramentas executadas no servidor, comogoogleSearchecodeExecution, não são suportados e retornam 400. Imagens podem ser enviadas porinlineData(base64) oufileData.fileUri(URL https); se o modelo aceita imagens, quem define é o inputSchema.
Envie um fluxo Chat
No servidor, configure SPICY_API_KEY, SPICY_MODEL obtido do catálogo e um SPICY_IDEMPOTENCY_KEY salvo para a operação. O exemplo precisa de jq. Você pode trocar o prompt, mas os campos devem seguir o esquema atual.
jq -n --arg model "$SPICY_MODEL" '{
model: $model,
messages: [{role: "user", content: "Explain a rainbow in one sentence."}],
stream: true
}' > chat-request.json
curl --fail-with-body --no-buffer --max-time 120 \
https://api.spicyapi.ai/v1/chat/completions \
-H "Authorization: Bearer $SPICY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $SPICY_IDEMPOTENCY_KEY" \
--data-binary @chat-request.jsonEnvie uma requisição Gemini
As variáveis de ambiente são as mesmas da seção anterior. O ID do modelo vai na URL, e o corpo da requisição não leva model. Coloque a URL entre aspas duplas e escreva ${SPICY_MODEL}, para que o shell não interprete os dois-pontos seguintes como modificador de variável.
jq -n '{
systemInstruction: {parts: [{text: "Answer in one sentence."}]},
contents: [{role: "user", parts: [{text: "Explain a rainbow."}]}],
generationConfig: {temperature: 0.7, maxOutputTokens: 256}
}' > gemini-request.json
curl --fail-with-body --no-buffer --max-time 120 \
"https://api.spicyapi.ai/v1beta/models/${SPICY_MODEL}:streamGenerateContent?alt=sse" \
-H "x-goog-api-key: $SPICY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $SPICY_IDEMPOTENCY_KEY" \
--data-binary @gemini-request.jsonSem streaming, troque :streamGenerateContent?alt=sse por :generateContent e leia a resposta em candidates[0].content.parts[].text; o uso fica em usageMetadata. Em uma requisição com streaming sem alt=sse, a resposta é um array JSON escrito aos poucos; os SDKs oficiais já incluem alt=sse sozinhos.
Conversas e ferramentas
No Chat sem streaming, leia choices[0].message; no fluxo, choices[].delta por evento SSE completo. Fragmentos TCP não delimitam JSON. Monte tool_calls por index e valide os argumentos completos antes da execução. Preserve a mensagem assistant e devolva o resultado com o tool_call_id correspondente. Responses exige o histórico completo em input e não aceita previous_response_id; use function_call_output com call_id. Messages usa blocos text, tool_use e tool_result em content; não dá para reaproveitar diretamente o formato de mensagens do Chat.
No Gemini, contents alterna entre dois papéis, user e model. Quando o modelo quer chamar uma ferramenta, ele retorna uma parte functionCall; depois da execução, transforme o resultado em uma parte functionResponse e devolva-a no próximo conteúdo user, com o mesmo name da chamada.
Conclusão, erros e desconexões
| Protocolo | Encerramento normal | Uso |
|---|---|---|
| Chat Completions | data: [DONE] | usage do último chunk |
| Responses | evento response.completed | response.usage dentro do evento |
| Messages | message_stop depois de message_delta | usage de message_delta |
| Gemini | último evento com finishReason, sem [DONE] | usageMetadata |
Antes de o fluxo começar, confira o status HTTP e o Content-Type; os erros vêm no JSON próprio de cada protocolo:
| Protocolo | Corpo do erro |
|---|---|
| Chat Completions, Responses | {"error": {"message", "type", "param", "code"}} |
| Messages | {"type": "error", "error": {"type", "message"}} |
| Gemini | {"error": {"code", "message", "status"}} |
Os códigos de status HTTP são os mesmos nos quatro protocolos. No Gemini, status usa os nomes de estado do Google: 400 é INVALID_ARGUMENT, 401 é UNAUTHENTICATED, 402 é FAILED_PRECONDITION, 403 é PERMISSION_DENIED, 404 é NOT_FOUND, 429 é RESOURCE_EXHAUSTED e 503 é UNAVAILABLE. Para decidir no código, use o código de status e o tipo de erro; não compare o texto de message.
Se ocorrer um erro depois que o fluxo começou, o Chat e o Gemini enviam um evento com um objeto error, o Responses envia um evento error e o Messages envia event: error; o cliente precisa tratar esses eventos de erro, timeouts e interrupções prematuras. Desative o buffering de respostas no proxy. Fechar o navegador ou acionar o AbortSignal só interrompe a recepção; não equivale a cancelar nem garante reembolso. Quando o servidor tem dados de uso confiáveis, uma desconexão feita pelo cliente continua sendo cobrada pelo uso efetivamente gerado; sem dados de uso, fechar a conexão não pode ser tratado como uma conclusão gratuita.
Cotação e cobrança
Para que o usuário confirme antes a cotação exata e o limite, use o fluxo nativo jobs/quote → jobs/stream com o mesmo model/input, quoteId e expectedCost. /v1 e /v1beta não aceitam esses campos e aplicam o preço da aceitação. Verifique tarefas e extrato para o valor final; o fim do texto não confirma a liquidação. reasoning_tokens já está incluído em completion_tokens, sem soma adicional.
Outros formatos
Os exemplos mostram envelopes de protocolo. Substitua MODEL_ID_FROM_CATALOG por um modelo atual que aceite os campos; não envie o marcador.
{
"model": "MODEL_ID_FROM_CATALOG",
"input": "Explain a rainbow in one sentence.",
"stream": false
}{
"model": "MODEL_ID_FROM_CATALOG",
"max_tokens": 256,
"messages": [{"role": "user", "content": "Explain a rainbow in one sentence."}],
"stream": false
}Continue lendo
Cotações e compatibilidade · Cobrança · Integração em produção · Diagnóstico e recuperação

