The OpenAI API patterns you need every day — chat completions, streaming, function calling, structured outputs, embeddings, vision, audio, and error handling — with copy-ready code.
Quick reference
| Task | Code |
|---|---|
| Install | pip install openai |
| Set API key (env) | export OPENAI_API_KEY="sk-..." |
| Chat completion | client.chat.completions.create(model="gpt-4o", messages=[...]) |
| Get text | response.choices[0].message.content |
| Stream response | stream=True + for chunk in stream |
| JSON output | response_format={"type": "json_object"} |
| Structured output | response_format=MyPydanticModel (beta.parse) |
| Function calling | tools=[{"type": "function", "function": {...}}] |
| Embeddings | client.embeddings.create(model="text-embedding-3-small", input="...") |
| Image input | Pass {"type": "image_url", "image_url": {"url": "..."}} in messages |
| DALL-E image | client.images.generate(model="dall-e-3", prompt="...") |
| Whisper transcribe | client.audio.transcriptions.create(model="whisper-1", file=f) |
| TTS audio | client.audio.speech.create(model="tts-1", voice="alloy", input="...") |
| Token count | tiktoken.encoding_for_model("gpt-4o").encode(text) |
| Async client | from openai import AsyncOpenAI |
Setup
pip install openai tiktoken
from openai import OpenAI
import os
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
# Or: OpenAI() auto-reads OPENAI_API_KEY from environment
Chat completions
Basic request:
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?"},
],
max_tokens=256,
temperature=0.7,
)
text = response.choices[0].message.content
print(text)
# Token usage
print(response.usage.prompt_tokens, response.usage.completion_tokens)
Multi-turn conversation (keep history manually):
messages = [{"role": "system", "content": "You are a concise assistant."}]
def chat(user_input):
messages.append({"role": "user", "content": user_input})
response = client.chat.completions.create(model="gpt-4o", messages=messages)
reply = response.choices[0].message.content
messages.append({"role": "assistant", "content": reply})
return reply
print(chat("Hello! My name is Alice."))
print(chat("What is my name?")) # Model remembers "Alice"
Key parameters
| Parameter | Default | Description |
|---|---|---|
model |
required | Model ID (see models table) |
messages |
required | List of {role, content} dicts |
max_tokens |
model max | Max tokens to generate |
temperature |
1.0 | Randomness 0–2 (0 = deterministic) |
top_p |
1.0 | Nucleus sampling (use instead of temperature) |
n |
1 | Number of choices to generate |
stop |
None | Stop sequences (str or list) |
stream |
False | Enable streaming |
seed |
None | Reproducible outputs (best-effort) |
logprobs |
False | Return log probabilities |
presence_penalty |
0 | Penalise new topics −2–2 |
frequency_penalty |
0 | Penalise repetition −2–2 |
Streaming
stream = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Tell me a joke."}],
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
print(delta, end="", flush=True)
print()
Collect full response while streaming:
full_text = ""
for chunk in stream:
delta = chunk.choices[0].delta.content or ""
full_text += delta
print(delta, end="", flush=True)
Structured outputs (JSON)
JSON mode (any valid JSON)
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "Return valid JSON only."},
{"role": "user", "content": "List 3 programming languages with their year created."},
],
response_format={"type": "json_object"},
)
import json
data = json.loads(response.choices[0].message.content)
Structured outputs with Pydantic (guaranteed schema)
from pydantic import BaseModel
from openai import OpenAI
client = OpenAI()
class Language(BaseModel):
name: str
year_created: int
paradigm: str
class LanguageList(BaseModel):
languages: list[Language]
response = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=[
{"role": "user", "content": "List 3 programming languages."},
],
response_format=LanguageList,
)
result = response.choices[0].message.parsed # LanguageList instance
for lang in result.languages:
print(lang.name, lang.year_created)
Function calling (tools)
import json
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
},
"required": ["city"],
},
},
}
]
messages = [{"role": "user", "content": "What's the weather in Paris?"}]
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
tool_choice="auto",
)
message = response.choices[0].message
# Check if model wants to call a function
if message.tool_calls:
tool_call = message.tool_calls[0]
args = json.loads(tool_call.function.arguments)
# Execute your actual function
weather_result = {"temperature": 18, "condition": "sunny"}
# Send result back to model
messages.append(message)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(weather_result),
})
final = client.chat.completions.create(model="gpt-4o", messages=messages, tools=tools)
print(final.choices[0].message.content)
tool_choice options:
| Value | Behaviour |
|---|---|
"auto" |
Model decides (default) |
"none" |
Never call tools |
"required" |
Always call a tool |
{"type": "function", "function": {"name": "fn"}} |
Force specific function |
Embeddings
response = client.embeddings.create(
model="text-embedding-3-small",
input="The quick brown fox",
)
vector = response.data[0].embedding # list of floats
print(len(vector)) # 1536
# Batch multiple texts
response = client.embeddings.create(
model="text-embedding-3-small",
input=["Text one", "Text two", "Text three"],
)
vectors = [item.embedding for item in response.data]
Cosine similarity:
import numpy as np
def cosine_similarity(a, b):
a, b = np.array(a), np.array(b)
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
sim = cosine_similarity(vectors[0], vectors[1])
print(f"Similarity: {sim:.4f}")
Embedding models
| Model | Dimensions | Max tokens | Best for |
|---|---|---|---|
text-embedding-3-small |
1536 | 8191 | Cost-efficient RAG |
text-embedding-3-large |
3072 | 8191 | Highest accuracy |
text-embedding-ada-002 |
1536 | 8191 | Legacy (use 3-small) |
Vision (image input)
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "What's in this image?"},
{
"type": "image_url",
"image_url": {"url": "https://example.com/photo.jpg"},
},
],
}
],
)
print(response.choices[0].message.content)
Base64 local image:
import base64
with open("image.png", "rb") as f:
b64 = base64.standard_b64encode(f.read()).decode()
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "Describe this image."},
{
"type": "image_url",
"image_url": {"url": f"data:image/png;base64,{b64}"},
},
],
}
],
)
detail level for image tokens:
| Detail | Tokens | Use when |
|---|---|---|
"low" |
85 | Fast, cheap, no detail needed |
"high" |
85 + 170/tile | Fine detail required |
"auto" |
Automatic | Default |
"image_url": {"url": "https://...", "detail": "low"}
Image generation (DALL-E)
response = client.images.generate(
model="dall-e-3",
prompt="A futuristic cityscape at sunset, digital art",
size="1024x1024",
quality="standard",
n=1,
)
image_url = response.data[0].url
revised_prompt = response.data[0].revised_prompt # DALL-E 3 auto-revises
print(image_url)
Image editing (DALL-E 2):
with open("image.png", "rb") as img, open("mask.png", "rb") as mask:
response = client.images.edit(
image=img,
mask=mask,
prompt="Add a rainbow in the sky",
n=1,
size="1024x1024",
)
| Model | Sizes | Max n | Best for |
|---|---|---|---|
dall-e-3 |
1024×1024, 1792×1024, 1024×1792 | 1 | Quality, prompt following |
dall-e-2 |
256×256 to 1024×1024 | 10 | Edits, variations, cost |
Audio
Transcription (Whisper)
with open("audio.mp3", "rb") as f:
transcript = client.audio.transcriptions.create(
model="whisper-1",
file=f,
language="en", # Optional: ISO 639-1 code
response_format="text", # "text" | "json" | "verbose_json" | "srt" | "vtt"
)
print(transcript.text)
# With timestamps (verbose_json)
with open("audio.mp3", "rb") as f:
result = client.audio.transcriptions.create(
model="whisper-1",
file=f,
response_format="verbose_json",
timestamp_granularities=["word"],
)
for word in result.words:
print(f"{word.word}: {word.start:.2f}s–{word.end:.2f}s")
Translation (to English)
with open("french_audio.mp3", "rb") as f:
result = client.audio.translations.create(
model="whisper-1",
file=f,
)
print(result.text) # Always English output
Text-to-speech (TTS)
response = client.audio.speech.create(
model="tts-1", # or "tts-1-hd" for higher quality
voice="alloy",
input="Hello, world! This is a test of the OpenAI TTS API.",
speed=1.0, # 0.25–4.0
response_format="mp3", # mp3 | opus | aac | flac | wav | pcm
)
with open("output.mp3", "wb") as f:
f.write(response.content)
Available voices: alloy, echo, fable, onyx, nova, shimmer
Async client
import asyncio
from openai import AsyncOpenAI
client = AsyncOpenAI()
async def get_completion(prompt: str) -> str:
response = await client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}],
)
return response.choices[0].message.content
# Run multiple requests concurrently
async def main():
prompts = ["Explain BFS", "Explain DFS", "Explain Dijkstra"]
results = await asyncio.gather(*[get_completion(p) for p in prompts])
for r in results:
print(r[:100])
asyncio.run(main())
Error handling
from openai import (
OpenAI,
APIConnectionError,
RateLimitError,
APIStatusError,
AuthenticationError,
BadRequestError,
)
import time
client = OpenAI()
def call_with_retry(messages, max_retries=3):
for attempt in range(max_retries):
try:
return client.chat.completions.create(
model="gpt-4o",
messages=messages,
)
except RateLimitError:
wait = 2 ** attempt
print(f"Rate limited. Retrying in {wait}s...")
time.sleep(wait)
except APIConnectionError as e:
print(f"Connection error: {e}")
time.sleep(1)
except AuthenticationError:
raise # Don't retry auth errors
except BadRequestError as e:
print(f"Bad request: {e}")
raise
except APIStatusError as e:
print(f"API error {e.status_code}: {e.message}")
if e.status_code >= 500:
time.sleep(2 ** attempt)
else:
raise
raise RuntimeError("Max retries exceeded")
Built-in retry with tenacity
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
@retry(
retry=retry_if_exception_type(RateLimitError),
wait=wait_exponential(multiplier=1, min=4, max=60),
stop=stop_after_attempt(5),
)
def chat_with_retry(messages):
return client.chat.completions.create(model="gpt-4o", messages=messages)
Error types
| Error class | Status | Cause |
|---|---|---|
AuthenticationError |
401 | Invalid API key |
PermissionDeniedError |
403 | No access to model |
NotFoundError |
404 | Model/resource not found |
RateLimitError |
429 | TPM/RPM limit hit |
BadRequestError |
400 | Invalid parameters, content policy |
InternalServerError |
500 | OpenAI server error |
APIConnectionError |
— | Network issue |
APITimeoutError |
— | Request timed out |
Token counting with tiktoken
import tiktoken
enc = tiktoken.encoding_for_model("gpt-4o")
text = "Hello, world! How are you?"
tokens = enc.encode(text)
print(len(tokens)) # 7
# Estimate cost before calling API
def count_messages_tokens(messages, model="gpt-4o"):
enc = tiktoken.encoding_for_model(model)
total = 0
for msg in messages:
total += 4 # message overhead
total += len(enc.encode(msg["content"]))
total += 2 # reply overhead
return total
messages = [
{"role": "system", "content": "You are helpful."},
{"role": "user", "content": "Explain recursion in 2 sentences."},
]
print(count_messages_tokens(messages))
Models
| Model | Context | Best for |
|---|---|---|
gpt-4o |
128k | Default: best quality + speed balance |
gpt-4o-mini |
128k | Cost-efficient for simple tasks |
gpt-4-turbo |
128k | Legacy GPT-4 Turbo |
gpt-4o-2024-08-06 |
128k | Structured outputs support |
o1 |
200k | Complex reasoning (slower, no streaming) |
o1-mini |
128k | Cost-efficient reasoning |
o3-mini |
200k | Fastest reasoning model |
text-embedding-3-small |
8k | Embeddings (cost-efficient) |
text-embedding-3-large |
8k | Embeddings (highest accuracy) |
dall-e-3 |
— | Image generation |
whisper-1 |
— | Audio transcription |
tts-1 / tts-1-hd |
— | Text-to-speech |
Moderation
response = client.moderations.create(
model="omni-moderation-latest",
input="I want to hurt someone.",
)
result = response.results[0]
print(result.flagged) # True
print(result.categories) # Namespace of booleans per category
print(result.category_scores) # Namespace of floats 0–1
Categories: harassment, harassment/threatening, hate, hate/threatening, illicit, illicit/violent, self-harm, self-harm/instructions, self-harm/intent, sexual, sexual/minors, violence, violence/graphic
Practical patterns
System prompt best practices
messages = [
{
"role": "system",
"content": """You are a senior Python developer.
Rules:
- Answer concisely, in 2–4 sentences max
- Always include a code example when relevant
- Use Python 3.12+ syntax
- If unsure, say so""",
},
{"role": "user", "content": "How do I read a file in Python?"},
]
Prompt template with variables
def build_prompt(language: str, task: str) -> str:
return f"""Write a {language} function that {task}.
Requirements:
- Include type hints
- Handle errors
- Add a brief docstring"""
messages = [
{"role": "system", "content": "You are an expert programmer."},
{"role": "user", "content": build_prompt("Python", "validates an email address")},
]
Classify text
def classify(text: str, categories: list[str]) -> str:
cats = ", ".join(categories)
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{
"role": "system",
"content": f"Classify the text into exactly one of: {cats}. Reply with only the category name.",
},
{"role": "user", "content": text},
],
temperature=0,
)
return response.choices[0].message.content.strip()
print(classify("I love this product!", ["positive", "negative", "neutral"]))
# → "positive"
Extract structured data
from pydantic import BaseModel
class Invoice(BaseModel):
vendor: str
amount: float
currency: str
date: str
line_items: list[str]
def extract_invoice(text: str) -> Invoice:
response = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=[
{"role": "system", "content": "Extract invoice data from the text."},
{"role": "user", "content": text},
],
response_format=Invoice,
)
return response.choices[0].message.parsed
invoice_text = "Invoice from Acme Corp, $1,234.50 USD on 2024-03-15. Items: Widget x2, Gadget x1"
data = extract_invoice(invoice_text)
print(data.vendor, data.amount)
RAG (Retrieval-Augmented Generation) skeleton
def rag_answer(query: str, retrieved_chunks: list[str]) -> str:
context = "\n\n".join(f"[{i+1}] {c}" for i, c in enumerate(retrieved_chunks))
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "system",
"content": "Answer using only the provided context. If the answer is not in the context, say so.",
},
{
"role": "user",
"content": f"Context:\n{context}\n\nQuestion: {query}",
},
],
)
return response.choices[0].message.content
Node.js / TypeScript
npm install openai
import OpenAI from "openai";
const client = new OpenAI(); // reads OPENAI_API_KEY
const response = await client.chat.completions.create({
model: "gpt-4o",
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "Hello!" },
],
});
console.log(response.choices[0].message.content);
Streaming in Node.js:
const stream = client.chat.completions.stream({
model: "gpt-4o",
messages: [{ role: "user", content: "Tell me a story." }],
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}
const finalResponse = await stream.finalChatCompletion();
Structured outputs in TypeScript:
import { zodResponseFormat } from "openai/helpers/zod";
import { z } from "zod";
const Step = z.object({
explanation: z.string(),
output: z.string(),
});
const MathSolution = z.object({
steps: z.array(Step),
final_answer: z.string(),
});
const completion = await client.beta.chat.completions.parse({
model: "gpt-4o-2024-08-06",
messages: [
{ role: "user", content: "Solve 8x + 31 = 2" },
],
response_format: zodResponseFormat(MathSolution, "math_solution"),
});
const solution = completion.choices[0].message.parsed;
console.log(solution.final_answer);
Common mistakes
| Mistake | Fix |
|---|---|
| Hardcoding API key in source | Use os.environ["OPENAI_API_KEY"] or .env |
| No retry on 429 | Implement exponential backoff |
| Sending full history without pruning | Trim oldest messages when approaching context limit |
temperature=0 for creative tasks |
Use temperature=0.7–1.0 for creativity |
Ignoring usage in response |
Track tokens to monitor cost |
| JSON mode without "JSON" in system prompt | Always mention "JSON" in the prompt when using json_object |
| Structured outputs on wrong model | Use gpt-4o-2024-08-06 or later for .parse() |
| Calling GPT-4 for every task | Use gpt-4o-mini for classification/simple tasks |
OpenAI API vs alternatives
| Provider | Best model | Context | Strengths |
|---|---|---|---|
| OpenAI | gpt-4o | 128k | Best all-around, widest ecosystem |
| Anthropic | claude-opus-4 | 200k | Long docs, coding, safety |
| gemini-2.0-flash | 1M | Huge context, multimodal | |
| Mistral | mistral-large | 128k | European, open weights variants |
| Meta | llama-3.3-70b | 128k | Open source, self-hostable |
| Groq | llama-3.1-70b | 128k | Fastest inference (LPU) |
FAQ
How do I get an API key?
Go to platform.openai.com, create an account, navigate to API Keys, and click "Create new secret key". Never commit this key to version control — use environment variables.
What's the difference between gpt-4o and gpt-4o-mini?gpt-4o is the full model with the best quality (~15× more expensive). gpt-4o-mini is much cheaper and faster — use it for classification, summarisation, and simple tasks where top-tier quality isn't needed.
How do I avoid hitting rate limits?
Implement exponential backoff on RateLimitError, batch requests where possible, cache responses for identical prompts, and upgrade your usage tier on the OpenAI dashboard.
Why is my JSON output not valid JSON despite json_object mode?
The model must see the word "JSON" in the system or user prompt, otherwise the API returns an error. For guaranteed schema compliance, use beta.chat.completions.parse with a Pydantic model instead.
How do I reduce token costs?
Use gpt-4o-mini for simple tasks, cache embeddings (they're identical for identical text), trim conversation history to only necessary context, use max_tokens to cap output, and avoid redundant system prompts per request.
Can I use the API in a browser / frontend?
Technically yes, but never expose your API key in client-side code — it will be stolen. Always proxy through your own backend, which calls OpenAI with your secret key, then returns the result to the browser.