Lesson 02 Claude API Setup
Theme:

Theory — Claude API Setup & First Calls

Understanding authentication, the Messages API, model selection, streaming, and error handling.

1. Introduction — Why the API Matters

Claude.ai gives you a polished chat interface to interact with Claude in your browser. It is great for exploration, brainstorming, and one-off tasks. But it is not programmable. You cannot embed it in your own application, automate it inside a data pipeline, or build a product on top of it.

The Anthropic API changes that. It exposes Claude's language capabilities as a network service you can call from any programming language, on any server, as many times as you need. With the API you can:

In short: Claude.ai is a consumer product; the API is the infrastructure layer that lets you build your own products and workflows on top of Claude's capabilities. This lesson walks through everything you need to go from zero to a working API call in Python.

2. Authentication — Keeping Your Key Safe

Every API call you make is authenticated with a secret API key. This key is tied to your Anthropic account and is used to track usage and bill you accordingly. Protecting it is your responsibility.

Obtaining a Key

Log in to console.anthropic.com, navigate to API Keys in the sidebar, and click Create Key. Give it a meaningful name (e.g., lesson02-dev) so you can identify it later. Copy the key immediately — it is shown only once and cannot be retrieved afterwards. If you lose it, you must create a new one and revoke the old one.

The Golden Rule: Never Hardcode Keys

The most common beginner mistake is writing the key directly into source code:

# NEVER do this
client = anthropic.Anthropic(api_key="sk-ant-api03-abc123...")  # ← visible in git history forever

If this file is ever pushed to a public (or even private) repository, your key is compromised. GitHub, GitLab, and similar platforms are routinely scraped for leaked credentials. The consequences range from unexpected API bills to account suspension.

The Correct Pattern: Environment Variables + python-dotenv

The standard solution is to store the key in an environment variable and load it at runtime. The python-dotenv library makes this easy by reading a .env file in your project root:

# .env  (NEVER commit this file)
ANTHROPIC_API_KEY=sk-ant-api03-your-real-key-here
# .env.example  (DO commit this — it shows the shape without secrets)
ANTHROPIC_API_KEY=sk-ant-api03-your-key-here
# .gitignore  (prevents .env from being committed accidentally)
.env
*.pyc
__pycache__/
from dotenv import load_dotenv
import anthropic

load_dotenv()  # loads .env into os.environ before anything else

# Anthropic SDK reads ANTHROPIC_API_KEY from os.environ automatically
client = anthropic.Anthropic()
What happens if a key leaks? Immediately go to console.anthropic.com → API Keys and click Revoke next to the compromised key. Then create a new key and update your .env. Check your usage dashboard for any unexpected charges.

3. The Messages API

All text generation with Claude goes through a single endpoint: POST /v1/messages. The Python SDK wraps this as client.messages.create(). Understanding its structure is the foundation of everything else in this course.

Request Parameters

ParameterRequiredTypeDescription
modelYesstrThe Claude model to use (e.g., claude-3-5-haiku-20241022)
max_tokensYesintHard cap on output tokens. The model stops here even mid-sentence.
messagesYeslist[dict]Conversation turns. Each dict has role ("user" or "assistant") and content (str).
systemNostrSystem prompt — sets Claude's persona, tone, and constraints for the entire conversation.
temperatureNofloatSampling temperature 0–1. Lower = more deterministic. Default: 1.
stop_sequencesNolist[str]Stop generating when any of these strings appear in the output.

A Complete Request and Response

import anthropic
from dotenv import load_dotenv

load_dotenv()
client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-3-5-haiku-20241022",
    max_tokens=256,
    system="You are a concise technical writer.",
    messages=[
        {"role": "user", "content": "What is an API, in one sentence?"}
    ]
)

# Accessing the response
print(response.content[0].text)
# → "An API (Application Programming Interface) is a set of rules and
#    protocols that allows different software applications to communicate
#    with each other."

print(response.usage.input_tokens)   # → 28
print(response.usage.output_tokens)  # → 32
print(response.stop_reason)          # → "end_turn"
print(response.id)                   # → "msg_01XFDUDYJgAACzvnptvVoYEL"
Why is max_tokens required? Unlike some other APIs, Claude requires you to declare the maximum output length upfront. This prevents runaway responses and forces you to think about your cost budget per call. Set it to a reasonable upper bound for your use case — not an arbitrarily large number.

The messages List and Multi-turn Conversations

Claude has no built-in memory between API calls. To simulate a conversation, you pass the entire history in the messages list on every call. The list must start with a "user" message and alternate strictly between "user" and "assistant" roles:

messages = [
    {"role": "user",      "content": "What is the capital of France?"},
    {"role": "assistant", "content": "The capital of France is Paris."},
    {"role": "user",      "content": "What is its population?"},
]
# Claude receives all three turns and responds to the last user message

4. Model Selection

Anthropic offers multiple Claude models optimised for different trade-offs between speed, cost, and capability. Choosing the right model for your use case is one of the most impactful decisions you can make — both for quality and for your API bill.

Model IDSpeedCost (relative)Best For
claude-3-5-haiku-20241022 Very Fast $ Classification, sentiment, summaries, real-time chat, high-volume tasks
claude-3-5-sonnet-20241022 Moderate $$ General tasks, coding assistance, document analysis, production applications
claude-3-opus-20240229 Slow $$$$ Complex multi-step reasoning, nuanced writing, research, highest quality needs

A Practical Decision Framework

Start with Haiku during development and for any use case where speed and cost matter more than perfection — classification, intent detection, simple Q&A, chat. Haiku is typically 10-20x cheaper than Opus and responds in a fraction of the time.

Promote to Sonnet when you need stronger reasoning, code generation, or analysis quality for a production application. Sonnet hits the sweet spot between capability and cost for the majority of tasks.

Reserve Opus for tasks where quality is paramount and cost is secondary — deep research synthesis, highly nuanced writing, complex multi-hop reasoning where Sonnet is not good enough.

Tip: Build your application against Haiku first to get the prompt logic right quickly and cheaply. Then test the same prompts against Sonnet before going to production. Only try Opus if Sonnet falls short.

5. Streaming

By default, client.messages.create() waits until Claude has generated the entire response, then returns it all at once. For short responses this is fine. But for longer outputs — a multi-paragraph essay, a code file, a detailed analysis — users can wait 10–30 seconds staring at a blank screen.

Streaming solves this. Instead of waiting for the full response, the API sends tokens as soon as they are generated, using a protocol called Server-Sent Events (SSE). From the user's perspective, text appears immediately and scrolls in as Claude "thinks". This makes the application feel dramatically faster even though the total generation time is the same.

Using the Stream Context Manager

import anthropic
from dotenv import load_dotenv

load_dotenv()
client = anthropic.Anthropic()

with client.messages.stream(
    model="claude-3-5-haiku-20241022",
    max_tokens=512,
    messages=[{"role": "user", "content": "Explain black holes to a 10-year-old."}]
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)

print()  # add a newline after the stream ends

Key details:

When to use streaming: Any time a human is waiting for the response — chat interfaces, interactive terminals, web apps. For batch processing, log analysis, or server-to-server calls where you process the full response programmatically, non-streaming is often simpler.

6. Error Handling

Production API code must handle failures gracefully. The Anthropic SDK raises typed exceptions so you can catch specific error conditions and respond appropriately.

ExceptionCauseRecommended Action
anthropic.RateLimitErrorToo many requests; quota exceededRetry with exponential backoff
anthropic.APIStatusErrorHTTP 4xx/5xx from the serverLog the status code; retry 5xx only
anthropic.APIConnectionErrorNetwork connectivity issueRetry with backoff; check internet
anthropic.AuthenticationErrorInvalid or missing API keyCheck your .env; do not retry

Exponential Backoff Pattern

import time
import anthropic

def call_with_retry(client, prompt: str, max_retries: int = 3) -> str:
    """Call the API with exponential backoff on rate limit errors."""
    for attempt in range(max_retries):
        try:
            response = client.messages.create(
                model="claude-3-5-haiku-20241022",
                max_tokens=256,
                messages=[{"role": "user", "content": prompt}]
            )
            return response.content[0].text
        except anthropic.RateLimitError:
            wait = 2 ** attempt  # 1s, 2s, 4s
            print(f"Rate limited. Retrying in {wait}s... (attempt {attempt + 1}/{max_retries})")
            time.sleep(wait)
        except anthropic.APIConnectionError as e:
            print(f"Connection error: {e}. Retrying...")
            time.sleep(2 ** attempt)
    raise RuntimeError(f"API call failed after {max_retries} retries")
Tip: For production systems consider the tenacity library, which provides a battle-tested retry decorator with jitter, configurable wait strategies, and logging out of the box.

7. Further Reading