The OpenAI API is an interface for building AI-powered applications with OpenAI models. It supports single- and multi-turn interactions, accepts both text and multimodal inputs, and integrates with built-in and third-party tools for agent workflows.
In this topic, you'll learn how to use both the Chat Completions and Responses APIs, make requests, maintain conversations across multiple turns, and stream responses to enhance user experience.
Making your first request
Before you can work with OpenAI's APIs, you need an API key. Next, send a request using your preferred language's SDK, HTTP client, or a community library. Here, we'll use Python. You can install the openai library with the following command:
$ pip install openaiThe client library will automatically read your API key from the OPENAI_API_KEY environment variable. For secure handling of your API keys, use the python-dotenv library:
$ pip install python-dotenvThen, initialize the client:
# pip install openai
from openai import OpenAI
import dotenv
import os
dotenv.load_dotenv()
client = OpenAI(
api_key=os.getenv("OPENAI_API_KEY"),
base_url=os.getenv("OPENAI_BASE_URL") # you can override the base URL if you're using a proxy
) # if these environment variables are set, the client will read them automaticallyFinally, send a request to an endpoint like Chat Completions or Responses:
A request to the Responses API requires two essential pieces of information: the model and the input. The model determines which LLM (such as gpt-5.2) processes your request, while the input provides the content to generate a response from.
response = client.responses.create(
model="gpt-5.1",
input="Explain the concept of recursion"
)
print(response.output_text)
print(f"Tokens used: {response.usage.total_tokens}")A request to the Chat Completions API requires two essential pieces of information: the model and an array of messages. The model specifies which LLM (such as gpt-5.2) processes your request, while messages provides the content to generate a response from.
completion = client.chat.completions.create(
model="gpt-5-mini", # sets the model to use. For the best results, use the most recent modelS
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Briefly explain the Python programming language."},
], # pass inputs as a list of messages
)
print(completion.choices[0].message.content) # parse the response object to access the model's responseAs shown, you can access the generated text through response.output_text for the Responses API and completion.choices[0].message.content for Chat Completions. You'll use this in most scenarios where you only need the model's response. In some cases, however, you may need to parse the output for additional processing or to access additional information. For instance, the usage field breaks down token consumption: input, output, and total.
For new projects, we recommend using the Responses API instead of Chat Completions.
Next steps
Now that you know how to make basic requests, let's see how you can handle multi-modal inputs and configure some additional parameters. When you want to include images alongside text, structure your input as a list of objects. Each object specifies its type and content:
response = client.responses.create(
model="gpt-5.1",
input=[
{
"role": "user",
"content": [
{ "type": "input_text", "text": "What's shown in this diagram?" },
{
"type": "input_image",
"image_url": "https://ucarecdn.com/5069650e-1948-4ec5-8489-6977209559fd/-/stretch/off/-/resize/2200x/-/format/webp/"
}
]
}
]
)
print(response.output_text)completion = client.chat.completions.create(
model="gpt-5.1",
messages=[
{
"role": "user",
"content": [
{ "type": "text", "text": "What's shown in this diagram?" },
{
"type": "image_url",
"image_url": {
"url": "https://ucarecdn.com/5069650e-1948-4ec5-8489-6977209559fd/-/stretch/off/-/resize/2200x/-/format/webp/"
}
}
]
}
]
)
print(completion.choices[0].message.content)For the Responses API, you can also provide file inputs the same way:
response = client.responses.create(
model="gpt-5-mini",
input=[
{
"role": "user",
"content": [
{ "type": "input_text", "text": "what is in this file?" },
{
"type": "input_file",
"file_url": "https://arxiv.org/pdf/2004.07606"
}
]
}
]
)
print(response.output_text)If you're using a reasoning model like gpt-5.2, you can include a parameter to set the reasoning level:
response = client.responses.create(
model="gpt-5.1",
input="Explain the concept of recursion",
reasoning={
"effort": "medium"
}
)Here are additional parameters you can set for these endpoints:
temperature— a value between 0 and 2. Controls randomness; a lower value produces more predictable results, while a higher one makes outputs more random.top_p— an alternative to temperature. Only results in a particular percentage are returned (like0.8for results in the top 80%). We don't recommend using bothtop_pandtemperature.stream— use this if you need results as soon as the model starts generating a response.reasoning_effort— useful for reasoning models. A lower value results in faster responses and fewer reasoning tokens.instructions— instructions provided by the developer. This is only available in the Responses API.
Finally, you can use Python's standard error handling patterns. Network issues, authentication failures, rate limits, and other errors will raise exceptions you can catch:
from openai import OpenAI, BadRequestError
try:
response = client.responses.create(
# model="gpt-5.1", # the model field is required
input="Hello!"
)
print(response.output)
except BadRequestError as e:
print(f"Request failed: {e.message}")Calculating token usage
Using the OpenAI API comes with associated fees. When you use these APIs, you can easily estimate your token usage and approximate costs through the usage field in the API response:
print(f"\\nToken Usage:")
print(f" Input tokens: {response.usage.input_tokens}")
print(f" Output tokens: {response.usage.output_tokens}")
print(f" Total tokens: {response.usage.total_tokens}")print(f"\\nToken Usage:")
print(f" Prompt tokens: {completion.usage.prompt_tokens}")
print(f" Completion tokens: {completion.usage.completion_tokens}")
print(f" Total tokens: {completion.usage.total_tokens}")You can then calculate the price by multiplying the input/output tokens by pre-defined price-per-token rates. Here's a helper function you can use:
def calculate_cost(input_tokens, output_tokens):
"""Calculate USD cost for gpt-5-mini."""
input_cost = (input_tokens / 1_000_000) * 0.250 # $0.250 per 1M input tokens
output_cost = (output_tokens / 1_000_000) * 2.000 # $2.000 per 1M output tokens
return input_cost + output_cost
print(f"\\nTotal Cost: ${calculate_cost(response.usage.input_tokens, response.usage.output_tokens)}")
"""
Token Usage:
Input tokens: 24
Output tokens: 362
Total tokens: 386
Total Cost: $0.0007300000000000001
"""def calculate_cost(prompt_tokens, completion_tokens):
"""Calculate USD cost for gpt-5-mini."""
prompt_cost = (prompt_tokens / 1_000_000) * 0.250 # $0.250 per 1M input tokens
completion_cost = (completion_tokens / 1_000_000) * 2.000 # $2.000 per 1M output tokens
return prompt_cost + completion_cost
print(f"\\nTotal Cost: ${calculate_cost(completion.usage.prompt_tokens, completion.usage.completion_tokens)}")
"""
Token Usage:
Prompt tokens: 24
Completion tokens: 362
Total tokens: 386
Total Cost: $0.0007300000000000001
"""Prices are subject to change, so always confirm the latest pricing on OpenAI's official documentation to ensure accurate cost estimates.
A great strategy for optimizing usage is following effective prompting techniques. Longer prompts use more tokens, which translates to higher costs, while shorter prompts are more cost-effective. However, don't compromise on the details. Make sure your prompts are detailed enough to get a useful response from the model. Costs can add up quickly with extensive use, making it important to budget and manage your API usage.
Multi-turn conversations
Single exchanges work fine for simple queries, but most applications need back-and-forth conversations. There are several ways to handle multi-turn exchanges with the Responses API. Manually, you can pass previous messages alternating between user and assistant messages to construct chat history:
response = client.responses.create(
model="gpt-5.1",
input=[
{"role": "developer", "content": "Speak in cipher."},
{"role": "user", "content": "My name is James Bond."},
{"role": "assistant", "content": "Hello, James Bond! 🕵️ Nice to meet you!"},
{"role": "user", "content": "What did I say my name was again? I may have lied. "},
],
)
# You said your name was James Bond, but now you're hinting you may have fibbed about that! 😄
# No worries—whether you're actually a secret agent or not, I'm here to help either way.You can also use these other roles for different inputs:
system— high-level instructions for the model.developer— instructions provided by the developer. This role also corresponds to theinstructionsparameter when creating responses. We'll use this in the next snippet.user— the user's input.assistant— messages generated by the model.
For Chat Completions, this is the only way to handle multi-turn exchanges:
completion = client.chat.completions.create(
model="gpt-5.1",
messages=[
{"role": "system", "content": "Speak covertly."},
{"role": "user", "content": "My name is James Bond."},
{"role": "assistant", "content": "Hello, James Bond! 🕵️ Nice to meet you!"},
{"role": "user", "content": "What did I say my name was again? I may have lied. "},
],
)
print(completion.choices[0].message.content)
# You said your name was James Bond, but now you're hinting you may have fibbed about that! 😄
# No worries—whether you're actually a secret agent or not, I'm here to help either way.With the Responses API, you don't have to manage conversation state manually. Instead, you can use the previous response's ID to reference previous outputs. Every response includes an id field—a unique string that references the entire conversation up to that point. To continue a conversation, use the previous_response_id parameter and pass the previous response's ID:
# Start a conversation
response1 = client.responses.create(
model="gpt-5.1",
instructions="Your analysis should be under 50 words. ",
input="I'm building a web app. Should I use REST or GraphQL?"
)
print("Response 1... \\n")
print(response1.output_text)
# Continue the conversation
response2 = client.responses.create(
model="gpt-5.1",
instructions="I only need about five bullet points. ",
input="What are the main advantages of your recommendation?",
previous_response_id=response1.id
)
print("\\nResponse 2:\\n", response2.output_text)The second request doesn't need to repeat the original question about REST versus GraphQL. The model already knows the context through response1.id. When you reference a previous response, the API automatically loads the complete conversation history.
You only track the most recent response ID. Each ID forms a link in a chain, and the API follows this chain backward to reconstruct context.
Another way to manage conversations is to use the Conversations API alongside the Responses API. Think of a conversation as a container for messages, tool calls, tool outputs, and other data related to a request. To use this capability, create a conversation object first:
conversation = client.conversations.create()
print(conversation.id) # save this for later and comment out the above code to avoid creating a new conversation each time you run the codeThen, use this object in later responses to share context across sessions and jobs:
response = client.responses.create(
model="gpt-5.1",
input=[{"role": "user", "content": "What are the 5 Ds of dodgeball?"}],
conversation="<conv_id>" # use the conversation ID from before
)
response2 = client.responses.create(
model="gpt-5.1",
input=[{"role": "user", "content": "Okay, I honestly don't get it."}],
conversation="conv_id" # use the conversation ID from before
)
print(response2.output_text)
"""
The humor is in how *stupidly serious* the "advice" is.
1. He promises **5 different skills** (the "5 Ds").
2. He then says: **"Dodge, duck, dip, dive, and… dodge."**
3. That's only **4 unique words**. He repeats "dodge" and pretends it's a full, legitimate list.
"""Managing chat history
When conversations get too long, you can shrink them to avoid exceeding the models' context window and save tokens by using .compact as follows:
# Have a multi-turn conversation
response1 = client.responses.create(
model="gpt-5.1",
input="Explain quantum computing"
)
response2 = client.responses.create(
model="gpt-5.1",
input="What are qubits?",
previous_response_id=response1.id
)
# Compact the entire conversation chain
compacted = client.responses.compact(
model="gpt-5.1",
previous_response_id=response2.id # References the full history
)
"""
compacted.output now contains a condensed version of:
- Original input about quantum computing
- Assistant's explanation
- Follow-up about qubits
- Assistant's response about qubits
"""
# Use the compacted history in a new request
response3 = client.responses.create(
model="gpt-5.1",
input=compacted.output + [{"role": "user", "content": "How does this differ from classical computing?"}]
)
print(response3.output_text)By default, response objects have a time-to-live of 30 days on OpenAI's servers. You can disable retention by setting store to false when making a request. If your use case requires long-term storage of conversation history, it may help to store the data in your own databases and manually construct the chat history, as we saw earlier.
For conversations, you can delete (.delete), update (.update), retrieve (.retrieve), and view items in a conversation:
items = client.conversations.items.list("conv_id", limit=10)
print(items.data)Streaming
When you need immediate feedback, the API offers streaming mode. This mode delivers text incrementally as the model generates it, creating a more engaging experience. To enable streaming, just set stream=True in your request. The API will return a stream object you can iterate over:
stream = client.responses.create(
model="gpt-5.1",
input="Write a brief history of programming languages",
stream=True
)
for event in stream:
if event.type == "response.output_text.delta":
print(event.delta, end="", flush=True)
elif event.type == "response.done":
print("\n\nDone!")stream = client.chat.completions.create(
model="gpt-5.1",
messages=[
{"role": "system", "content": "Speak covertly."},
{"role": "user", "content": "Write a brief history of programming languages"},
],
stream=True
)
for chunk in stream:
content = chunk.choices[0].delta.content
if content:
print(content, end="", flush=True)
print("\n\nDone!")When streaming in conversations, capture the response ID from the final chunk. You'll need this to continue the conversation:
response_id = None
response_text = ""
stream = client.responses.create(
model="gpt-5.1",
input="Tell me about Python decorators",
stream=True,
previous_response_id=response_id
)
for event in stream:
if event.type == "response.output_text.delta":
response_text += event.delta
print(event.delta, end="", flush=True)
# The response object contains the ID
if hasattr(event, 'response') and event.response and hasattr(event.response, 'id'):
response_id = event.response.id
print(f"\n\nResponse ID: {response_id}")When streaming, it's also important to handle errors that occur mid-generation gracefully. This ensures users receive useful feedback about the interruption.
Conclusion
The OpenAI API provides a streamlined interface for interacting with OpenAI models. You've learned how to make basic and multimodal requests, carry out multi-turn conversations, manage chat history, and stream responses for real-time experiences. The patterns you've learned here form the foundation for building AI-powered applications with OpenAI models.