Keys on the server only. Prefer the official SDK for the project's language. For new apps, align with the API style the repository already uses (Chat Completions or Responses).
Stack and scope
| Ask | Role |
|---|---|
| Chat Completions | POST /v1/chat/completions — widely used |
| Responses API | Newer unified API (if the project has already migrated) |
SDK openai |
TS/Python/etc. |
| Tools | Function calling |
| Auth | Authorization: Bearer <OPENAI_API_KEY> |
Default base: https://api.openai.com/v1. Azure OpenAI / proxies: different base URL and auth — preserve the project's.
Non-negotiable rules
- API key only in backend/workers — never in the public frontend.
- Model ID pinned in config/env; deliberate upgrade.
- Timeouts and retries with backoff only on
429/ transient 5xx. - Streaming with propagated cancellation.
- Do not log full prompts/responses with PII/secrets.
Chat Completions
import OpenAI from "openai";
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const completion = await client.chat.completions.create({
model: process.env.OPENAI_MODEL!,
messages: [
{ role: "system", content: "You are a precise coding assistant." },
{ role: "user", content: "Summarize this diff in 3 bullets." },
],
temperature: 0.2,
});
const text = completion.choices[0]?.message?.content ?? "";
- Roles:
system/user/assistant/tool. - Read
finish_reason(stop,tool_calls,length, …). - Structured output:
response_format/ JSON schema when the version and model support it — validate on the server anyway.
Tools (function calling)
- Declare
toolswith JSON Schema parameters. - If the response brings
tool_calls, run it on the server. - Append messages
role: "tool"withtool_call_idand continue the loop.
- Validate arguments before side effects.
- Least privilege in tools.
- Human confirmation / idempotency for destructive actions.
Streaming
const stream = await client.chat.completions.create({
model: process.env.OPENAI_MODEL!,
messages,
stream: true,
});
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta?.content;
if (delta) {
// encaminhar ao client
}
}
- Proxy SSE/chunks without buffering everything if the UX is streamed.
- Handle dropped and half-closed connections.
Responses API (when the project uses it)
- Use the client
responses.create/ equivalent SDK stream. - Do not mix Completions and Responses shapes in the same adapter without an anti-corruption layer.
- Tools, text, and usage follow the Responses contract — read the types of the installed SDK.
Errors and limits
| Situation | Action |
|---|---|
401 / 403 |
Key/project — no blind retry |
429 |
Backoff + rate limits / quota |
5xx |
Limited retry or fallback |
length / context |
Truncate history with an explicit strategy |
- Monitor tokens (
usage) and cost per feature. - Separate keys and projects per environment.
Compatibility
- Ollama and other proxies may expose
/v1/chat/completions— seeollama-api. - Claude/Anthropic is another API — see
claude-api; do not reuse payloads blindly.
Anti-patterns
- OpenAI key in the browser bundle
- Tool loop without an iteration ceiling
- Retry on schema/validation error
- Relying only on “JSON mode” without parse/validate
- Mixing Azure OpenAI and api.openai.com in the same config unintentionally
Completion criteria
- Key and model via secure env/config
- Completions or Responses aligned with the project
- Correct and bounded tool loop if there are tools
- Cancelable streaming when applicable
- 429/5xx handled; observable usage