API OpenAI com Python e FastAPI é uma combinação adequada para criar um backend que recebe mensagens, valida os dados, chama um modelo da OpenAI e devolve uma resposta em JSON. A chave permanece no servidor, sem ser exposta ao navegador ou ao aplicativo do usuário.
Neste tutorial, você criará um endpoint POST /api/responder usando o SDK oficial assíncrono da OpenAI e a Responses API. O exemplo também inclui validação com Pydantic, CORS restrito, tratamento de erros, teste com curl e uma opção de streaming por Server-Sent Events.
Documentação e exemplos revisados em 10 de outubro de 2026. O código não foi executado neste artigo; valide dependências, modelo, limites e comportamento no seu próprio ambiente antes de usar em produção.
O que será construído
A arquitetura terá três partes:
- um cliente envia uma mensagem ao seu endpoint FastAPI;
- o servidor valida o corpo, acrescenta instruções e chama a OpenAI API;
- o servidor devolve apenas os campos necessários ao cliente.
Navegador ou aplicativo
|
| POST /api/responder
v
Backend FastAPI
- autenticação do seu usuário
- validação
- limites
- chave da OpenAI
|
| Responses API
v
OpenAI API
O frontend nunca deve receber OPENAI_API_KEY. Ele chama seu backend, e o backend chama a OpenAI.
Pré-requisitos
| Item | Recomendação |
|---|---|
| Python | Python 3.10 ou mais recente, conforme o SDK oficial atual |
| Conta | Conta na OpenAI Platform com projeto e faturamento configurados |
| Chave | Chave de projeto armazenada em variável de ambiente |
| Framework | FastAPI com servidor ASGI |
| SDK | Biblioteca oficial openai |
| Modelo | Model ID atual escolhido conforme tarefa e orçamento |
O SDK Python oficial oferece clientes síncrono e assíncrono. Como os endpoints FastAPI normalmente são assíncronos, este exemplo usa AsyncOpenAI para não bloquear desnecessariamente o event loop enquanto espera a resposta HTTP.
1. Crie o projeto e instale as dependências
mkdir openai-fastapi
cd openai-fastapi
python -m venv .venv
Ative o ambiente no macOS ou Linux
source .venv/bin/activate
Ative no Windows PowerShell
.venv\Scripts\Activate.ps1
Instale FastAPI, o servidor e o SDK:
python -m pip install "fastapi[standard]" openai python-dotenv
fastapi[standard] instala dependências recomendadas, incluindo o servidor necessário para executar a aplicação. O pacote python-dotenv é usado apenas para carregar um arquivo local durante o desenvolvimento.
Em produção, registre versões testadas em um arquivo de lock. Não presuma que uma atualização automática de dependência manterá o mesmo comportamento.
2. Configure as variáveis de ambiente
Crie um arquivo chamado .env:
OPENAI_API_KEY=sua_chave_aqui
OPENAI_MODEL=gpt-6-luna
FRONTEND_ORIGINS=http://localhost:3000
Adicione o arquivo ao .gitignore:
.env
.venv/
__pycache__/
*.pyc
O modelo padrão do tutorial é gpt-6-luna, opção atual voltada a tarefas focadas e de alto volume. Para trabalho mais complexo, avalie modelos como gpt-6.1-sol ou gpt-6-astra no catálogo oficial.
3. Crie o arquivo main.py
Este exemplo:
- falha na inicialização se a chave não estiver configurada;
- aceita apenas mensagens entre 1 e 8 mil caracteres;
- permite uma instrução opcional limitada;
- não expõe mensagens internas de erro ao usuário;
- não armazena o objeto Response por padrão;
- restringe CORS a origens configuradas.
import os
from typing import Final
import openai
from dotenv import load_dotenv
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from openai import AsyncOpenAI
from pydantic import BaseModel, Field
load_dotenv()
api_key = os.getenv("OPENAI_API_KEY")
if not api_key:
raise RuntimeError(
"Defina OPENAI_API_KEY como variável de ambiente."
)
MODEL: Final[str] = os.getenv("OPENAI_MODEL", "gpt-6-luna")
client = AsyncOpenAI(api_key=api_key)
app = FastAPI(
title="API OpenAI com FastAPI",
version="1.0.0",
)
allowed_origins = [
origin.strip()
for origin in os.getenv(
"FRONTEND_ORIGINS",
"http://localhost:3000",
).split(",")
if origin.strip()
]
app.add_middleware(
CORSMiddleware,
allow_origins=allowed_origins,
allow_credentials=False,
allow_methods=["GET", "POST"],
allow_headers=["Content-Type", "Authorization"],
)
class ChatRequest(BaseModel):
mensagem: str = Field(
min_length=1,
max_length=8_000,
)
instrucao: str | None = Field(
default=None,
max_length=2_000,
)
class ChatResponse(BaseModel):
resposta: str
model: str
response_id: str
@app.get("/health")
async def health() -> dict[str, str]:
return {"status": "ok"}
@app.post(
"/api/responder",
response_model=ChatResponse,
)
async def responder(payload: ChatRequest) -> ChatResponse:
instructions = (
payload.instrucao
or (
"Responda em português do Brasil. "
"Se não tiver informação suficiente, diga isso. "
"Não invente dados, fontes ou ações concluídas."
)
)
try:
response = await client.responses.create(
model=MODEL,
instructions=instructions,
input=payload.mensagem,
store=False,
)
except openai.RateLimitError as exc:
raise HTTPException(
status_code=429,
detail=(
"Limite temporário atingido. "
"Tente novamente depois."
),
) from exc
except openai.AuthenticationError as exc:
raise HTTPException(
status_code=500,
detail="Falha na configuração do serviço.",
) from exc
except openai.APIConnectionError as exc:
raise HTTPException(
status_code=503,
detail=(
"Não foi possível conectar à OpenAI API."
),
) from exc
except openai.APIError as exc:
raise HTTPException(
status_code=502,
detail=(
"A OpenAI API retornou um erro."
),
) from exc
text = response.output_text.strip()
if not text:
raise HTTPException(
status_code=502,
detail="O modelo não retornou texto.",
)
return ChatResponse(
resposta=text,
model=response.model,
response_id=response.id,
)
Os limites de 8 mil e 2 mil caracteres são decisões da aplicação, não limites oficiais do modelo. Eles reduzem abuso e custos inesperados, mas devem ser ajustados à tarefa real.
Como o código funciona
AsyncOpenAI
AsyncOpenAI é o cliente assíncrono do SDK oficial. A chamada utiliza await, permitindo que o servidor atenda outras conexões enquanto aguarda a resposta externa.
Pydantic
FastAPI usa modelos Pydantic para ler o JSON, converter tipos, validar campos e gerar o schema OpenAPI. Se o corpo for inválido, o framework devolve uma resposta de validação antes de chamar a OpenAI.
Responses API
client.responses.create() envia a solicitação ao endpoint atual de respostas. response.output_text reúne o conteúdo textual produzido.
store=False
O parâmetro impede o armazenamento padrão do objeto Response nesse fluxo. Ele não elimina logs ou retenção no restante da arquitetura e não substitui uma política de dados.
4. Inicie o servidor em desenvolvimento
fastapi dev main.py
A aplicação ficará disponível, normalmente, em:
http://127.0.0.1:8000;http://127.0.0.1:8000/docspara Swagger UI;http://127.0.0.1:8000/redocpara ReDoc.
Não use modo de recarregamento automático em produção. A documentação de execução do FastAPI alerta que --reload consome mais recursos e é destinado ao desenvolvimento.
5. Teste o endpoint com curl
curl -X POST http://127.0.0.1:8000/api/responder \
-H "Content-Type: application/json" \
-d '{
"mensagem": "Explique o que é FastAPI em quatro frases."
}'
Uma resposta bem-sucedida terá estrutura semelhante a:
{
"resposta": "FastAPI é um framework...",
"model": "gpt-6-luna",
"response_id": "resp_..."
}
O response_id pode ajudar a investigar falhas e correlacionar logs. Não registre a chave, o corpo completo ou dados pessoais sem necessidade.
Como adicionar autenticação ao seu endpoint
O exemplo não autentica usuários finais. Sem proteção, qualquer pessoa que alcance o endpoint poderá consumir seus créditos.
Em produção, adicione pelo menos:
- sessão, JWT ou outra autenticação adequada;
- limite por usuário, IP, organização ou plano;
- limite diário ou mensal de custo;
- bloqueio de contas abusivas;
- identificador de segurança pseudonimizado;
- logs de auditoria para ações importantes.
Não confie apenas em CORS. CORS controla o que navegadores permitem, mas não impede que outro cliente HTTP chame publicamente seu servidor.
Como configurar CORS corretamente
CORS é necessário quando o frontend e o backend usam origens diferentes. Uma origem é formada por protocolo, domínio e porta.
A documentação oficial do FastAPI sobre CORS recomenda listar origens explicitamente, especialmente quando a aplicação usa cookies ou cabeçalhos de autorização.
Evite em produção:
allow_origins=["*"]
Prefira:
allow_origins=[
"https://app.exemplo.com",
"https://admin.exemplo.com",
]
Se usar credenciais, teste preflight, cookies, cabeçalhos e domínios reais. Não autorize origens desconhecidas apenas para eliminar um erro no navegador.
Como criar streaming com FastAPI
Streaming permite entregar trechos de texto enquanto o modelo continua gerando. A Responses API envia eventos semânticos; o evento response.output_text.delta contém os novos trechos.
Adicione estes imports ao início de main.py:
import json
from fastapi.responses import StreamingResponse
Depois, acrescente o endpoint:
@app.post("/api/stream")
async def stream_response(
payload: ChatRequest,
) -> StreamingResponse:
async def event_generator():
try:
stream = await client.responses.create(
model=MODEL,
instructions=(
"Responda em português do Brasil. "
"Não invente fatos ausentes."
),
input=payload.mensagem,
store=False,
stream=True,
)
async for event in stream:
if event.type == "response.output_text.delta":
data = json.dumps(
{"delta": event.delta},
ensure_ascii=False,
)
yield f"data: {data}\n\n"
elif event.type == "response.completed":
yield "event: done\ndata: {}\n\n"
except openai.APIError:
data = json.dumps(
{"mensagem": "Falha ao gerar a resposta."},
ensure_ascii=False,
)
yield f"event: error\ndata: {data}\n\n"
return StreamingResponse(
event_generator(),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"X-Accel-Buffering": "no",
},
)
Depois que o streaming começa, o servidor não consegue substituir facilmente a resposta por um erro HTTP comum. Por isso, falhas ocorridas durante o fluxo são enviadas como eventos SSE.
Streaming pode exibir conteúdo antes da revisão completa. Avalie moderação, interrupção, reconexão e tratamento de respostas incompletas.
Como manter uma conversa
Para continuar uma conversa simples, envie previous_response_id na próxima chamada:
primeira = await client.responses.create(
model=MODEL,
input="Meu produto é uma plataforma de cursos.",
store=True,
)
segunda = await client.responses.create(
model=MODEL,
previous_response_id=primeira.id,
input="Crie três mensagens de onboarding.",
)
Se precisar de uma conversa durável entre sessões ou dispositivos, avalie a Conversations API. Antes disso, defina autenticação, propriedade da conversa, exclusão, retenção e autorização de acesso.
Não aceite um previous_response_id fornecido pelo cliente sem verificar se ele pertence ao usuário autenticado. Caso contrário, uma pessoa pode tentar acessar contexto de outra conversa.
Como escolher o modelo no backend
Evite permitir que qualquer usuário envie livremente um model ID. Isso pode causar custos inesperados ou acesso a modelos inadequados.
Uma estratégia simples é mapear funções internas:
MODELS = {
"economico": "gpt-6-luna",
"equilibrado": "gpt-6.1-sol",
"avancado": "gpt-6-astra",
}
O backend escolhe o perfil autorizado e não confia em um model ID arbitrário vindo do navegador.
| Perfil | Uso provável |
|---|---|
| Econômico | Classificação, extração e transformação simples |
| Equilibrado | Análise profissional, texto e automações com maior exigência |
| Avançado | Raciocínio complexo, programação e tarefas difíceis |
Faça testes com dados reais anonimizados. Um modelo mais caro pode reduzir correções, mas um modelo menor pode atender perfeitamente uma tarefa restrita.
Como controlar custos
- limite tamanho das mensagens;
- limite o número de chamadas por usuário;
- não envie históricos irrelevantes;
- use modelos menores em tarefas simples;
- aproveite prompt caching em prefixos estáveis;
- registre tokens e ferramentas por chamada;
- defina limites financeiros no projeto;
- interrompa loops de novas tentativas;
- revise respostas longas sem necessidade.
custo estimado =
tokens de entrada
+ gravação e leitura de cache
+ tokens de saída
+ ferramentas
+ novas tentativas
+ revisão humana
Use a tabela oficial de preços e não fixe valores no código. Tarifas podem variar por modelo, contexto e modo de processamento.
Tratamento de erros
| Exceção | Resposta recomendada do backend |
|---|---|
AuthenticationError | Erro interno de configuração; não exponha detalhes ao usuário |
RateLimitError | HTTP 429 e orientação para tentar depois |
APIConnectionError | HTTP 503 para indisponibilidade temporária |
APIError | HTTP 502 genérico, com log interno controlado |
| Validação FastAPI | HTTP 422 gerado automaticamente |
Em produção, respeite Retry-After e use backoff com jitter para erros temporários. Não tente novamente erros de chave, créditos esgotados ou limites financeiros sem corrigir a causa.
Logs e observabilidade
Registre o suficiente para investigar falhas, sem copiar todo o conteúdo do usuário por padrão.
- ID interno da requisição;
response_idda OpenAI;- model ID;
- status e classe do erro;
- latência;
- tokens de entrada, cache e saída;
- usuário ou cliente pseudonimizado;
- ferramentas chamadas;
- resultado da validação.
Não registre a chave, cabeçalhos de autorização, senhas, documentos completos ou dados pessoais sem finalidade e controle de acesso.
Como executar em produção
Para iniciar com o comando de produção do FastAPI:
fastapi run main.py
Também é possível usar um servidor ASGI diretamente:
uvicorn main:app --host 0.0.0.0 --port 8000
Um deploy real também precisa considerar:
- HTTPS;
- proxy reverso ou plataforma gerenciada;
- reinício automático;
- múltiplos processos ou réplicas;
- timeouts;
- health checks;
- segredos gerenciados;
- autenticação e autorização;
- limites e proteção contra abuso;
- monitoramento de custo e erros.
Erros frequentes nesta integração
A chave não foi encontrada
Confirme o nome OPENAI_API_KEY, reinicie o terminal e verifique se o arquivo .env está no diretório correto.
O navegador mostra erro de CORS
Adicione a origem exata do frontend em FRONTEND_ORIGINS. Protocolo, domínio e porta fazem parte da origem.
Erro 401
A chave pode estar inválida, revogada, ligada a outro projeto ou sem permissão para o recurso.
Erro 429
Pode indicar rate limit, saldo esgotado, limite financeiro ou limite de uso. Leia o código específico do erro; repetir a chamada não resolve todos esses casos.
A resposta demora
Compare modelo, reasoning effort, tamanho da entrada, ferramentas e volume. Use streaming quando a experiência exigir feedback imediato, mas não o trate como redução do tempo total de processamento.
Checklist antes de colocar o endpoint no ar
- chave fora do código e do navegador;
- dependências registradas em lockfile;
- autenticação dos usuários;
- CORS restrito;
- limites de entrada e saída;
- rate limit por usuário;
- tratamento de
401,429,500e503; - timeouts e número máximo de tentativas;
- logs sem segredos;
- monitoramento de tokens e custos;
- testes de prompt injection;
- revisão humana nas tarefas de risco;
- processo para revogar chaves;
- política de retenção e exclusão.
A integração básica exige poucas linhas, mas um serviço confiável precisa proteger credenciais, usuários, custos e dados. Use o endpoint simples para validar a ideia, depois acrescente autenticação, avaliações, observabilidade e controles antes de ampliar o tráfego.

