Google has Gemini. Anthropic has Claude. Meta has Llama. Mistral, Cohere, DeepSeek, Qwen β dozens of model providers.
Every single one of them supports the OpenAI API format.
Not because OpenAI is the best. Not because they're required to. But because OpenAI shipped first, developers adopted it, and now it's the de facto standard. Like how USB-C won β not by being technically perfect, but by being everywhere.
If you understand the OpenAI chat completions API, you understand how to talk to virtually every LLM on the planet. Let's dissect it.
The Chat Completions Endpoint
Everything centers on one endpoint:
POST https://api.openai.com/v1/chat/completions
That's it. One endpoint. You send messages, you get a completion. Every feature β tools, streaming, JSON mode, vision, structured outputs β layers on top of this single endpoint.
python
import openai
client = openai.OpenAI()
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What is the capital of France?"}
])
print(response.choices[0].message.content)"The capital of France is Paris."
The Messages Format
The core of the API is the messages array β an ordered list of messages that represent the conversation. Each message has a role and content:
The Four Roles
Role Purpose Who writes it
| system | Sets behavior, personality, instructions | Developer |
|---|---|---|
| user | The human's input | End user |
| assistant | The model's response | The LLM |
| tool | Result from a tool call | Your backend code |
python
messages = [
# System: Developer instructions (invisible to user)
{
"role": "system",
"content": "You are a concise, technical AI assistant. "
"Answer in 2-3 sentences max."
},
# User: What the human typed
{
"role": "user",
"content": "Explain transformers in AI"
},
# Assistant: What the model responded (from a previous turn)
{
"role": "assistant",
"content": "Transformers use self-attention to process all tokens "
"in parallel, unlike RNNs which go sequentially."
},
# User: Follow-up
{
"role": "user",
"content": "What's self-attention?"
}]
Why Messages, Not a Single Prompt?
The old completions API (v1) took a single text string. The chat completions API uses structured messages because:
Multi-turn conversations β each message has a clear role and boundary
System prompts β developers can set behavior without users seeing/overriding it
Tool results β tool outputs go in tool messages, keeping the flow clean
History management β easy to truncate old messages to fit the context window
π§
The Request Object
Here's the full anatomy of a chat completions request:
python
response = client.chat.completions.create(
# Required
model="gpt-4o", # Which model
messages=[...], # Conversation history
# Common optional
temperature=0.7, # Randomness (0 = deterministic, 2 = creative)
max_tokens=1000, # Max output length
top_p=1.0, # Nucleus sampling (alternative to temperature)
# Tool calling
tools=[...], # Available tools
tool_choice="auto", # "auto", "none", or force specific tool
# Output format
response_format={"type": "json_object"}, # Force JSON output
# Streaming
stream=True, # Get tokens as they generate
# Advanced
stop=["\n\n", "END"], # Stop sequences
n=1, # Number of completions to generate
seed=42, # Reproducibility (best-effort)
frequency_penalty=0.0, # Penalize repeated tokens
presence_penalty=0.0, # Penalize already-used tokens
logprobs=True, # Return token probabilities
top_logprobs=5, # Number of top tokens to return
user="user_123" # End-user ID for abuse tracking)
Key Parameters Explained
Parameter Range What it does
| temperature | 0-2 | 0 = always pick the most likely token, 1 = balanced, 2 = wild creativity |
|---|---|---|
| max_tokens | 1-model_max | Hard cap on output length. Model stops even mid-sentence |
| top_p | 0-1 | Only consider tokens in the top P% probability mass |
| stop | string[] | Model stops generating when it produces these strings |
| n | 1-128 | Generate multiple completions (costs nΓ tokens) |
| seed | integer | Makes output more reproducible (not guaranteed identical) |
Pro tip: Use temperature OR top_p, not both. They're alternative ways to control randomness. Most people use temperature.
π
The Response Object
python
{
"id": "chatcmpl-abc123",
"object": "chat.completion",
"created": 1700000000,
"model": "gpt-4o-2024-08-06",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "The capital of France is Paris."
},
"finish_reason": "stop" # Why it stopped
}
],
"usage": {
"prompt_tokens": 25, # Tokens you sent
"completion_tokens": 8, # Tokens generated
"total_tokens": 33 # Total
}}
finish_reason Values
Reason Meaning
| stop | Model finished naturally (or hit a stop sequence) |
|---|---|
| length | Hit max_tokens limit β output was cut off |
| tool_calls | Model wants to call a tool |
| content_filter | Content was filtered for safety |
Always check finish_reason! If it's length, the model's response was truncated. You're missing content.
Why This Format Won
The OpenAI API format became the standard because of three things:
- First Mover Advantage
OpenAI shipped ChatGPT in November 2022 and the API in March 2023. By the time competitors shipped their APIs, thousands of tools, libraries, and tutorials already used the OpenAI format.
- Simplicity
The API is genuinely well-designed. One endpoint, four message roles, a clean JSON format. Compare this to older ML serving APIs that needed custom gRPC schemas, protobuf definitions, and model-specific preprocessing.
- Network Effects
Once LangChain, LlamaIndex, and other frameworks built around the OpenAI format, every new model provider had a choice: invent your own format and force developers to learn it, or just support the OpenAI format and get instant compatibility.
Everyone chose compatibility:
Provider Own API OpenAI-Compatible API
Anthropic Yes (Messages API) Community adapters
| Google Gemini | Yes (Gemini API) | OpenAI compat mode |
|---|---|---|
| Mistral | Yes | Yes (OpenAI format) |
| Together AI | No | Yes (OpenAI format only) |
| Groq | No | Yes (OpenAI format only) |
| Fireworks AI | No | Yes (OpenAI format only) |
| Ollama | No | Yes (OpenAI format only) |
| vLLM | No | Yes (OpenAI format only) |
| LiteLLM | β | Universal OpenAI adapter |
Some providers (Together, Groq, Ollama) don't even have their own format. They went straight to OpenAI compatibility. That's how dominant this spec became.
Using the Format with Any Provider
Because the format is standardized, switching providers is often just changing the base URL and API key:
python
from openai import OpenAI
OpenAI
client = OpenAI(
api_key="sk-...",
base_url="https://api.openai.com/v1")
Groq (same code, different URL)
client = OpenAI(
api_key="gsk_...",
base_url="https://api.groq.com/openai/v1")
Ollama (local, same code)
client = OpenAI(
api_key="ollama", # Not actually needed
base_url="http://localhost:11434/v1")
Together AI
client = OpenAI(
api_key="tog_...",
base_url="https://api.together.xyz/v1")
Same code works with all of them!
response = client.chat.completions.create(
model="gpt-4o", # Change to provider's model name
messages=[{"role": "user", "content": "Hello!"}])
This is incredibly powerful. Your application code stays the same. You only change the configuration.
π¬
Multimodal Messages
The messages format extends naturally to images, audio, and other modalities:
python
Text + Image (Vision)
messages = [
{
"role": "user",
"content": [
{"type": "text", "text": "What's in this image?"},
{
"type": "image_url",
"image_url": {
"url": "https://example.com/photo.jpg",
"detail": "high" # "low", "high", or "auto"
}
}
]
}]
Note: content can be a string OR an array of content parts
String: {"content": "Hello"}
Array: {"content": [{"type": "text", "text": "Hello"}, {"type": "image_url", ...}]}
π‘οΈ
The Completions vs Chat Completions API
You'll occasionally see references to the old v1/completions endpoint. Here's the difference:
Completions (Legacy) Chat Completions (Current)
| Endpoint | /v1/completions | /v1/chat/completions |
|---|---|---|
| Input | Single prompt string | messages array |
| Roles | None | system, user, assistant, tool |
| Tool calling | Not supported | Supported |
| Status | Deprecated for GPT-4+ | Active, primary |
The old completions API treated the model like a text-in, text-out function. The chat completions API treats it like a conversational partner with roles and tool capabilities. Every modern model uses chat completions.
π¦
Authentication
Simple bearer token authentication:
bash
curl https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer sk-..." \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o",
"messages": [{"role": "user", "content": "Hello!"}]}'
For organization-level access:
bash
-H "OpenAI-Organization: org-..."
No OAuth, no API key exchange flows, no complex authentication. Just a bearer token. This simplicity is another reason the API won.
π
Practical Takeaways
One endpoint does everything β POST /v1/chat/completions is the only endpoint you need for 95% of tasks
Four roles define all conversations β system, user, assistant, tool
The format is the industry standard β learn it once, use it with every provider
Always check finish_reason β length means your output was truncated
Temperature controls creativity β 0 for deterministic, 0.7 for balanced, 1+ for creative
Switching providers = changing base_url β the OpenAI SDK works with almost any provider
π
What's Next?
Episode 37: Streaming β Why does ChatGPT show you words one at a time? It's not a fancy UI trick. The model genuinely generates tokens sequentially, and streaming delivers them as they're produced. SSE, chunked transfer, and why streaming makes everything feel faster.
β Previous
Ep 35: Agent Failure Modes
Next β
Ep 37: Streaming
Next: Episode 37 β Streaming
Watch ChatGPT's words appear one by one, like someone typing in real time. It's not a UX trick β it's how generation works.
This is part of a 98-episode series covering AI engineering from tokens to production deployment.