| name | openai-api |
| description | Reference for the OpenAI API and ANY OpenAI-compatible endpoint (set `base_url`/`baseURL` to point the official OpenAI SDK at OpenAI, Azure OpenAI, a local server — vLLM, Ollama, llama.cpp, LM Studio, TGI — or a gateway/aggregator like Groq, Together, Fireworks, OpenRouter, DeepSeek, z.ai). Covers client init + base_url, Chat Completions, streaming, function/tool calling, structured outputs, vision, embeddings, reasoning models, error handling, and per-endpoint feature compatibility, across Python, TypeScript, Go, Java, Ruby, C#, PHP, and cURL.
TRIGGER — read BEFORE writing code — whenever: the prompt names OpenAI or the OpenAI SDK in any form (`openai`, `from openai`, `import OpenAI`, `OpenAI(`, `openai-python`, `openai-node`, `openai-go`, `gpt-*`, `o1`/`o3`/`o4`, `text-embedding-*`, `chat/completions`, `response_format`, `tool_calls`); the user points a client at a custom endpoint via `base_url`/`baseURL`/`OPENAI_BASE_URL`/`OPENAI_API_BASE`; the user targets a local or third-party OpenAI-compatible server (vLLM, Ollama, llama.cpp `llama-server`, LM Studio, Text Generation Inference/TGI, LocalAI, Groq, Together, Fireworks, Anyscale, OpenRouter, DeepSeek, Mistral `/v1`, z.ai/GLM, Perplexity); OR the task is LLM-shaped and the project already imports an OpenAI SDK.
SKIP when the Anthropic/Claude SDK is the target (use the `claude-api` skill instead): the prompt names Claude/Anthropic/`anthropic`/`@anthropic-ai`/`claude-*`/the Messages API (`/v1/messages`, `input_schema`, `cache_control`, `anthropic-version`). If the project imports the `anthropic` SDK and there is no OpenAI marker, defer to `claude-api`. |
| license | Complete terms in LICENSE.txt (adapted from the Anthropic `claude-api` skill; see NOTICE.md). |
Building LLM-Powered Applications on the OpenAI API (and any OpenAI-compatible endpoint)
This skill helps you build against the OpenAI API wire format — the de-facto lingua franca that OpenAI's own SDKs speak and that dozens of other servers implement. The single most important idea: you point the official OpenAI SDK at any endpoint by setting base_url (Python/Go/Ruby/PHP) / baseURL (TypeScript) / the endpoint option (Java/C#). Change the URL, keep the code.
The One Thing To Get Right: base_url
Every OpenAI SDK talks to https://api.openai.com/v1 by default. Override the base URL and the same code drives a local model, a self-hosted cluster, or a third-party gateway:
from openai import OpenAI
client = OpenAI()
client = OpenAI(base_url="http://localhost:8000/v1", api_key="not-needed")
client = OpenAI(base_url="https://api.groq.com/openai/v1", api_key=os.environ["GROQ_API_KEY"])
The base URL should end in /v1 for almost every server (the SDK appends /chat/completions, /models, etc.). Read shared/endpoints.md for the copy-paste base-URL + api-key preset for each well-known endpoint — this is the file that makes "any endpoint" real.
Local servers usually ignore the API key but the SDKs still require a non-empty string — pass any placeholder ("not-needed", "sk-noauth"). Setting it to "" or leaving OPENAI_API_KEY unset raises before the request is sent.
Before You Start
- Provider check. If the target is the Anthropic/Claude SDK (
anthropic, @anthropic-ai/*, /v1/messages, input_schema, cache_control), stop — use the claude-api skill, not this one. This skill emits OpenAI-format code.
- Endpoint check. Determine the target endpoint (OpenAI proper, Azure, a local server, or a gateway) and its
base_url. If unknown, ask — the base URL and auth differ per endpoint (shared/endpoints.md). Azure OpenAI is a special case: it uses a deployment name as the model and its own SDK path (AzureOpenAI / AzureOpenAI client) — see shared/endpoints.md § Azure.
- Don't assume the model. There is no universal default model and no fixed price list — the endpoint defines what models exist. Discover with
GET /v1/models (client.models.list()) and use what the endpoint serves. For OpenAI proper, use the latest model the user names or that /v1/models reports; never hardcode a stale model string or invent one. See shared/models.md.
Output Requirement
When the user asks you to add or modify an OpenAI / OpenAI-compatible feature, your code must go through one of:
- The official OpenAI SDK for the project's language (
openai for Python/Node/Go/Ruby/Java, the official OpenAI .NET package, or the community-standard openai-php/client for PHP). This is the default whenever an SDK exists.
- Raw HTTP (
curl, requests, fetch, httpx, …) — only when the user explicitly asks for cURL/REST, the project is a shell/cURL project, or the language has no usable SDK.
Never guess SDK surface. Method names, option names, and import paths must come from the {lang}/openai-api/ files in this skill or the official SDK repos in shared/live-sources.md. If a binding isn't documented here, WebFetch the SDK repo before writing. For statically-typed SDKs (Go, Java, C#), if the network is unavailable, write from the patterns in the {lang}/ file and fix against the compiler.
Defaults
Unless the user says otherwise:
- Use the Chat Completions API (
/v1/chat/completions) — it is the surface every OpenAI-compatible endpoint implements. The Responses API (/v1/responses) is richer but is largely OpenAI-first-party; most third-party/local servers do not implement it. Default to Chat Completions for portability; reach for Responses only when the target is confirmed to be OpenAI (or a server that documents Responses support). See shared/responses-api.md.
- Stream any request that may produce long output or use a high
max_tokens — it avoids client/proxy read timeouts. Add stream_options={"include_usage": True} to still get token counts on the final chunk (shared/streaming-concepts.md).
- Discover the model rather than hardcoding one; pass whatever the user specifies. Keep the model id in a variable/const so switching endpoints is one edit.
- For reasoning models (OpenAI o-series / gpt-5 reasoning tiers, DeepSeek-R1, QwQ, and compatible servers) prefer
reasoning_effort where supported and read reasoning content from the documented field — see shared/reasoning-models.md.
⚠️ The Central Caveat: Compatibility Varies By Endpoint
"OpenAI-compatible" is a spectrum, not a guarantee. A local llama.cpp server speaks Chat Completions and streaming but may not implement response_format: json_schema, parallel tool calls, logprobs, the Batch API, /v1/responses, or vision. Never assume a feature exists on a non-OpenAI endpoint — degrade gracefully and tell the user. shared/compatibility.md is the single source of truth in this skill for which features are universal vs. OpenAI-only vs. endpoint-dependent. Consult it before promising a feature on a custom base_url.
| Feature | Portability |
|---|
Chat Completions, system/user/assistant roles, max_tokens, streaming (SSE + [DONE]) | Universal — every compatible endpoint |
Function/tool calling (tools, tool_calls, tool role) | Widely supported; parallel tool calls and strict vary |
Structured outputs (response_format: {type: "json_schema", strict: true}) | OpenAI + newer vLLM/TGI; often absent on small local servers (json_object mode is more common) |
Vision (image_url content parts) | Only vision-capable models/servers |
Embeddings (/v1/embeddings) | Common but a separate model/endpoint — many chat-only servers omit it |
reasoning_effort, reasoning tokens | OpenAI reasoning tiers + a few compat servers (DeepSeek, some vLLM) |
Responses API (/v1/responses), Batch API, Files, Assistants/Agents | OpenAI-first-party — assume absent elsewhere unless documented |
| Prompt caching | OpenAI does it automatically (no param); other endpoints vary — see shared/prompt-caching.md |
Subcommands
If the User Request is a bare subcommand string (no prose), search every Subcommands table in this document and follow the matching Action. Invoke via /openai-api <subcommand>.
| Subcommand | Action |
|---|
endpoints | List the base-URL + auth presets for common endpoints. Read shared/endpoints.md and present the table plus the one client-init snippet for the user's detected language. |
compat | Report whether a feature is portable across endpoints. Read shared/compatibility.md and answer for the specific feature/endpoint the user names. |
migrate-from-anthropic | Translate Anthropic Messages-API code to the OpenAI format (roles, input_schema→parameters, content blocks→parts, stop_reason→finish_reason). Read shared/migrate-from-anthropic.md and follow it; confirm scope (which files) before editing. |
Language Detection
Before reading code examples, determine the language:
*.py, requirements.txt, pyproject.toml, Pipfile → Python → python/openai-api/
*.ts, *.tsx, package.json, tsconfig.json → TypeScript → typescript/openai-api/
*.js, *.jsx (no .ts) → TypeScript (JS uses the same SDK) → typescript/openai-api/
*.go, go.mod → Go → go/openai-api/
*.java, pom.xml, build.gradle → Java → java/openai-api/
*.kt, *.kts → Java (Kotlin uses the Java SDK) → java/openai-api/
*.rb, Gemfile → Ruby → ruby/openai-api/
*.cs, *.csproj → C# → csharp/openai-api/
*.php, composer.json → PHP → php/openai-api/
- Shell / no SDK / unsupported language → cURL →
curl/examples.md
Multiple languages: ask which one the OpenAI integration is in. Unknown language: use AskUserQuestion (Python, TypeScript, Go, Java, Ruby, C#, PHP, cURL); if unavailable, default to Python and say so. Unsupported language (Rust, Swift, Elixir, …): use curl/examples.md and note community SDKs may exist.
Official SDK per language
| Language | Package | Notes |
|---|
| Python | openai | OpenAI() / AsyncOpenAI(), base_url= |
| TypeScript/JS | openai | new OpenAI({ baseURL }) |
| Go | github.com/openai/openai-go (v3+) | option.WithBaseURL(...) |
| Java | com.openai:openai-java | OpenAIOkHttpClient.builder().baseUrl(...) |
| Ruby | openai gem | OpenAI::Client.new(base_url: ...) |
| C# / .NET | OpenAI (official) or Azure.AI.OpenAI for Azure | OpenAIClient with OpenAIClientOptions { Endpoint = ... } |
| PHP | openai-php/client (community standard — no official SDK) | OpenAI::factory()->withBaseUri(...)->make() |
| cURL | — | change the URL, keep the JSON |
Architecture
Almost everything is one endpoint: POST /v1/chat/completions. Tools, structured outputs, vision, and reasoning are all parameters of that one call, not separate APIs.
- Messages are a flat list of
{role, content} — roles are system (or developer on newer OpenAI reasoning models), user, assistant, and tool. Content is a string, or an array of typed parts (text, image_url, …) for multimodal input.
- Tools / function calling — you pass
tools=[{type:"function", function:{name, description, parameters}}]; the model replies with message.tool_calls[]; you execute and append {role:"tool", tool_call_id, content} messages, then call again. You own the loop (no server-run agent loop in the portable surface).
- Structured outputs — constrain the reply with
response_format ({type:"json_schema", json_schema:{...}, strict:true} on OpenAI + newer servers, or the looser {type:"json_object"} elsewhere).
- Supporting endpoints —
GET /v1/models (discovery), POST /v1/embeddings, and (OpenAI-only) /v1/responses, /v1/batches, /v1/files.
finish_reason (not Anthropic's stop_reason) tells you why generation stopped: stop, length, tool_calls, content_filter, function_call (legacy).
Models (no hardcoded list — discover them)
Unlike a single-provider skill, there is no fixed model table here because the model set is whatever your base_url serves. Always discover:
for m in client.models.list().data:
print(m.id)
- OpenAI proper: use the model the user names, or query
/v1/models. Do not invent model ids or prices; if the user asks "which model / how much," read shared/models.md and, when live info is needed, GET /v1/models or WebFetch the pricing page from shared/live-sources.md.
- Local / gateway endpoints: the model id is whatever the server registered (e.g.
llama.cpp often serves a single model under the name you loaded it with; Ollama uses names like llama3.1:8b; vLLM uses the HF repo id). List them and pass the exact string.
- Keep the model id in one variable/const so pointing at a different endpoint is a single edit.
See shared/models.md for discovery fields (context_window is not a standard field — most servers only return id/owned_by) and capability notes.
Reasoning Models (Quick Reference)
OpenAI reasoning tiers (o-series, gpt-5 reasoning) and several compatible models (DeepSeek-R1, QwQ, and reasoning models served via vLLM/TGI) "think" before answering.
reasoning_effort ("low"|"medium"|"high", plus "minimal" on some OpenAI tiers) controls depth on OpenAI Chat Completions. Pass it top-level. Not all compatible servers accept it — omit or feature-detect on unknown endpoints.
- System vs developer role: newer OpenAI reasoning models treat the first instruction message as
developer; system is still accepted and mapped. Portable code can keep using system.
- Reasoning content: on OpenAI the hidden reasoning is not returned (only a
reasoning_tokens count in usage.completion_tokens_details). Some compatible servers do return reasoning text — DeepSeek exposes message.reasoning_content; others emit <think>…</think> inline. Read the documented field; don't assume.
- Sampling params (
temperature, top_p) are often ignored or rejected by reasoning models — leave them unset unless the user asks.
Full detail and per-server behavior: shared/reasoning-models.md.
Structured Outputs (Quick Reference)
Two tiers, in decreasing portability:
- JSON mode —
response_format={"type":"json_object"}. Guarantees valid JSON (not a specific schema). You must also instruct the model (in the prompt) to produce JSON, or the request errors. Widely supported.
- Strict JSON Schema —
response_format={"type":"json_schema","json_schema":{"name":...,"schema":{...},"strict":true}}. Guarantees the reply matches your schema. OpenAI + newer vLLM/TGI/others; absent on many local servers. Schema needs "additionalProperties": false and every property in required.
Python/TS SDKs offer a typed helper: client.beta.chat.completions.parse(..., response_format=MyPydanticModel) / zodResponseFormat(...). See shared/structured-outputs.md and the language README.md.
Function / Tool Calling (Quick Reference)
- Define tools as
{"type":"function","function":{"name","description","parameters":<JSON Schema>}}. Note: parameters, not Anthropic's input_schema.
- The model returns
choices[0].message.tool_calls — a list of {id, type:"function", function:{name, arguments}}. arguments is a JSON string — always json.loads() / JSON.parse() it; never string-match.
- Append the assistant message (with its
tool_calls) back, then one {"role":"tool","tool_call_id":<id>,"content":<result>} per call, then call again. Loop until finish_reason != "tool_calls".
- Parallel tool calls: OpenAI may emit several
tool_calls in one message — return a tool message for each tool_call_id. Disable with parallel_tool_calls=false. Many local servers emit only one call at a time.
- Forcing a tool:
tool_choice="auto"|"none"|"required"|{"type":"function","function":{"name":...}}. "required" and named forcing are OpenAI features; not all servers honor them.
strict: true on the function definition (OpenAI) constrains arguments to the schema. Portable code should still validate.
See shared/tool-use-concepts.md and {lang}/openai-api/tool-use.md.
Streaming (Quick Reference)
- Set
stream=True. Chunks arrive as chat.completion.chunk objects; text is in choices[0].delta.content (may be None/absent on role/tool chunks). The raw SSE stream ends with a literal data: [DONE].
- Usage while streaming: by default streamed responses omit
usage. Add stream_options={"include_usage": True} to get a final chunk with token counts (OpenAI + many compat servers; ignored by some).
- Streaming tool calls arrive as fragments:
delta.tool_calls[].function.arguments is concatenated across chunks; accumulate by index. See {lang}/openai-api/streaming.md.
- The SDKs give you an iterator (
for chunk in stream) plus helpers to accumulate the final message — prefer those over hand-parsing SSE. Raw SSE only for cURL/no-SDK.
Client Config (Quick Reference)
base_url/baseURL — the whole point (see shared/endpoints.md). Also settable via OPENAI_BASE_URL env (Python/TS honor OPENAI_BASE_URL; the older OPENAI_API_BASE is still read by some tools — prefer OPENAI_BASE_URL).
api_key — OPENAI_API_KEY env by default; pass a placeholder for keyless local servers (must be non-empty).
timeout — default 10 min. Units differ: Python/Ruby seconds, TypeScript milliseconds, Go time.Duration, Java/C# Duration/TimeSpan. Raise it for big non-streaming generations, or stream instead.
max_retries/maxRetries — default 2 (retries 408/409/429/5xx + connection errors). 0 disables.
default_headers — needed by some gateways (e.g. OpenRouter's HTTP-Referer/X-Title, or an org/project header). See shared/endpoints.md.
organization/project — OpenAI-only; ignored elsewhere.
Reading Guide
After detecting the language, read what the task needs.
Every SDK language uses the same layout — {lang}/openai-api/ with:
README.md — read first. Install, client init + base_url, basic chat, system prompts, vision, structured outputs, embeddings, reasoning, error handling, multi-turn.
tool-use.md — function calling: definitions, the tool loop, parallel calls, strict.
streaming.md — streaming chat and streaming tool calls.
Shared (language-agnostic) concept files in shared/:
endpoints.md — base-URL + auth presets for every well-known endpoint. Read whenever a non-default base_url is in play.
compatibility.md — which features are universal / OpenAI-only / endpoint-dependent. Read before promising any feature on a custom endpoint.
models.md — model discovery (/v1/models), naming per endpoint, capability notes.
tool-use-concepts.md — function-calling concepts, the loop, parallel calls, tool_choice.
structured-outputs.md — JSON mode vs strict JSON Schema, schema rules, SDK parse helpers.
reasoning-models.md — reasoning_effort, reasoning tokens/content, o-series/gpt-5/DeepSeek-R1/<think>.
streaming-concepts.md — SSE framing, [DONE], delta accumulation, include_usage.
responses-api.md — the OpenAI Responses API and why it's usually not portable; when to use it.
prompt-caching.md — OpenAI automatic caching; how other endpoints differ.
error-codes.md — HTTP status codes, the typed-exception class per SDK, retry guidance.
migrate-from-anthropic.md — translating Claude Messages-API code to the OpenAI format.
live-sources.md — WebFetch URLs for the latest official docs and SDK repos.
Quick Task Reference
- Point the SDK at a custom/local endpoint →
shared/endpoints.md + {lang}/openai-api/README.md
- "Will feature X work on endpoint Y?" →
shared/compatibility.md
- Single chat / classification / summarization / extraction →
{lang}/openai-api/README.md
- Chat UI / incremental display →
{lang}/openai-api/README.md + {lang}/openai-api/streaming.md + shared/streaming-concepts.md
- Function calling / tools / agent loop →
shared/tool-use-concepts.md + {lang}/openai-api/tool-use.md
- JSON / typed output →
shared/structured-outputs.md + {lang}/openai-api/README.md (Structured Outputs)
- Reasoning models (o-series, gpt-5, DeepSeek-R1, QwQ) →
shared/reasoning-models.md
- Embeddings →
{lang}/openai-api/README.md (Embeddings) + shared/models.md
- Which model / how much does it cost →
shared/models.md, then /v1/models or shared/live-sources.md (never answer from memory)
- Debugging HTTP errors →
shared/error-codes.md
- Porting from the Anthropic SDK →
shared/migrate-from-anthropic.md
Common Pitfalls
- Empty API key on local servers. The SDKs require a non-empty
api_key; keyless servers still need a placeholder string. Unset OPENAI_API_KEY → the SDK raises before sending.
- Base URL
/v1 suffix. Almost every server expects the base URL to end in /v1; the SDK appends the path. http://localhost:11434 (Ollama root) fails — use http://localhost:11434/v1.
- Assuming a feature is portable.
json_schema structured outputs, parallel tool calls, strict, logprobs, vision, embeddings, the Responses/Batch/Files APIs, tool_choice:"required" — all vary by endpoint. Feature-detect or degrade; consult shared/compatibility.md. Don't promise the user a feature the endpoint may not implement.
arguments is a JSON string. message.tool_calls[i].function.arguments is serialized JSON — parse it; do not read fields off the raw string.
- Streaming drops
usage. Add stream_options={"include_usage": True} if you need token counts from a streamed call.
max_tokens on reasoning models. OpenAI reasoning models use max_completion_tokens (not max_tokens) and count hidden reasoning tokens against it — set it generously. Some tiers reject temperature/top_p. See shared/reasoning-models.md.
- Don't hardcode a model or its price. The endpoint defines the model set and pricing; discover via
/v1/models and keep the id in one place.
- JSON mode needs a prompt hint.
response_format={"type":"json_object"} errors unless the messages also instruct the model to output JSON.
- Roles:
developer vs system. Newer OpenAI reasoning models prefer developer; system still works and is portable. Don't send developer to a server that doesn't recognize it.
- Responses API ≠ Chat Completions.
/v1/responses has a different request/response shape (input, output, instructions) and is mostly OpenAI-only. Don't mix it with Chat Completions code, and don't reach for it on a custom base_url unless documented. See shared/responses-api.md.
- Azure is not drop-in. Azure OpenAI uses a deployment name as the model,
api-version, and a dedicated client (AzureOpenAI). See shared/endpoints.md § Azure.
- Don't reimplement SDK helpers. Use the SDK's streaming accumulator, typed exceptions,
.parse() structured-output helper, and pagination instead of hand-rolling.
- Error handling — catch specific classes. Chain most-specific-first (
NotFoundError → RateLimitError → APIStatusError → APIConnectionError); per-SDK class names in shared/error-codes.md. Non-OpenAI servers may return non-standard error bodies — guard when reading .code/.type.
- Provider mismatch. If the file/project is Anthropic (
anthropic SDK, /v1/messages), this skill is the wrong one — use claude-api.
When to Use WebFetch
Fetch the latest docs (shared/live-sources.md) when: the user asks for "latest"/"current" info, cached data looks wrong, a model/price is in question, or a feature isn't covered here. Never answer model/pricing questions from memory — discover or fetch.