Call an LLM API for the first time
Send your first request to a model from code instead of a chat window, and learn to read what comes back. One runnable example, plus what changes when you swap providers.
What you’ll learn
- Get an API key from a hosted provider, or skip the key entirely by pointing at a local Ollama server.
- Install the SDK for the endpoint you are calling.
- Send a first chat completion request from code and get a real reply back.
- Read the response object: which field holds the reply text, and what the other fields tell you.
- Store the API key in an environment variable instead of writing it into your code.
- Recognize the OpenAI-compatible Chat Completions shape most providers share, and how Anthropic's Messages API differs.
So far you may have only talked to a model through a chat window: type a prompt into a page someone else built, read the reply on that same page. An API call is the same idea with the page removed. Your own code sends the prompt and gets the reply back as data, ready to use however you want. This walks through making that first call, reading what comes back, and keeping the key out of the code you write.
1. Get a key, or point at a local server instead
There are two ways to make this first call, and they lead to the exact same code in step 3. Hosted: create an account with a provider such as OpenAI, then generate a key from its dashboard, a string that starts with sk- and is shown to you exactly once.1 Local: install Ollama and it runs its own OpenAI-compatible server on your machine at http://localhost:11434/v1, no signup, no key, no per-token cost.4
sk- saved somewhere safe, or running curl http://localhost:11434/v1/models on your machine returns a JSON list instead of a connection error.2. Install the SDK
One package talks to any OpenAI-compatible endpoint, hosted or local, since the request and response shapes are the same either way.1
pip install openainpm install openaipython -c "import openai; print(openai.__version__)" prints a version number, not an error.3. Make the call
This is the same three lines whether you are pointed at a hosted account or a local server. Only the client setup changes.
from openai import OpenAI
client = OpenAI() # reads the key from OPENAI_API_KEY automatically
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "Say hello in one sentence."}],
)
print(response.choices[0].message.content)from openai import OpenAI
client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
response = client.chat.completions.create(
model="qwen2.5-coder:7b",
messages=[{"role": "user", "content": "Say hello in one sentence."}],
)
print(response.choices[0].message.content)import OpenAI from "openai";
const client = new OpenAI(); // reads OPENAI_API_KEY automatically
const response = await client.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: "Say hello in one sentence." }],
});
console.log(response.choices[0].message.content);.mjs, or add "type": "module" to your package.json, so the top-level await works. Then run it with node yourfile.mjs.4. Read the response object
The reply is not just a string, it is an object with the text nested inside, plus metadata about the call.2 The fields that matter for a first call:
| Field | What it tells you |
|---|---|
| response.choices[0].message.content | The reply text. This is what you printed above. |
| response.choices[0].finish_reason | "stop" for a natural end, "length" if it hit a token limit before finishing. |
| response.model | Which model actually answered. Providers sometimes route a name to a specific dated snapshot. |
| response.usage.total_tokens | Input tokens plus output tokens. On a hosted provider, this is what you are billed for. |
print(response.model) and print(response.usage) next to the reply, and compare them against what you asked for.5. Keep the key out of your code
Never write the key as a literal string inside a file you might commit. The SDK already looks for it in an environment variable, so set it there instead and the line OpenAI() from step 3 picks it up with no arguments.1
export OPENAI_API_KEY="sk-..."$env:OPENAI_API_KEY = "sk-..."For a project you will reopen later, put the key in a .env file in the project folder and load it at the top of your script, instead of retyping the export every time you open a new terminal.
pip install python-dotenvfrom dotenv import load_dotenv
load_dotenv() # reads .env into the environment before OpenAI() runs.env to .gitignore before your first commit. A key that reaches a public repository should be treated as already compromised, rotate it from the provider dashboard rather than assuming no one found it.6. One shape, many providers, and Anthropic's different one
The call in step 3, client.chat.completions.create with a list of {role, content} messages, is not specific to one company. Ollama's local server replies to the exact same shape, which is why step 3 only needed a different base_url and api_key, not different code.4 Other providers that describe themselves as "OpenAI-compatible" work the same way: swap base_url, api_key, and model, keep everything else.
Not every provider uses that shape, though. Anthropic's Messages API is a different one: max_tokens is a required field on every request rather than an optional limit, and the reply's text sits inside a list of typed content blocks instead of a plain string.3
import anthropic
client = anthropic.Anthropic() # reads the key from ANTHROPIC_API_KEY automatically
message = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Say hello in one sentence."}],
)
print(message.content[0].text)message.content[0].text here against response.choices[0].message.content from step 3. Same intent, a different path to reach it, which is exactly the kind of difference to expect when a provider does not advertise OpenAI compatibility.stream=True to get tokens back as they generate instead of waiting for the full reply; add a {"role": "system", "content": "..."} message to set behavior before the user turn; and keep appending to the same messages list across calls to hold a multi-turn conversation. That growing messages list is exactly what the loop in the next guide automates.Troubleshooting
- 401 or "invalid API key": the key is not set in the shell running your script, or you are calling a hosted client with no key set at all. Print
os.environ.get("OPENAI_API_KEY")to confirm it is there before debugging anything else. - 429 or a quota error on a fresh hosted account: some providers require billing details on file before the first paid call succeeds, even for a small request. A local Ollama server has no rate limit or billing step.
- ModuleNotFoundError: No module named 'openai': the package installed into a different Python than the one running your script. Run
python -m pip install openaiusing the samepythonyou use to run the file. - Connection refused against localhost:11434: Ollama is not running. Start it, then confirm with
curl http://localhost:11434/v1/modelsbefore retrying the SDK call.
You now have working code that sends a prompt and reads a real reply, the same exchange every chat interface is built on top of. From here, Build your first agent takes this exact call and wraps it in a loop that lets the model request a tool and act on the result, and What an AI agent actually is covers the concepts underneath that loop.
Sources
Verified against primary sources: August 2026.
- Developer quickstart. OpenAI (official docs). https://developers.openai.com/api/docs/quickstart
- Chat Completions API reference. OpenAI (official docs). https://developers.openai.com/api/docs/api-reference/chat/create
- Messages API reference. Anthropic (official docs). https://platform.claude.com/docs/en/api/messages
- OpenAI compatibility. Ollama docs (official). https://docs.ollama.com/api/openai-compatibility