How to Build a Claude AI Partner Integration: A Practical Guide for Developers
Learn how to integrate third-party tools and services with Claude AI using the Partner API. Step-by-step guide with Python and TypeScript examples for building custom integrations.
This guide walks you through building a Claude AI partner integration, covering authentication, message streaming, tool use, and error handling with practical code examples in Python and TypeScript.
How to Build a Claude AI Partner Integration: A Practical Guide for Developers
Claude AI's Partner ecosystem allows developers and organizations to extend Claude's capabilities by integrating third-party tools, services, and data sources. Whether you're building a customer support bot, a code assistant, or a data analysis pipeline, the Partner API provides the foundation for creating seamless, intelligent integrations.
In this guide, you'll learn how to build a Claude AI partner integration from scratch. We'll cover authentication, message streaming, tool use, error handling, and best practices—with real code examples in Python and TypeScript.
Understanding the Partner API
The Partner API is a RESTful interface that allows your application to send messages to Claude and receive responses. Unlike the standard API, the Partner API is designed for integrations that need to:
- Maintain persistent sessions
- Use custom tools and functions
- Stream responses for real-time interaction
- Handle complex multi-turn conversations
Key Concepts
- API Key: Your unique authentication credential
- Session: A conversation context that persists across multiple requests
- Message: A single exchange (user input + Claude response)
- Tool: A function or API that Claude can invoke
- Stream: Real-time response delivery
Prerequisites
Before you start, make sure you have:
- A Claude API account with Partner access (contact Anthropic sales)
- Your API key (stored securely, never in client-side code)
- Python 3.8+ or Node.js 16+ installed
- Basic familiarity with REST APIs and JSON
Step 1: Setting Up Authentication
All Partner API requests require an API key passed in the x-api-key header. Here's how to set up your client:
Python Example
import requests
import json
API_KEY = "your-api-key-here"
BASE_URL = "https://api.anthropic.com/v1"
def create_client():
return {
"headers": {
"x-api-key": API_KEY,
"anthropic-version": "2023-06-01",
"content-type": "application/json"
}
}
client = create_client()
TypeScript Example
const API_KEY = process.env.CLAUDE_API_KEY;
const BASE_URL = 'https://api.anthropic.com/v1';
const headers = {
'x-api-key': API_KEY!,
'anthropic-version': '2023-06-01',
'content-type': 'application/json'
};
Security Tip: Never hardcode API keys. Use environment variables or a secrets manager.
Step 2: Sending Your First Message
Once authenticated, you can send a message to Claude. The request body includes the model, messages array, and optional parameters.
Python Example
def send_message(prompt: str, system_prompt: str = ""):
url = f"{BASE_URL}/messages"
payload = {
"model": "claude-3-opus-20240229",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": prompt}
]
}
if system_prompt:
payload["system"] = system_prompt
response = requests.post(url, headers=client["headers"], json=payload)
response.raise_for_status()
return response.json()
Usage
result = send_message("Explain quantum computing in simple terms.")
print(result["content"][0]["text"])
TypeScript Example
async function sendMessage(prompt: string, systemPrompt?: string) {
const url = ${BASE_URL}/messages;
const payload: any = {
model: 'claude-3-opus-20240229',
max_tokens: 1024,
messages: [{ role: 'user', content: prompt }]
};
if (systemPrompt) payload.system = systemPrompt;
const response = await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify(payload)
});
if (!response.ok) throw new Error(API error: ${response.status});
return response.json();
}
// Usage
sendMessage('Explain quantum computing in simple terms.')
.then(data => console.log(data.content[0].text));
Step 3: Streaming Responses for Real-Time Interaction
For a better user experience, stream Claude's responses token by token. This is especially important for chat interfaces and long responses.
Python Example with Streaming
def stream_message(prompt: str):
url = f"{BASE_URL}/messages"
payload = {
"model": "claude-3-opus-20240229",
"max_tokens": 1024,
"stream": True,
"messages": [
{"role": "user", "content": prompt}
]
}
with requests.post(url, headers=client["headers"], json=payload, stream=True) as response:
response.raise_for_status()
for line in response.iter_lines():
if line:
decoded = line.decode('utf-8')
if decoded.startswith('data: '):
data = json.loads(decoded[6:])
if data['type'] == 'content_block_delta':
yield data['delta']['text']
Usage
for chunk in stream_message("Write a short poem about AI."):
print(chunk, end='', flush=True)
TypeScript Example with Streaming
async function* streamMessage(prompt: string) {
const url = ${BASE_URL}/messages;
const payload = {
model: 'claude-3-opus-20240229',
max_tokens: 1024,
stream: true,
messages: [{ role: 'user', content: prompt }]
};
const response = await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify(payload)
});
if (!response.ok) throw new Error(API error: ${response.status});
const reader = response.body!.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n').filter(l => l.startsWith('data: '));
for (const line of lines) {
const data = JSON.parse(line.slice(6));
if (data.type === 'content_block_delta') {
yield data.delta.text;
}
}
}
}
// Usage (in an async context)
for await (const chunk of streamMessage('Write a short poem about AI.')) {
process.stdout.write(chunk);
}
Step 4: Adding Tools to Your Integration
Tools allow Claude to perform actions like querying databases, calling external APIs, or running calculations. Define tools using JSON Schema.
Defining a Tool
tools = [
{
"name": "get_weather",
"description": "Get the current weather for a city",
"input_schema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "City name"
}
},
"required": ["city"]
}
}
]
Using Tools in a Request
def ask_with_tools(prompt: str):
url = f"{BASE_URL}/messages"
payload = {
"model": "claude-3-opus-20240229",
"max_tokens": 1024,
"tools": tools,
"messages": [
{"role": "user", "content": prompt}
]
}
response = requests.post(url, headers=client["headers"], json=payload)
response.raise_for_status()
return response.json()
Claude will decide when to call the tool
result = ask_with_tools("What's the weather in Tokyo?")
When Claude decides to use a tool, the response includes a tool_use content block with the tool name and arguments. Your integration must execute the tool and return the result.
Handling Tool Calls
def handle_tool_call(tool_name: str, arguments: dict):
if tool_name == "get_weather":
city = arguments["city"]
# Call your weather API here
return {"temperature": 22, "condition": "sunny"}
raise ValueError(f"Unknown tool: {tool_name}")
In your main loop
response = ask_with_tools("What's the weather in Tokyo?")
for block in response["content"]:
if block["type"] == "tool_use":
result = handle_tool_call(block["name"], block["input"])
# Send tool result back to Claude
# (See next section for multi-turn handling)
Step 5: Handling Multi-Turn Conversations
For complex tasks, you need to maintain conversation history. Each turn includes previous messages plus the latest user input.
def multi_turn_conversation():
conversation = []
while True:
user_input = input("You: ")
if user_input.lower() in ["exit", "quit"]:
break
conversation.append({"role": "user", "content": user_input})
payload = {
"model": "claude-3-opus-20240229",
"max_tokens": 1024,
"messages": conversation
}
response = requests.post(f"{BASE_URL}/messages", headers=client["headers"], json=payload)
response.raise_for_status()
data = response.json()
assistant_message = data["content"][0]["text"]
print(f"Claude: {assistant_message}")
conversation.append({"role": "assistant", "content": assistant_message})
Step 6: Error Handling and Best Practices
Robust error handling is critical for production integrations.
Common Error Codes
| Status Code | Meaning | Recovery Strategy |
|---|---|---|
| 400 | Bad Request | Check payload format |
| 401 | Unauthorized | Verify API key |
| 429 | Rate Limited | Implement exponential backoff |
| 500 | Server Error | Retry after delay |
Python Error Handler
import time
def safe_request(url: str, payload: dict, max_retries: int = 3):
for attempt in range(max_retries):
try:
response = requests.post(url, headers=client["headers"], json=payload)
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as e:
if e.response.status_code == 429:
wait = 2 ** attempt
print(f"Rate limited. Waiting {wait}s...")
time.sleep(wait)
elif e.response.status_code >= 500:
wait = 2 ** attempt
print(f"Server error. Retrying in {wait}s...")
time.sleep(wait)
else:
raise
raise Exception("Max retries exceeded")
Best Practices Checklist
- ✅ Store API keys in environment variables or a secrets vault
- ✅ Implement rate limiting and exponential backoff
- ✅ Log all API interactions for debugging
- ✅ Validate tool inputs before execution
- ✅ Set reasonable
max_tokenslimits - ✅ Use streaming for real-time user experiences
- ✅ Handle tool call timeouts gracefully
- ✅ Monitor API usage and costs
Step 7: Testing Your Integration
Before deploying, thoroughly test your integration:
- Unit tests: Test each tool function independently
- Integration tests: Test the full message flow
- Edge cases: Empty responses, malformed tool calls, network failures
- Load testing: Ensure your integration handles concurrent users
Simple Test Script (Python)
def test_weather_tool():
result = handle_tool_call("get_weather", {"city": "London"})
assert "temperature" in result
assert "condition" in result
print("✓ Weather tool works correctly")
def test_message_sending():
result = send_message("Say 'hello'")
assert "hello" in result["content"][0]["text"].lower()
print("✓ Message sending works")
test_weather_tool()
test_message_sending()
Conclusion
Building a Claude AI partner integration is straightforward once you understand the core concepts: authentication, message passing, streaming, tool use, and error handling. By following the patterns in this guide, you can create powerful, production-ready integrations that extend Claude's capabilities into your own applications.
Remember to start simple, test thoroughly, and iterate based on real-world usage. The Partner API is designed to be flexible—leverage tools to give Claude access to your unique data and services.
Key Takeaways
- Authentication is simple: Use your API key in the
x-api-keyheader with the correct API version - Streaming improves UX: Always use streaming for real-time applications to reduce perceived latency
- Tools extend Claude's capabilities: Define tools with JSON Schema and handle tool calls in your application logic
- Handle errors gracefully: Implement retry logic with exponential backoff for rate limits and server errors
- Maintain conversation state: Keep a messages array to support multi-turn conversations and tool call chains