Chapter 2: OpenAI API Fundamentals

In Chapter 1, you learned the fundamentals of Generative AI and sketched out the idea of an AI Learning Assistant. In this chapter, we go one level deeper: how does your Python application actually talk to an OpenAI model? Understanding this communication layer is what turns “I know what AI can do” into “I can build with it.”

What You Will Build

AI Learning Assistant
        ↓
Python Application
        ↓
OpenAI API
        ↓
OpenAI Model
        ↓
AI Response

What you’ll learn:

  • API fundamentals
  • API keys
  • Environment variables
  • The OpenAI Python SDK
  • Requests and responses
  • Model selection
  • Conversation input
  • Error handling
  • Streaming responses
  • Token and cost awareness
  • Building a complete command-line chatbot

2.1 What Is an API?

API stands for Application Programming Interface — a defined way for two pieces of software to communicate without needing to know each other’s internal implementation.

Real-world analogy: think of a restaurant.

You
    ↓
Waiter
    ↓
Kitchen
    ↓
Waiter
    ↓
Your Food

You never walk into the kitchen yourself — the waiter is the interface between you and the kitchen. An API works the same way:

Your Python Application
        ↓
API Request
        ↓
OpenAI Platform
        ↓
AI Model
        ↓
API Response
        ↓
Your Python Application

Your Python application never directly touches the AI model itself. It sends a request through the API and receives a response back — the model’s internal workings stay hidden, which is exactly the point: you get a stable, predictable interface to build against.

2.2 API Request and API Response

Every API interaction has two halves.

Request — your application sends information:

Request:
- Model
- User input
- Instructions

Response — the API sends information back:

Response:
- Generated answer
- Usage information
- Other response data

Conceptually:

REQUEST
{
  "model": "...",
  "input": "What is Python?"
}
    ↓
RESPONSE
{
  "output": "Python is a programming language..."
}

2.3 What Is an API Key?

An API key is a secret credential that authenticates your application to OpenAI — functionally, a password for your app.

Your Application
        +
API Key
        ↓
OpenAI API

Never do this:

api_key = "secret-key-here"

Why not? Because a hardcoded key can easily end up:

  • Uploaded to GitHub
  • Shared with someone by accident
  • Included in a screenshot
  • Exposed in a public application

Instead, load it from an environment variable:

Environment Variable
        ↓
Python Application
        ↓
OpenAI API

2.4 Setting Up Your Project

Create a new project:

openai-api-fundamentals/
│
├── app.py
├── .env
├── .gitignore
└── requirements.txt

Install the packages:

pip install openai python-dotenv

requirements.txt:

openai
python-dotenv

2.5 Store Your API Key Securely

Create a file called .env:

OPENAI_API_KEY=your_api_key_here

Then create .gitignore:

.env
__pycache__/

This ensures your secret file never gets accidentally committed to Git — a mistake that’s extremely common and extremely costly if a real key leaks publicly.

2.6 Create Your First API Client

Create app.py:

import os
from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()

client = OpenAI(
    api_key=os.getenv("OPENAI_API_KEY")
)

Let’s walk through this line by line.

Step 1

from dotenv import load_dotenv

This lets Python read variables out of your .env file.

Step 2

load_dotenv()

This loads OPENAI_API_KEY=... into your environment so Python can access it.

Step 3

os.getenv("OPENAI_API_KEY")

This retrieves the key’s value from the environment.

Step 4

client = OpenAI(...)

This creates the client object your code will use for every future request. Once this is set up, your application is ready to talk to the API.

2.7 Your First API Request

Add:

response = client.responses.create(
    model="YOUR_MODEL_NAME",
    input="What is Generative AI?"
)

print(response.output_text)

The flow:

Your Python Code
        ↓
client.responses.create()
        ↓
OpenAI API
        ↓
Model
        ↓
Response
        ↓
response.output_text

The Responses API is built specifically to accept model input and hand back generated output in a form that’s easy to use in application code.

2.8 Understanding the Request

This snippet:

response = client.responses.create(
    model="YOUR_MODEL_NAME",
    input="What is Generative AI?"
)

has two important pieces.

Model — model="YOUR_MODEL_NAME" tells the API which AI should handle the request.

Input — input="What is Generative AI?" is what you’re asking the AI to process.

MODEL: Which AI should process the request?
INPUT: What should the AI process?

2.9 Understanding the Response

The full response object contains more than just the visible answer text.

Response
    │
    ├── Output
    ├── Usage
    ├── Status
    └── Other metadata

For a simple text answer:

print(response.output_text)

you get:

Generative AI is a type of artificial intelligence
that can generate new content.

The rest of the response (usage stats, status, etc.) becomes important later when you need to track cost or debug failures.

2.10 Practical Demo 1: Simple Question-Answer Application

Create app.py:

import os
from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()

client = OpenAI(
    api_key=os.getenv("OPENAI_API_KEY")
)

question = input("Ask a question: ")

response = client.responses.create(
    model="YOUR_MODEL_NAME",
    input=question
)

print("\nAI Response:")
print(response.output_text)

Run it:

python app.py

Example:

Ask a question: What is an API?

Output:

AI Response:
An API is a way for software applications
to communicate with each other.

2.11 Practical Demo 2: Adding Instructions

Instead of sending only input=question, you can layer in additional instructions that shape the model’s behavior:

Role:
You are a programming teacher.

User Question:
What is an API?

In code:

response = client.responses.create(
    model="YOUR_MODEL_NAME",
    instructions="You are a friendly programming teacher.",
    input=question
)

Now the model has explicit behavioral guidance layered on top of the raw question — this is the seed of the next chapter, Prompt Engineering.

2.12 Instructions vs User Input

A useful mental model:

Instructions
        +
User Input
        ↓
AI Model
        ↓
Response

Instructions tell the model how to behave:

You are a Python teacher.
Explain concepts simply.

User Input is the actual question:

What is a list in Python?

Keeping these conceptually separate — even before we get to system/user roles formally — sets you up well for everything that follows.

2.13 Practical Demo 3: Build a Reusable Function

Rather than repeating API-calling code everywhere, wrap it in a function:

def ask_ai(question):
    response = client.responses.create(
        model="YOUR_MODEL_NAME",
        instructions="You are a helpful programming teacher.",
        input=question
    )
    return response.output_text

Then:

answer = ask_ai("What is Python?")
print(answer)

Why this is better:

ask_ai()
    ↓
API Request
    ↓
AI Response

You can now reuse ask_ai() anywhere in your application instead of duplicating request logic.

2.14 Practical Demo 4: Build a Continuous Chatbot

Now let’s combine everything into a real chatbot loop:

import os
from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()

client = OpenAI(
    api_key=os.getenv("OPENAI_API_KEY")
)

print("AI Learning Assistant")
print("Type 'exit' to quit.\n")

while True:
    question = input("You: ")

    if question.lower() == "exit":
        print("Goodbye!")
        break

    response = client.responses.create(
        model="YOUR_MODEL_NAME",
        instructions="You are a helpful programming teacher.",
        input=question
    )

    print("\nAI:", response.output_text)
    print()

Execution flow:

Start
    ↓
Ask user for input
    ↓
Is input "exit"?
├── Yes → Stop
└── No
    ↓
Send API request
    ↓
Receive response
    ↓
Display answer
    ↓
Repeat

This is your first functional command-line AI chatbot.

2.15 What Is Streaming?

Without streaming:

User Question
    ↓
Wait
    ↓
Complete Response
    ↓
Display Everything

With streaming:

User Question
    ↓
Response starts arriving
    ↓
First part appears
    ↓
More content appears
    ↓
Complete response

For long responses, streaming makes the application feel far more responsive, even though the total generation time is similar.

Normal:    [Wait..................][Complete answer]
Streaming: [The][answer][appears][piece][by][piece]

The OpenAI API supports streaming so applications can process and display output as it’s generated, rather than waiting for the whole thing.

2.16 Practical Demo 5: Streaming Output

Conceptually, streaming code looks like:

stream = client.responses.create(
    model="YOUR_MODEL_NAME",
    input="Explain artificial intelligence in detail.",
    stream=True
)

for event in stream:
    print(event)

In a real application, you’d inspect event types and print just the text deltas representing newly generated content. The key idea:

Response
    ↓
Event 1
    ↓
Event 2
    ↓
Event 3
    ↓
Event 4

instead of waiting for one large final blob.

2.17 Error Handling

API calls can fail for many reasons:

  • Invalid API key
  • No internet connection
  • Rate limit
  • Invalid request
  • Service issue
  • Incorrect model configuration

Your application should never crash unexpectedly because of this. Basic structure:

try:
    response = client.responses.create(
        model="YOUR_MODEL_NAME",
        input=question
    )
    print(response.output_text)
except Exception as error:
    print("Something went wrong.")
    print(error)

A more user-friendly version:

try:
    response = client.responses.create(
        model="YOUR_MODEL_NAME",
        input=question
    )
    print(response.output_text)
except Exception:
    print("Unable to get a response right now.")
    print("Please check your API configuration and try again.")

2.18 Better Error Handling

A production application should distinguish between different failure types rather than treating every error identically.

API Request
    ↓
Did it succeed?
├── Yes → Show response
│
└── No
    ↓
Identify error
├── Authentication
├── Rate limit
├── Invalid request
└── Network issue

Example structure:

try:
    response = client.responses.create(
        model="YOUR_MODEL_NAME",
        input=question
    )
except Exception as error:
    print(f"API Error: {error}")

Later, in production-focused chapters, we’ll layer in logging, retries, and monitoring on top of this.

2.19 Conversation History

A chatbot becomes genuinely useful once it remembers prior turns in the conversation.

User: My name is Alex.
AI: Nice to meet you, Alex.
User: What is my name?
AI: Your name is Alex.

For this to work, the application must preserve and resend relevant context.

Conversation History
        ↓
Current Question
        ↓
API Request
        ↓
AI Response

A simple local representation:

conversation = [
    {
        "role": "user",
        "content": "My name is Alex."
    },
    {
        "role": "assistant",
        "content": "Nice to meet you, Alex."
    }
]

The current question then gets added to this list before the next request. (The exact API structure for passing conversation history should always follow the current SDK/API documentation, since these details evolve.)

2.20 Practical Demo 6: Simple Conversation Memory

Basic design:

conversation = []

Flow when the user asks a question:

User Question
    ↓
Add to conversation
    ↓
Send relevant conversation
    ↓
Receive AI answer
    ↓
Store AI answer

Conceptually:

conversation.append({
    "role": "user",
    "content": question
})

Then:

conversation.append({
    "role": "assistant",
    "content": answer
})

The conversation list grows as:

[
    User Message 1,
    AI Response 1,
    User Message 2,
    AI Response 2
]

This pattern becomes foundational in:

  • AI assistants
  • Agents
  • RAG systems
  • Customer support applications

2.21 Understanding API Costs

Every API request consumes resources, and most providers bill by token.

Input + Output = Total Processing

Longer input:

Large Document
    ↓
More Input
    ↓
More Processing

Longer output requests ("Write a 10,000-word detailed report") cost more than short ones ("Explain this in 10 words").

As a developer, keep in mind:

  • Input length
  • Output length
  • Conversation history size
  • Repeated requests
  • Model selection

This is why production AI applications need ongoing cost monitoring and optimization — costs can grow silently as usage scales.

2.22 Model Selection

Different models vary in capability, speed, and cost. When choosing one, ask:

1. What is the task? Simple classification needs a different strategy than complex reasoning.

2. How fast should the response be? Real-time applications may need to prioritize speed.

3. How much does each request cost? At 1,000,000 requests, even small per-request cost differences add up fast.

4. Does the application need multimodal capabilities? Text + image tasks need capabilities beyond plain text generation.

Always check current OpenAI model documentation before finalizing a model choice for a production project — model lineups and pricing change over time.

2.23 Complete Chapter Project

Project: Command-Line AI Learning Assistant

Requirements — the application should:

  1. Load the API key securely.
  2. Create an OpenAI client.
  3. Ask the user for questions.
  4. Send questions to the model.
  5. Display responses.
  6. Continue until the user types exit.
  7. Handle API errors.
  8. Provide teacher-style instructions.

Architecture:

┌──────────────────────┐
│        User          │
└──────────┬───────────┘
           │
           ▼
┌──────────────────────┐
│  Python Application   │
└──────────┬───────────┘
           │
           ▼
┌──────────────────────┐
│      OpenAI API      │
└──────────┬───────────┘
           │
           ▼
┌──────────────────────┐
│       AI Model        │
└──────────┬───────────┘
           │
           ▼
┌──────────────────────┐
│      AI Response      │
└──────────────────────┘

2.24 Suggested Project Structure

openai-learning-assistant/
│
├── app.py
├── .env
├── .gitignore
└── requirements.txt

Later, this can evolve into:

openai-learning-assistant/
│
├── app.py
├── config.py
├── ai_service.py
├── conversation.py
├── .env
├── .gitignore
└── requirements.txt

This is exactly how a small experiment gradually becomes a maintainable application — a pattern you’ll see repeated throughout this course.

2.25 Chapter 2 Practice Challenges

Challenge 1: Ask Questions — build a working Ask a question: prompt that returns an AI response.

Challenge 2: Add a Teacher Persona — make the AI respond as “a friendly programming teacher.”

Challenge 3: Add Exit Logic — typing exit should stop the program.

Challenge 4: Add Error Handling — a failed API request should show "Something went wrong. Please try again." instead of crashing.

Challenge 5: Add Conversation Memory — get the assistant to remember "My name is Alex." and correctly answer "What is my name?" afterward.

Chapter 2 Summary

You learned the complete API communication flow:

1. User enters a question
        ↓
2. Python application creates a request
        ↓
3. API authenticates the request
        ↓
4. OpenAI model processes the input
        ↓
5. API returns a response
        ↓
6. Python displays the result

You also learned:

  • What an API is
  • What an API key is
  • How to protect API keys
  • How to use the OpenAI Python SDK
  • How to create a client
  • How to send input
  • How to read output
  • How to use instructions
  • How to build a chatbot loop
  • What streaming means
  • Why error handling matters
  • How conversation history works
  • Why model and cost selection matter

Next chapter: Chapter 3 — Prompt Engineering, where we’ll learn how to write effective instructions, design system behavior, use few-shot examples, request structured outputs, and build a powerful prompt-based AI application.

Scroll to Top