A heavy industrial cable meeting a junction point on a near-black background, with red sparks flying at the connection. An API call is this junction: two systems touching at one small, well-defined point.
An LLM API call is one HTTPS request to a model that someone else hosts. The junction is small, documented, and sparks on first contact.

Every LLM product starts with one HTTPS request. This guide takes you from no account to a working Python call against Claude, then the same call on the OpenAI SDK, streaming, JSON output, what the call costs, and the first errors you will meet. For the concepts behind the call, read the LLM mental model for engineers .

The request lifecycle

Step 1 Build The SDK turns your arguments into a JSON request body.
Step 2 Send HTTPS POST to the provider, API key in a header.
Step 3 Generate The model produces output token by token on the provider's GPUs.
Step 4 Respond and parse JSON comes back: content, stop reason, token usage. Your code reads the blocks.

Step 1: Get an API key

Create a key in the Claude Console at platform.claude.com (Anthropic) or at platform.openai.com (OpenAI). New Anthropic accounts get a small amount of free credit to test with. To try models before creating any account, use the free options on our Playgrounds page . Export the key as an environment variable: export ANTHROPIC_API_KEY="sk-ant-...". Never paste it into code, and never commit it to Git - see Protect your accounts .

Step 2: Install the SDK

An SDK is the official client library. It handles auth headers, retries, and typed responses for you.

bash
pip install anthropic   # Anthropic
pip install openai      # OpenAI

Step 3: A minimal call to Claude

The current Sonnet-tier model ID is claude-sonnet-5. max_tokens is required and caps the output length. The client reads ANTHROPIC_API_KEY from the environment.

python
import anthropic

client = anthropic.Anthropic()

message = client.messages.create(
    model="claude-sonnet-5",
    max_tokens=200,
    messages=[
        {"role": "user", "content": "Explain what an API key is in one sentence."}
    ],
)

for block in message.content:
    if block.type == "text":
        print(block.text)

The loop matters: message.content is a list of typed blocks, not a string. On claude-sonnet-5 a reasoning block can precede the text block, so always check block.type.

The same call with the OpenAI SDK

OpenAI’s current interface is the Responses API. It takes a single input string and offers an output_text shortcut.

python
from openai import OpenAI

client = OpenAI()  # reads OPENAI_API_KEY

response = client.responses.create(
    model="gpt-5.5",
    input="Explain what an API key is in one sentence.",
)

print(response.output_text)

Same lifecycle, different shapes:

AnthropicOpenAI
Env varANTHROPIC_API_KEYOPENAI_API_KEY
Callclient.messages.create(...)client.responses.create(...)
Model IDclaude-sonnet-5gpt-5.5
Output capmax_tokens requiredoptional
Read textloop over content blocksresponse.output_text

Streaming

Streaming prints tokens as they arrive instead of waiting for the full response. Use it for anything user-facing, and for any long output.

python
with client.messages.stream(
    model="claude-sonnet-5",
    max_tokens=1000,
    messages=[{"role": "user", "content": "Explain rate limiting in three short paragraphs."}],
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)
    final = stream.get_final_message()  # full message, incl. usage

Structured output: get JSON back

Do not parse prose with regexes. Define a Pydantic model and use client.messages.parse (reusing the client from Step 3); the API constrains the output to your schema and the SDK validates it.

python
from pydantic import BaseModel

class Contact(BaseModel):
    name: str
    email: str
    demo_requested: bool

response = client.messages.parse(
    model="claude-sonnet-5",
    max_tokens=500,
    messages=[{
        "role": "user",
        "content": "Extract the contact: Jane Doe (jane@example.com) asked for a demo.",
    }],
    output_format=Contact,
)

contact = response.parsed_output  # a validated Contact instance
print(contact.name, contact.demo_requested)

What the response contains

  • id: unique message ID. Log it; support teams trace requests with it.
  • model: the model that actually answered.
  • content: list of typed blocks (text, thinking, tool_use). Filter by block.type.
  • stop_reason: why generation ended. end_turn means finished naturally. max_tokens means your cap truncated the answer; raise it and retry.
  • usage: input_tokens and output_tokens. This is the billing meter; read it after every call.

What this call costs

A token is a short chunk of text, roughly 4 English characters. Anthropic prices claude-sonnet-5 at about 1.85 EUR ($2) per million input tokens and 9.20 EUR ($10) per million output tokens until 2026-08-31, then about 2.75 EUR ($3) and 13.80 EUR ($15). EUR figures assume $1 = 0.92 EUR; billing itself is in USD. The Step 3 call sends about 20 input tokens and returns about 60 output tokens. That is (20 x 2 + 60 x 10) / 1,000,000 = $0.00064, roughly 0.06 euro cents today, and roughly 0.09 euro cents at the standard price. One million such calls cost about 590 EUR today and about 880 EUR from September. Cheap per call, real at scale - set a budget cap before you ship anything, as covered in Set spending limits before you ship .

The first errors you will hit

What it meansFix
401 authentication_errorKey missing, wrong, or revokedCheck the env var name and value
404 not_found_errorModel ID typoCopy the exact ID from the docs
429 rate_limit_errorToo many requests or tokensWait for retry-after, then retry
400 invalid_request_errorBad parameterRead the error message; it names the field

Three details save you an afternoon. First, the SDK raises typed exceptions (anthropic.AuthenticationError, anthropic.RateLimitError, anthropic.NotFoundError), so catch those instead of string-matching messages. Second, the SDK already retries 429 and 5xx errors twice with backoff before you ever see them. Third, claude-sonnet-5 rejects a non-default temperature with a 400; leave sampling parameters out entirely (see temperature and sampling ). When an error still stumps you, work through How to read an error message .

Further reading

Sources