Mastering Claude API: A Practical Guide to Authentication, Streaming, and Error Handling
Learn how to authenticate, stream responses, and handle errors with the Claude API. Includes Python and TypeScript code examples for real-world use.
This guide covers the essentials of using the Claude API: setting up authentication with API keys, making your first request, streaming responses for real-time output, and implementing robust error handling. You'll get practical code examples in Python and TypeScript.
Introduction
The Claude API from Anthropic gives developers direct access to Claude's powerful language models. Whether you're building a chatbot, an analysis tool, or a creative writing assistant, understanding the core API patterns is essential.
This guide walks you through the three pillars of working with the Claude API: authentication, streaming, and error handling. By the end, you'll be able to make your first API call, handle responses in real time, and gracefully manage failures.
Prerequisites
- An Anthropic account with an API key (get one at console.anthropic.com)
- Python 3.8+ or Node.js 18+ installed
- Basic familiarity with REST APIs and JSON
1. Authentication
Every request to the Claude API requires an API key. You pass it via the x-api-key header.
Python Example
import requests
API_KEY = "your-api-key-here"
headers = {
"x-api-key": API_KEY,
"anthropic-version": "2023-06-01",
"content-type": "application/json"
}
data = {
"model": "claude-3-5-sonnet-20241022",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Hello, Claude!"}]
}
response = requests.post(
"https://api.anthropic.com/v1/messages",
headers=headers,
json=data
)
print(response.json())
TypeScript Example
const API_KEY = "your-api-key-here";
const response = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"x-api-key": API_KEY,
"anthropic-version": "2023-06-01",
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "claude-3-5-sonnet-20241022",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello, Claude!" }],
}),
});
const data = await response.json();
console.log(data);
Security tip: Never hardcode API keys in your source code. Use environment variables or a secrets manager.
2. Streaming Responses
For a better user experience, stream Claude's responses token by token. This reduces perceived latency and lets you display output as it's generated.
Python with requests
import requests
import json
API_KEY = "your-api-key-here"
headers = {
"x-api-key": API_KEY,
"anthropic-version": "2023-06-01",
"content-type": "application/json"
}
data = {
"model": "claude-3-5-sonnet-20241022",
"max_tokens": 1024,
"stream": True,
"messages": [{"role": "user", "content": "Write a short poem about AI."}]
}
with requests.post(
"https://api.anthropic.com/v1/messages",
headers=headers,
json=data,
stream=True
) as response:
for line in response.iter_lines():
if line:
# Each line starts with "data: "
if line.startswith(b"data: "):
json_str = line[6:].decode("utf-8")
if json_str == "[DONE]":
break
chunk = json.loads(json_str)
if chunk["type"] == "content_block_delta":
print(chunk["delta"]["text"], end="", flush=True)
TypeScript with Fetch API
const API_KEY = "your-api-key-here";
const response = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"x-api-key": API_KEY,
"anthropic-version": "2023-06-01",
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "claude-3-5-sonnet-20241022",
max_tokens: 1024,
stream: true,
messages: [{ role: "user", content: "Write a short poem about AI." }],
}),
});
const reader = response.body!.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() || "";
for (const line of lines) {
if (line.startsWith("data: ")) {
const jsonStr = line.slice(6);
if (jsonStr === "[DONE]") break;
const chunk = JSON.parse(jsonStr);
if (chunk.type === "content_block_delta") {
process.stdout.write(chunk.delta.text);
}
}
}
}
3. Error Handling
APIs fail. Network issues, rate limits, and invalid requests happen. Here's how to handle them gracefully.
Common Error Codes
| Status Code | Meaning | Typical Cause |
|---|---|---|
| 400 | Bad Request | Invalid JSON or missing required field |
| 401 | Unauthorized | Invalid or missing API key |
| 429 | Rate Limit Exceeded | Too many requests in a short time |
| 500 | Internal Server Error | Anthropic server issue (retry) |
Python with Retry Logic
import requests
import time
from requests.exceptions import RequestException
API_KEY = "your-api-key-here"
MAX_RETRIES = 3
def call_claude_with_retry(payload):
headers = {
"x-api-key": API_KEY,
"anthropic-version": "2023-06-01",
"content-type": "application/json"
}
for attempt in range(MAX_RETRIES):
try:
response = requests.post(
"https://api.anthropic.com/v1/messages",
headers=headers,
json=payload,
timeout=30
)
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as e:
if response.status_code == 429:
wait = int(response.headers.get("Retry-After", 2 ** attempt))
print(f"Rate limited. Waiting {wait}s...")
time.sleep(wait)
continue
elif response.status_code >= 500:
if attempt < MAX_RETRIES - 1:
wait = 2 ** attempt
print(f"Server error. Retrying in {wait}s...")
time.sleep(wait)
continue
else:
raise Exception(f"Server error after {MAX_RETRIES} retries: {e}")
else:
raise
except RequestException as e:
if attempt < MAX_RETRIES - 1:
wait = 2 ** attempt
print(f"Network error. Retrying in {wait}s...")
time.sleep(wait)
continue
else:
raise Exception(f"Network error after {MAX_RETRIES} retries: {e}")
return None
Usage
payload = {
"model": "claude-3-5-sonnet-20241022",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Hello!"}]
}
result = call_claude_with_retry(payload)
print(result)
TypeScript with Exponential Backoff
async function callClaudeWithRetry(
payload: object,
maxRetries: number = 3
): Promise<any> {
const headers = {
"x-api-key": API_KEY,
"anthropic-version": "2023-06-01",
"Content-Type": "application/json",
};
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(
"https://api.anthropic.com/v1/messages",
{
method: "POST",
headers,
body: JSON.stringify(payload),
}
);
if (!response.ok) {
if (response.status === 429) {
const wait = Math.pow(2, attempt) * 1000;
console.log(Rate limited. Waiting ${wait}ms...);
await new Promise((resolve) => setTimeout(resolve, wait));
continue;
} else if (response.status >= 500) {
if (attempt < maxRetries - 1) {
const wait = Math.pow(2, attempt) * 1000;
console.log(Server error. Retrying in ${wait}ms...);
await new Promise((resolve) => setTimeout(resolve, wait));
continue;
} else {
throw new Error(
Server error after ${maxRetries} retries: ${response.status}
);
}
} else {
throw new Error(HTTP error ${response.status});
}
}
return await response.json();
} catch (error) {
if (attempt < maxRetries - 1) {
const wait = Math.pow(2, attempt) * 1000;
console.log(Network error. Retrying in ${wait}ms...);
await new Promise((resolve) => setTimeout(resolve, wait));
} else {
throw error;
}
}
}
}
Best Practices
- Always use environment variables for your API key. Never commit it to version control.
- Set reasonable timeouts (30 seconds for non-streaming, longer for streaming).
- Implement exponential backoff for retries to avoid hammering the API.
- Log errors with enough context (request ID, model, timestamp) for debugging.
- Use streaming for interactive applications to improve perceived performance.
Conclusion
You now have a solid foundation for working with the Claude API. Authentication is straightforward with API keys, streaming gives you real-time output, and proper error handling makes your application resilient. Start with the non-streaming examples to verify connectivity, then add streaming and retry logic as your use case demands.
Key Takeaways
- Authenticate every request with the
x-api-keyheader and the correctanthropic-version. - Enable streaming by setting
"stream": truein your request body and parsing Server-Sent Events. - Handle HTTP 429 (rate limit) and 5xx (server) errors with exponential backoff retry logic.
- Always store API keys in environment variables, never in code.
- Use the
claude-3-5-sonnet-20241022model for the best balance of speed and quality.