beginner13 min readPart 1 of 1

OpenAI API Python Tutorial: API Key, Responses API, Streaming and Embeddings

Learn the OpenAI API in Python: create an API key, call the Responses API with the official SDK, stream output, keep conversation state, get structured outputs, call functions and create embeddings.

TechLogHub
·

Prerequisites

  • •Python 3.9 or newer
  • •An OpenAI platform account with billing enabled
  • •Basic Python and command-line knowledge

What You'll Learn

  • Create and store an OpenAI API key safely
  • Make requests with the Responses API and the official Python SDK
  • Stream responses and build multi-turn conversations
  • Extract structured data with Pydantic and call your own functions
  • Create embeddings for semantic search and handle errors, retries and cost

The OpenAI API lets you add text generation, structured data extraction, tool calling, embeddings and speech to your own applications. This tutorial walks through the OpenAI API in Python from zero: creating an API key, making your first request with the official SDK, streaming, multi-turn conversations, structured outputs, function calling, embeddings and production basics such as error handling and cost tracking.

All examples use the Responses API, which OpenAI recommends for new projects. The older Chat Completions API is still supported, and a short section at the end shows how the two map to each other.

Step 1: get an OpenAI API key

  • Sign in at platform.openai.com and create or select a project.
  • Add a payment method or credits under Billing. API usage is billed separately from a ChatGPT subscription.
  • Open the API keys page, create a new secret key for your project and copy it once; you cannot view it again.
  • Never paste the key into source code or commit it to Git. Store it in an environment variable, which the SDK reads automatically:

    export OPENAI_API_KEY="sk-proj-your-key-here"

    On Windows PowerShell use setx OPENAI_API_KEY "sk-proj-your-key-here" and open a new terminal. For local projects, a .env file loaded with python-dotenv and listed in .gitignore works well. Read more about API key hygiene.

    Step 2: install the Python SDK

    python -m venv .venv
    source .venv/bin/activate
    pip install --upgrade openai

    If you use uv, uv add openai does the same. The SDK supports both synchronous and async clients.

    Step 3: your first request

    import os
    from openai import OpenAI
    MODEL = os.environ.get("OPENAI_MODEL", "gpt-6-luna")
    client = OpenAI()
    response = client.responses.create(
        model=MODEL,
        input="Explain what an API rate limit is in two sentences.",
    )
    print(response.output_text)

    client.responses.create sends the request and output_text joins the text the model produced. The model name is read from an environment variable so you can switch models without editing code. At the time of writing, OpenAI's docs list gpt-6-luna as its most efficient model for high-volume work and gpt-6-astra as its most capable model for demanding tasks; model names and prices change regularly, so check the models page before you choose.

    Add instructions

    Use the instructions parameter for system-level guidance such as tone, format or persona:

    response = client.responses.create(
        model=MODEL,
        instructions="You are a senior Python reviewer. Reply in bullet points.",
        input="Review this: def add(a, b): return a+b",
    )
    print(response.output_text)

    Step 4: stream the response

    Streaming prints tokens as they arrive, which makes chat interfaces feel fast. Pass stream=True and handle events by type:

    stream = client.responses.create(
        model=MODEL,
        input="Write a haiku about version control.",
        stream=True,
    )
    for event in stream:
        if event.type == "response.output_text.delta":
            print(event.delta, end="", flush=True)
        elif event.type == "response.completed":
            print()
        elif event.type == "error":
            print("Error:", event.message)

    Step 5: multi-turn conversations

    The Responses API can keep conversation state for you. Pass the previous response's id and only send the new message:

    first = client.responses.create(
        model=MODEL,
        input="My name is Priya and I am learning Go.",
    )
    second = client.responses.create(
        model=MODEL,
        previous_response_id=first.id,
        input="Suggest a first project for me, using my name.",
    )
    print(second.output_text)

    Instructions are not carried over by previous_response_id, so send them again on each request. Responses are stored by default to make this work; set store=False if you manage the transcript yourself by passing a list of messages as input. For long-lived chats, OpenAI also offers a Conversations API with persistent conversation objects.

    Step 6: structured outputs with Pydantic

    When you need JSON you can parse reliably, describe the shape with a Pydantic model and use responses.parse:

    from pydantic import BaseModel
    class Invoice(BaseModel):
        vendor: str
        total: float
        currency: str
        due_date: str
    response = client.responses.parse(
        model=MODEL,
        input="Extract the invoice: ACME Ltd, total 1,250.00 EUR, due 30 November 2026.",
        text_format=Invoice,
    )
    invoice = response.output_parsed
    print(invoice.vendor, invoice.total, invoice.currency)

    The model's output is constrained to the schema, so you get a typed object instead of hoping the text is valid JSON. Without the parse helper, the equivalent raw option is the text.format parameter with a JSON schema.

    Step 7: function calling (tools)

    Function calling lets the model ask your code to run a function, for example to look up an order or query a database. You describe the function with a JSON schema, run it when the model requests it, and send the result back:

    import json
    tools = [{
        "type": "function",
        "name": "get_weather",
        "description": "Get the current temperature in Celsius for a city.",
        "parameters": {
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"],
            "additionalProperties": False,
        },
        "strict": True,
    }]
    def get_weather(city):
        return {"city": city, "temp_c": 18}
    input_items = [{"role": "user", "content": "Do I need a jacket in Paris today?"}]
    response = client.responses.create(model=MODEL, input=input_items, tools=tools)
    input_items += response.output
    for item in response.output:
        if item.type == "function_call" and item.name == "get_weather":
            args = json.loads(item.arguments)
            result = get_weather(args["city"])
            input_items.append({
                "type": "function_call_output",
                "call_id": item.call_id,
                "output": json.dumps(result),
            })
    final = client.responses.create(model=MODEL, input=input_items, tools=tools)
    print(final.output_text)

    The first call returns a function_call item instead of text. Your code executes the real function, appends a function_call_output with the matching call_id, and the second call produces the final answer. The Responses API also offers built-in tools such as web search and file search that run on OpenAI's side.

    Step 8: create embeddings

    Embeddings turn text into vectors so you can measure semantic similarity, the basis of semantic search and retrieval-augmented generation. Install numpy, then:

    import numpy as np
    docs = [
        "How do I reset my password?",
        "Our refund policy lasts 30 days.",
        "You can change your login credentials in settings.",
    ]
    result = client.embeddings.create(model="text-embedding-3-small", input=docs)
    vectors = [np.array(d.embedding) for d in result.data]
    query = client.embeddings.create(model="text-embedding-3-small", input="forgot my password")
    q = np.array(query.data[0].embedding)
    def cosine(a, b):
        return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))
    scores = [cosine(q, v) for v in vectors]
    best = int(np.argmax(scores))
    print(docs[best], round(scores[best], 3))

    The password questions score highest even though they share few words with the query. text-embedding-3-small is cheap and good for most search use cases, and text-embedding-3-large is more accurate at higher cost. For more than a few thousand documents, store vectors in a vector database instead of a Python list. See what an embedding is for the theory.

    Step 9: use the async client in web apps

    In an async web framework such as FastAPI, use AsyncOpenAI so API calls do not block the event loop. It has the same methods as the synchronous client, awaited:

    import asyncio
    from openai import AsyncOpenAI
    aclient = AsyncOpenAI()
    async def summarize(text):
        response = await aclient.responses.create(
            model=MODEL,
            input=f"Summarize in one sentence: {text}",
        )
        return response.output_text
    async def main():
        results = await asyncio.gather(
            summarize("Kubernetes schedules containers across a cluster of machines."),
            summarize("PostgreSQL is an open source relational database."),
        )
        print(results)
    asyncio.run(main())

    asyncio.gather sends both requests concurrently, which is much faster than awaiting them one by one. Keep concurrency within your rate limits; a semaphore is an easy way to cap parallel requests.

    Step 10: errors, retries, timeouts and cost

    The SDK raises typed exceptions and already retries some failures (connection errors, 429 and 5xx responses) twice with backoff by default. Wrap calls so your app degrades gracefully:

    import openai
    client = OpenAI(timeout=30.0, max_retries=3)
    try:
        response = client.responses.create(model=MODEL, input="Hello")
        print(response.output_text)
        print("Tokens used:", response.usage.total_tokens)
    except openai.RateLimitError:
        print("Rate limited or out of credits: slow down or check billing.")
    except openai.APIConnectionError:
        print("Network problem reaching the API.")
    except openai.APIStatusError as e:
        print("API error", e.status_code, e.message)

    Every response includes usage with input and output token counts, so log it to track spend per feature. To cut costs, use the smallest model that passes your tests, keep prompts short, reuse long static prefixes so prompt caching applies, and use the Batch API (about half price) for jobs that can wait up to a day. Set a monthly budget and alerts in the dashboard.

    Responses API vs Chat Completions

    Plenty of tutorials and libraries still use Chat Completions, so it helps to recognise both. This is the same first request in the older style:

    completion = client.chat.completions.create(
        model=MODEL,
        messages=[{"role": "user", "content": "Explain an API rate limit in two sentences."}],
    )
    print(completion.choices[0].message.content)

    The main differences: Responses takes input and instructions instead of a messages array, returns typed output items with an output_text helper, can store conversation state with previous_response_id, uses a flatter function tool format, moves structured outputs from response_format to text.format, and gives access to built-in tools. Chat Completions remains supported, and many other providers and local servers such as Ollama implement its format, which is why it is still common for portable code.

    Next steps

  • Turn audio into text with the OpenAI speech-to-text and Whisper API tutorial.
  • Build agents and RAG pipelines on top of the API with the LangChain tutorial.
  • Compare providers before you commit: OpenAI API vs Anthropic API vs Gemini API.
  • Prototype for free against a local model with the run an LLM locally tutorial.
  • openai apipythonopenaiembeddingsllmai

    Keep the references coming

    One email a week: new and trending developer tools, fresh comparisons, and what shipped. Unsubscribe in one click.