# Claude Code SDK
> Build custom AI agents with the Claude Code SDK
## Why use the Claude Code SDK?
The Claude Code SDK provides all the building blocks you need to build production-ready agents:
- **Optimized Claude integration**: Automatic prompt caching and
performance optimizations
- **Rich tool ecosystem**: File operations, code execution, web search, and
MCP extensibility
- **Advanced permissions**: Fine-grained control over agent capabilities
- **Production essentials**: Built-in error handling, session management, and
monitoring
## What can you build with the SDK?
Here are some example agent types you can create:
**Coding agents:**
- SRE agents that diagnose and fix production issues
- Security review bots that audit code for vulnerabilities
- Oncall engineering assistants that triage incidents
- Code review agents that enforce style and best practices
**Business agents:**
- Legal assistants that review contracts and compliance
- Finance advisors that analyze reports and forecasts
- Customer support agents that resolve technical issues
- Content creation assistants for marketing teams
The SDK is currently available in TypeScript and Python, with a command line interface (CLI) for quick prototyping.
## Quick start
Get your first agent running in under 5 minutes:
Install `@anthropic-ai/claude-code` from NPM:
```bash
npm install -g @anthropic-ai/claude-code
```
Install `@anthropic-ai/claude-code` from NPM:
```bash
npm install -g @anthropic-ai/claude-code
```
Install `claude-code-sdk` from PyPI and `@anthropic-ai/claude-code` from NPM:
```bash
pip install claude-code-sdk
npm install -g @anthropic-ai/claude-code # Required dependency
```
(Optional) Install IPython for interactive development:
```bash
pip install ipython
```
Get your API key from the [Anthropic Console](https://console.anthropic.com/) and set the `ANTHROPIC_API_KEY` environment variable:
```bash
export ANTHROPIC_API_KEY="your-api-key-here"
```
Create one of these example agents:
```bash
# Create a simple legal assistant
claude -p "Review this contract clause for potential issues: 'The party agrees to unlimited liability...'" \
--append-system-prompt "You are a legal assistant. Identify risks and suggest improvements."
```
```ts
// legal-agent.ts
import { query } from "@anthropic-ai/claude-code";
// Create a simple legal assistant
for await (const message of query({
prompt: "Review this contract clause for potential issues: 'The party agrees to unlimited liability...'",
options: {
systemPrompt: "You are a legal assistant. Identify risks and suggest improvements.",
maxTurns: 2
}
})) {
if (message.type === "result") {
console.log(message.result);
}
}
```
```python
# legal-agent.py
import asyncio
from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions
async def main():
async with ClaudeSDKClient(
options=ClaudeCodeOptions(
system_prompt="You are a legal assistant. Identify risks and suggest improvements.",
max_turns=2
)
) as client:
# Send the query
await client.query(
"Review this contract clause for potential issues: 'The party agrees to unlimited liability...'"
)
# Stream the response
async for message in client.receive_response():
if hasattr(message, 'content'):
# Print streaming content as it arrives
for block in message.content:
if hasattr(block, 'text'):
print(block.text, end='', flush=True)
if __name__ == "__main__":
asyncio.run(main())
```
Copy and paste the command above directly into your terminal.
1. Set up project:
```bash
npm init -y
npm install @anthropic-ai/claude-code tsx
```
2. Add `"type": "module"` to your package.json
3. Save the code above as `legal-agent.ts`, then run:
```bash
npx tsx legal-agent.ts
```
Save the code above as `legal-agent.py`, then run:
```bash
python legal-agent.py
```
For [IPython](https://ipython.org/)/Jupyter notebooks, you can run the code directly in a cell:
```python
await main()
```
Each example above creates a working agent that will:
- Analyze the prompt using Claude's reasoning capabilities
- Plan a multi-step approach to solve the problem
- Execute actions using tools like file operations, bash commands, and web search
- Provide actionable recommendations based on the analysis
## Core usage
### Overview
The Claude Code SDK allows you to interface with Claude Code in non-interactive mode from your applications.
**Prerequisites**
* Node.js 18+
* `@anthropic-ai/claude-code` from NPM
**Basic usage**
The primary command-line interface to Claude Code is the `claude` command. Use the `--print` (or `-p`) flag to run in non-interactive mode and print the final result:
```bash
claude -p "Analyze system performance" \
--append-system-prompt "You are a performance engineer" \
--allowedTools "Bash,Read,WebSearch" \
--permission-mode acceptEdits \
--cwd /path/to/project
```
**Configuration**
The SDK leverages all the CLI options available in Claude Code. Here are the key ones for SDK usage:
| Flag | Description | Example |
| :------------------------- | :----------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------ |
| `--print`, `-p` | Run in non-interactive mode | `claude -p "query"` |
| `--output-format` | Specify output format (`text`, `json`, `stream-json`) | `claude -p --output-format json` |
| `--resume`, `-r` | Resume a conversation by session ID | `claude --resume abc123` |
| `--continue`, `-c` | Continue the most recent conversation | `claude --continue` |
| `--verbose` | Enable verbose logging | `claude --verbose` |
| `--append-system-prompt` | Append to system prompt (only with `--print`) | `claude --append-system-prompt "Custom instruction"` |
| `--allowedTools` | Space-separated list of allowed tools, or
string of comma-separated list of allowed tools | `claude --allowedTools mcp__slack mcp__filesystem`
`claude --allowedTools "Bash(npm install),mcp__filesystem"` |
| `--disallowedTools` | Space-separated list of denied tools, or
string of comma-separated list of denied tools | `claude --disallowedTools mcp__splunk mcp__github`
`claude --disallowedTools "Bash(git commit),mcp__github"` |
| `--mcp-config` | Load MCP servers from a JSON file | `claude --mcp-config servers.json` |
| `--permission-prompt-tool` | MCP tool for handling permission prompts (only with `--print`) | `claude --permission-prompt-tool mcp__auth__prompt` |
For a complete list of CLI options and features, see the [CLI reference](/en/docs/claude-code/cli-reference) documentation.
**Prerequisites**
* Node.js 18+
* `@anthropic-ai/claude-code` from NPM
To view the TypeScript SDK source code, visit the [`@anthropic-ai/claude-code` page on NPM](https://www.npmjs.com/package/@anthropic-ai/claude-code?activeTab=code).
**Basic usage**
The primary interface via the TypeScript SDK is the `query` function, which returns an async iterator that streams messages as they arrive:
```ts
import { query } from "@anthropic-ai/claude-code";
for await (const message of query({
prompt: "Analyze system performance",
abortController: new AbortController(),
options: {
maxTurns: 5,
systemPrompt: "You are a performance engineer",
allowedTools: ["Bash", "Read", "WebSearch"]
}
})) {
if (message.type === "result") {
console.log(message.result);
}
}
```
**Configuration**
The TypeScript SDK accepts all arguments supported by the [command line](/en/docs/claude-code/cli-reference), as well as the following additional options:
| Argument | Description | Default |
| :--------------------------- | :---------------------------------- | :----------------------------------------------------------------------------------- |
| `abortController` | Abort controller | `new AbortController()` |
| `cwd` | Current working directory | `process.cwd()` |
| `executable` | Which JavaScript runtime to use | `node` when running with Node.js, `bun` when running with Bun |
| `executableArgs` | Arguments to pass to the executable | `[]` |
| `pathToClaudeCodeExecutable` | Path to the Claude Code executable | Executable that ships with `@anthropic-ai/claude-code` |
| `permissionMode` | Permission mode for the session | `"default"` (options: `"default"`, `"acceptEdits"`, `"plan"`, `"bypassPermissions"`) |
**Prerequisites**
* Python 3.10+
* `claude-code-sdk` from PyPI
* Node.js 18+
* `@anthropic-ai/claude-code` from NPM
To view the Python SDK source code, see the [`claude-code-sdk`](https://github.com/anthropics/claude-code-sdk-python) repo.
For interactive development, use [IPython](https://ipython.org/): `pip install ipython`
**Basic usage**
The Python SDK provides two primary interfaces:
1. The `ClaudeSDKClient` class (Recommended)
Best for streaming responses, multi-turn conversations, and interactive applications:
```python
import asyncio
from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions
async def main():
async with ClaudeSDKClient(
options=ClaudeCodeOptions(
system_prompt="You are a performance engineer",
allowed_tools=["Bash", "Read", "WebSearch"],
max_turns=5
)
) as client:
await client.query("Analyze system performance")
# Stream responses
async for message in client.receive_response():
if hasattr(message, 'content'):
for block in message.content:
if hasattr(block, 'text'):
print(block.text, end='', flush=True)
# Run as script
asyncio.run(main())
# Or in IPython/Jupyter: await main()
```
The SDK also supports passing structured messages and image inputs:
```python
from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions
async with ClaudeSDKClient() as client:
# Text message
await client.query("Analyze this code for security issues")
# Message with image reference (image will be read by Claude's Read tool)
await client.query("Explain what's shown in screenshot.png")
# Multiple messages in sequence
messages = [
"First, analyze the architecture diagram in diagram.png",
"Now suggest improvements based on the diagram",
"Finally, generate implementation code"
]
for msg in messages:
await client.query(msg)
async for response in client.receive_response():
# Process each response
pass
# The SDK handles image files through Claude's built-in Read tool
# Supported formats: PNG, JPG, PDF, and other common formats
```
The Python examples on this page use `asyncio`, but you can also use `anyio`.
2. The `query` function
For simple, one-shot queries:
```python
from claude_code_sdk import query, ClaudeCodeOptions
async for message in query(
prompt="Analyze system performance",
options=ClaudeCodeOptions(system_prompt="You are a performance engineer")
):
if type(message).__name__ == "ResultMessage":
print(message.result)
```
**Configuration**
As the Python SDK accepts all arguments supported by the [command line](/en/docs/claude-code/cli-reference) through the `ClaudeCodeOptions` class.
### Authentication
#### Anthropic API key
For basic authentication, retrieve an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/) and set the `ANTHROPIC_API_KEY` environment variable, as demonstrated in the [Quick start](#quick-start).
#### Third-party API credentials
The SDK also supports authentication via third-party API providers:
- **Amazon Bedrock**: Set `CLAUDE_CODE_USE_BEDROCK=1` environment variable and configure AWS credentials
- **Google Vertex AI**: Set `CLAUDE_CODE_USE_VERTEX=1` environment variable and configure Google Cloud credentials
For detailed configuration instructions for third-party providers, see the [Amazon Bedrock](/en/docs/claude-code/amazon-bedrock) and [Google Vertex AI](/en/docs/claude-code/google-vertex-ai) documentation.
### Multi-turn conversations
For multi-turn conversations, you can resume conversations or continue from the most recent session:
```bash
# Continue the most recent conversation
claude --continue "Now refactor this for better performance"
# Resume a specific conversation by session ID
claude --resume 550e8400-e29b-41d4-a716-446655440000 "Update the tests"
# Resume in non-interactive mode
claude --resume 550e8400-e29b-41d4-a716-446655440000 "Fix all linting issues" --no-interactive
```
```ts
import { query } from "@anthropic-ai/claude-code";
// Continue most recent conversation
for await (const message of query({
prompt: "Now refactor this for better performance",
options: { continueSession: true }
})) {
if (message.type === "result") console.log(message.result);
}
// Resume specific session
for await (const message of query({
prompt: "Update the tests",
options: {
resumeSessionId: "550e8400-e29b-41d4-a716-446655440000",
maxTurns: 3
}
})) {
if (message.type === "result") console.log(message.result);
}
```
```python
import asyncio
from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions, query
# Method 1: Using ClaudeSDKClient for persistent conversations
async def multi_turn_conversation():
async with ClaudeSDKClient() as client:
# First query
await client.query("Let's refactor the payment module")
async for msg in client.receive_response():
# Process first response
pass
# Continue in same session
await client.query("Now add comprehensive error handling")
async for msg in client.receive_response():
# Process continuation
pass
# The conversation context is maintained throughout
# Method 2: Using query function with session management
async def resume_session():
# Continue most recent conversation
async for message in query(
prompt="Now refactor this for better performance",
options=ClaudeCodeOptions(continue_conversation=True)
):
if type(message).__name__ == "ResultMessage":
print(message.result)
# Resume specific session
async for message in query(
prompt="Update the tests",
options=ClaudeCodeOptions(
resume="550e8400-e29b-41d4-a716-446655440000",
max_turns=3
)
):
if type(message).__name__ == "ResultMessage":
print(message.result)
# Run the examples
asyncio.run(multi_turn_conversation())
```
### Using Plan Mode
Plan Mode allows Claude to analyze code without making modifications, useful for code reviews and planning changes.
```bash
claude -p "Review this code" --permission-mode plan
```
```ts
import { query } from "@anthropic-ai/claude-code";
for await (const message of query({
prompt: "Your prompt here",
options: {
permissionMode: 'plan'
}
})) {
if (message.type === "result") {
console.log(message.result);
}
}
```
```python
from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions
async with ClaudeSDKClient(
options=ClaudeCodeOptions(permission_mode='plan')
) as client:
await client.query("Your prompt here")
```
Plan Mode restricts editing, file creation, and command execution. See [permission modes](/en/docs/claude-code/iam#permission-modes) for details.
### Custom system prompts
System prompts define your agent's role, expertise, and behavior. This is where you specify what kind of agent you're building:
```bash
# SRE incident response agent
claude -p "API is down, investigate" \
--append-system-prompt "You are an SRE expert. Diagnose issues systematically and provide actionable solutions."
# Legal document review agent
claude -p "Review this contract" \
--append-system-prompt "You are a corporate lawyer. Identify risks, suggest improvements, and ensure compliance."
# Append to default system prompt
claude -p "Refactor this function" \
--append-system-prompt "Always include comprehensive error handling and unit tests."
```
```ts
import { query } from "@anthropic-ai/claude-code";
// SRE incident response agent
for await (const message of query({
prompt: "API is down, investigate",
options: {
systemPrompt: "You are an SRE expert. Diagnose issues systematically and provide actionable solutions.",
maxTurns: 3
}
})) {
if (message.type === "result") console.log(message.result);
}
// Append to default system prompt
for await (const message of query({
prompt: "Refactor this function",
options: {
appendSystemPrompt: "Always include comprehensive error handling and unit tests.",
maxTurns: 2
}
})) {
if (message.type === "result") console.log(message.result);
}
```
```python
import asyncio
from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions
async def specialized_agents():
# SRE incident response agent with streaming
async with ClaudeSDKClient(
options=ClaudeCodeOptions(
system_prompt="You are an SRE expert. Diagnose issues systematically and provide actionable solutions.",
max_turns=3
)
) as sre_agent:
await sre_agent.query("API is down, investigate")
# Stream the diagnostic process
async for message in sre_agent.receive_response():
if hasattr(message, 'content'):
for block in message.content:
if hasattr(block, 'text'):
print(block.text, end='', flush=True)
# Legal review agent with custom prompt
async with ClaudeSDKClient(
options=ClaudeCodeOptions(
append_system_prompt="Always include comprehensive error handling and unit tests.",
max_turns=2
)
) as dev_agent:
await dev_agent.query("Refactor this function")
# Collect full response
full_response = []
async for message in dev_agent.receive_response():
if type(message).__name__ == "ResultMessage":
print(message.result)
asyncio.run(specialized_agents())
```
## Advanced Usage
### Custom tools via MCP
The Model Context Protocol (MCP) lets you give your agents custom tools and capabilities. This is crucial for building specialized agents that need domain-specific integrations.
**Example agent tool configurations:**
```json
{
"mcpServers": {
"slack": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-slack"],
"env": { "SLACK_TOKEN": "your-slack-token" }
},
"jira": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-jira"],
"env": { "JIRA_TOKEN": "your-jira-token" }
},
"database": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres"],
"env": { "DB_CONNECTION_STRING": "your-db-url" }
}
}
}
```
**Usage examples:**
```bash
# SRE agent with monitoring tools
claude -p "Investigate the payment service outage" \
--mcp-config sre-tools.json \
--allowedTools "mcp__datadog,mcp__pagerduty,mcp__kubernetes" \
--append-system-prompt "You are an SRE. Use monitoring data to diagnose issues."
# Customer support agent with CRM access
claude -p "Help resolve customer ticket #12345" \
--mcp-config support-tools.json \
--allowedTools "mcp__zendesk,mcp__stripe,mcp__user_db" \
--append-system-prompt "You are a technical support specialist."
```
```ts
import { query } from "@anthropic-ai/claude-code";
// SRE agent with monitoring tools
for await (const message of query({
prompt: "Investigate the payment service outage",
options: {
mcpConfig: "sre-tools.json",
allowedTools: ["mcp__datadog", "mcp__pagerduty", "mcp__kubernetes"],
systemPrompt: "You are an SRE. Use monitoring data to diagnose issues.",
maxTurns: 4
}
})) {
if (message.type === "result") console.log(message.result);
}
```
```python
import asyncio
from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions
async def mcp_enabled_agent():
# Legal agent with document access and streaming
# Note: Configure your MCP servers as needed
mcp_servers = {
# Example configuration - uncomment and configure as needed:
# "docusign": {
# "command": "npx",
# "args": ["-y", "@modelcontextprotocol/server-docusign"],
# "env": {"API_KEY": "your-key"}
# }
}
async with ClaudeSDKClient(
options=ClaudeCodeOptions(
mcp_servers=mcp_servers,
allowed_tools=["mcp__docusign", "mcp__compliance_db"],
system_prompt="You are a corporate lawyer specializing in contract review.",
max_turns=4
)
) as client:
await client.query("Review this contract for compliance risks")
# Monitor tool usage and responses
async for message in client.receive_response():
if hasattr(message, 'content'):
for block in message.content:
if hasattr(block, 'type'):
if block.type == 'tool_use':
print(f"\n[Using tool: {block.name}]\n")
elif hasattr(block, 'text'):
print(block.text, end='', flush=True)
elif hasattr(block, 'text'):
print(block.text, end='', flush=True)
if type(message).__name__ == "ResultMessage":
print(f"\n\nReview complete. Total cost: ${message.total_cost_usd:.4f}")
asyncio.run(mcp_enabled_agent())
```
When using MCP tools, you must explicitly allow them using the `--allowedTools` flag. MCP tool names follow the pattern `mcp____` where:
- `serverName` is the key from your MCP configuration file
- `toolName` is the specific tool provided by that server
This security measure ensures that MCP tools are only used when explicitly permitted.
If you specify just the server name (i.e., `mcp__`), all tools from that server will be allowed.
Glob patterns (e.g., `mcp__go*`) are not supported.
### Custom permission prompt tool
Optionally, use `--permission-prompt-tool` to pass in an MCP tool that we will use to check whether or not the user grants the model permissions to invoke a given tool. When the model invokes a tool the following happens:
1. We first check permission settings: all [settings.json files](/en/docs/claude-code/settings), as well as `--allowedTools` and `--disallowedTools` passed into the SDK; if one of these allows or denies the tool call, we proceed with the tool call
2. Otherwise, we invoke the MCP tool you provided in `--permission-prompt-tool`
The `--permission-prompt-tool` MCP tool is passed the tool name and input, and must return a JSON-stringified payload with the result. The payload must be one of:
```ts
// tool call is allowed
{
"behavior": "allow",
"updatedInput": {...}, // updated input, or just return back the original input
}
// tool call is denied
{
"behavior": "deny",
"message": "..." // human-readable string explaining why the permission was denied
}
```
**Implementation examples:**
```bash
# Use with your MCP server configuration
claude -p "Analyze and fix the security issues" \
--permission-prompt-tool mcp__security__approval_prompt \
--mcp-config security-tools.json \
--allowedTools "Read,Grep" \
--disallowedTools "Bash(rm*),Write"
# With custom permission rules
claude -p "Refactor the codebase" \
--permission-prompt-tool mcp__custom__permission_check \
--mcp-config custom-config.json \
--output-format json
```
```ts
const server = new McpServer({
name: "Test permission prompt MCP Server",
version: "0.0.1",
});
server.tool(
"approval_prompt",
'Simulate a permission check - approve if the input contains "allow", otherwise deny',
{
tool_name: z.string().describe("The name of the tool requesting permission"),
input: z.object({}).passthrough().describe("The input for the tool"),
tool_use_id: z.string().optional().describe("The unique tool use request ID"),
},
async ({ tool_name, input }) => {
return {
content: [
{
type: "text",
text: JSON.stringify(
JSON.stringify(input).includes("allow")
? {
behavior: "allow",
updatedInput: input,
}
: {
behavior: "deny",
message: "Permission denied by test approval_prompt tool",
}
),
},
],
};
}
);
// Use in SDK
import { query } from "@anthropic-ai/claude-code";
for await (const message of query({
prompt: "Analyze the codebase",
options: {
permissionPromptTool: "mcp__test-server__approval_prompt",
mcpConfig: "my-config.json",
allowedTools: ["Read", "Grep"]
}
})) {
if (message.type === "result") console.log(message.result);
}
```
```python
import asyncio
from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions
async def use_permission_prompt():
"""Example using custom permission prompt tool"""
# MCP server configuration
mcp_servers = {
# Example configuration - uncomment and configure as needed:
# "security": {
# "command": "npx",
# "args": ["-y", "@modelcontextprotocol/server-security"],
# "env": {"API_KEY": "your-key"}
# }
}
async with ClaudeSDKClient(
options=ClaudeCodeOptions(
permission_prompt_tool_name="mcp__security__approval_prompt", # Changed from permission_prompt_tool
mcp_servers=mcp_servers,
allowed_tools=["Read", "Grep"],
disallowed_tools=["Bash(rm*)", "Write"],
system_prompt="You are a security auditor"
)
) as client:
await client.query("Analyze and fix the security issues")
# Monitor tool usage and permissions
async for message in client.receive_response():
if hasattr(message, 'content'):
for block in message.content:
if hasattr(block, 'type'): # Added check for 'type' attribute
if block.type == 'tool_use':
print(f"[Tool: {block.name}] ", end='')
if hasattr(block, 'text'):
print(block.text, end='', flush=True)
# Check for permission denials in error messages
if type(message).__name__ == "ErrorMessage":
if hasattr(message, 'error') and "Permission denied" in str(message.error):
print(f"\nā ļø Permission denied: {message.error}")
# Example MCP server implementation (Python)
# This would be in your MCP server code
async def approval_prompt(tool_name: str, input: dict, tool_use_id: str = None):
"""Custom permission prompt handler"""
# Your custom logic here
if "allow" in str(input):
return json.dumps({
"behavior": "allow",
"updatedInput": input
})
else:
return json.dumps({
"behavior": "deny",
"message": f"Permission denied for {tool_name}"
})
asyncio.run(use_permission_prompt())
```
Usage notes:
- Use `updatedInput` to tell the model that the permission prompt mutated its input; otherwise, set `updatedInput` to the original input, as in the example above. For example, if the tool shows a file edit diff to the user and lets them edit the diff manually, the permission prompt tool should return that updated edit.
- The payload must be JSON-stringified
## Output formats
The SDK supports multiple output formats:
### Text output (default)
```bash
claude -p "Explain file src/components/Header.tsx"
# Output: This is a React component showing...
```
```ts
// Default text output
for await (const message of query({
prompt: "Explain file src/components/Header.tsx"
})) {
if (message.type === "result") {
console.log(message.result);
// Output: This is a React component showing...
}
}
```
```python
# Default text output with streaming
async with ClaudeSDKClient() as client:
await client.query("Explain file src/components/Header.tsx")
# Stream text as it arrives
async for message in client.receive_response():
if hasattr(message, 'content'):
for block in message.content:
if hasattr(block, 'text'):
print(block.text, end='', flush=True)
# Output streams in real-time: This is a React component showing...
```
### JSON output
Returns structured data including metadata:
```bash
claude -p "How does the data layer work?" --output-format json
```
```ts
// Collect all messages for JSON-like access
const messages = [];
for await (const message of query({
prompt: "How does the data layer work?"
})) {
messages.push(message);
}
// Access result message with metadata
const result = messages.find(m => m.type === "result");
console.log({
result: result.result,
cost: result.total_cost_usd,
duration: result.duration_ms
});
```
```python
# Collect all messages with metadata
async with ClaudeSDKClient() as client:
await client.query("How does the data layer work?")
messages = []
result_data = None
async for message in client.receive_messages():
messages.append(message)
# Capture result message with metadata
if type(message).__name__ == "ResultMessage":
result_data = {
"result": message.result,
"cost": message.total_cost_usd,
"duration": message.duration_ms,
"num_turns": message.num_turns,
"session_id": message.session_id
}
break
print(result_data)
```
Response format:
```json
{
"type": "result",
"subtype": "success",
"total_cost_usd": 0.003,
"is_error": false,
"duration_ms": 1234,
"duration_api_ms": 800,
"num_turns": 6,
"result": "The response text here...",
"session_id": "abc123"
}
```
### Streaming JSON output
Streams each message as it is received:
```bash
$ claude -p "Build an application" --output-format stream-json
```
Each conversation begins with an initial `init` system message, followed by a list of user and assistant messages, followed by a final `result` system message with stats. Each message is emitted as a separate JSON object.
## Message schema
Messages returned from the JSON API are strictly typed according to the following schema:
```ts
type SDKMessage =
// An assistant message
| {
type: "assistant";
message: Message; // from Anthropic SDK
session_id: string;
}
// A user message
| {
type: "user";
message: MessageParam; // from Anthropic SDK
session_id: string;
}
// Emitted as the last message
| {
type: "result";
subtype: "success";
duration_ms: float;
duration_api_ms: float;
is_error: boolean;
num_turns: int;
result: string;
session_id: string;
total_cost_usd: float;
}
// Emitted as the last message, when we've reached the maximum number of turns
| {
type: "result";
subtype: "error_max_turns" | "error_during_execution";
duration_ms: float;
duration_api_ms: float;
is_error: boolean;
num_turns: int;
session_id: string;
total_cost_usd: float;
}
// Emitted as the first message at the start of a conversation
| {
type: "system";
subtype: "init";
apiKeySource: string;
cwd: string;
session_id: string;
tools: string[];
mcp_servers: {
name: string;
status: string;
}[];
model: string;
permissionMode: "default" | "acceptEdits" | "bypassPermissions" | "plan";
};
```
We will soon publish these types in a JSONSchema-compatible format. We use semantic versioning for the main Claude Code package to communicate breaking changes to this format.
`Message` and `MessageParam` types are available in Anthropic SDKs. For example, see the Anthropic [TypeScript](https://github.com/anthropics/anthropic-sdk-typescript) and [Python](https://github.com/anthropics/anthropic-sdk-python/) SDKs.
## Input formats
The SDK supports multiple input formats:
### Text input (default)
```bash
# Direct argument
claude -p "Explain this code"
# From stdin
echo "Explain this code" | claude -p
```
```ts
// Direct prompt
for await (const message of query({
prompt: "Explain this code"
})) {
if (message.type === "result") console.log(message.result);
}
// From variable
const userInput = "Explain this code";
for await (const message of query({ prompt: userInput })) {
if (message.type === "result") console.log(message.result);
}
```
```python
import asyncio
from claude_code_sdk import ClaudeSDKClient
async def process_inputs():
async with ClaudeSDKClient() as client:
# Text input
await client.query("Explain this code")
async for message in client.receive_response():
# Process streaming response
pass
# Image input (Claude will use Read tool automatically)
await client.query("What's in this diagram? screenshot.png")
async for message in client.receive_response():
# Process image analysis
pass
# Multiple inputs with mixed content
inputs = [
"Analyze the architecture in diagram.png",
"Compare it with best practices",
"Generate improved version"
]
for prompt in inputs:
await client.query(prompt)
async for message in client.receive_response():
# Process each response
pass
asyncio.run(process_inputs())
```
### Streaming JSON input
A stream of messages provided via `stdin` where each message represents a user turn. This allows multiple turns of a conversation without re-launching the `claude` binary and allows providing guidance to the model while it is processing a request.
Each message is a JSON 'User message' object, following the same format as the output message schema. Messages are formatted using the [jsonl](https://jsonlines.org/) format where each line of input is a complete JSON object. Streaming JSON input requires `-p` and `--output-format stream-json`.
Currently this is limited to text-only user messages.
```bash
$ echo '{"type":"user","message":{"role":"user","content":[{"type":"text","text":"Explain this code"}]}}' | claude -p --output-format=stream-json --input-format=stream-json --verbose
```
## Agent integration examples
### SRE incident response bot
```bash
#!/bin/bash
# Automated incident response agent
investigate_incident() {
local incident_description="$1"
local severity="${2:-medium}"
claude -p "Incident: $incident_description (Severity: $severity)" \
--append-system-prompt "You are an SRE expert. Diagnose the issue, assess impact, and provide immediate action items." \
--output-format json \
--allowedTools "Bash,Read,WebSearch,mcp__datadog" \
--mcp-config monitoring-tools.json
}
# Usage
investigate_incident "Payment API returning 500 errors" "high"
```
```ts
import { query } from "@anthropic-ai/claude-code";
// Automated incident response agent
async function investigateIncident(
incidentDescription: string,
severity = "medium"
) {
const messages = [];
for await (const message of query({
prompt: `Incident: ${incidentDescription} (Severity: ${severity})`,
options: {
systemPrompt: "You are an SRE expert. Diagnose the issue, assess impact, and provide immediate action items.",
maxTurns: 6,
allowedTools: ["Bash", "Read", "WebSearch", "mcp__datadog"],
mcpConfig: "monitoring-tools.json"
}
})) {
messages.push(message);
}
return messages.find(m => m.type === "result");
}
// Usage
const result = await investigateIncident("Payment API returning 500 errors", "high");
console.log(result.result);
```
```python
import asyncio
from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions
async def investigate_incident(incident_description: str, severity: str = "medium"):
"""Automated incident response agent with real-time streaming"""
# MCP server configuration for monitoring tools
mcp_servers = {
# Example configuration - uncomment and configure as needed:
# "datadog": {
# "command": "npx",
# "args": ["-y", "@modelcontextprotocol/server-datadog"],
# "env": {"API_KEY": "your-datadog-key", "APP_KEY": "your-app-key"}
# }
}
async with ClaudeSDKClient(
options=ClaudeCodeOptions(
system_prompt="You are an SRE expert. Diagnose the issue, assess impact, and provide immediate action items.",
max_turns=6,
allowed_tools=["Bash", "Read", "WebSearch", "mcp__datadog"],
mcp_servers=mcp_servers
)
) as client:
# Send the incident details
prompt = f"Incident: {incident_description} (Severity: {severity})"
print(f"šØ Investigating: {prompt}\n")
await client.query(prompt)
# Stream the investigation process
investigation_log = []
async for message in client.receive_response():
if hasattr(message, 'content'):
for block in message.content:
if hasattr(block, 'type'):
if block.type == 'tool_use':
print(f"[{block.name}] ", end='')
if hasattr(block, 'text'):
text = block.text
print(text, end='', flush=True)
investigation_log.append(text)
# Capture final result
if type(message).__name__ == "ResultMessage":
return {
'analysis': ''.join(investigation_log),
'cost': message.total_cost_usd,
'duration_ms': message.duration_ms
}
# Usage
result = await investigate_incident("Payment API returning 500 errors", "high")
print(f"\n\nInvestigation complete. Cost: ${result['cost']:.4f}")
```
### Automated security review
```bash
# Security audit agent for pull requests
audit_pr() {
local pr_number="$1"
gh pr diff "$pr_number" | claude -p \
--append-system-prompt "You are a security engineer. Review this PR for vulnerabilities, insecure patterns, and compliance issues." \
--output-format json \
--allowedTools "Read,Grep,WebSearch"
}
# Usage and save to file
audit_pr 123 > security-report.json
```
```ts
import { query } from "@anthropic-ai/claude-code";
import { execSync } from "child_process";
async function auditPR(prNumber: number) {
// Get PR diff
const prDiff = execSync(`gh pr diff ${prNumber}`, { encoding: 'utf8' });
const messages = [];
for await (const message of query({
prompt: prDiff,
options: {
systemPrompt: "You are a security engineer. Review this PR for vulnerabilities, insecure patterns, and compliance issues.",
maxTurns: 3,
allowedTools: ["Read", "Grep", "WebSearch"]
}
})) {
messages.push(message);
}
return messages.find(m => m.type === "result");
}
// Usage
const report = await auditPR(123);
console.log(JSON.stringify(report, null, 2));
```
```python
import subprocess
import asyncio
import json
from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions
async def audit_pr(pr_number: int):
"""Security audit agent for pull requests with streaming feedback"""
# Get PR diff
pr_diff = subprocess.check_output(
["gh", "pr", "diff", str(pr_number)],
text=True
)
async with ClaudeSDKClient(
options=ClaudeCodeOptions(
system_prompt="You are a security engineer. Review this PR for vulnerabilities, insecure patterns, and compliance issues.",
max_turns=3,
allowed_tools=["Read", "Grep", "WebSearch"]
)
) as client:
print(f"š Auditing PR #{pr_number}\n")
await client.query(pr_diff)
findings = []
async for message in client.receive_response():
if hasattr(message, 'content'):
for block in message.content:
if hasattr(block, 'text'):
# Stream findings as they're discovered
print(block.text, end='', flush=True)
findings.append(block.text)
if type(message).__name__ == "ResultMessage":
return {
'pr_number': pr_number,
'findings': ''.join(findings),
'metadata': {
'cost': message.total_cost_usd,
'duration': message.duration_ms,
'severity': 'high' if 'vulnerability' in ''.join(findings).lower() else 'medium'
}
}
# Usage
report = await audit_pr(123)
print(f"\n\nAudit complete. Severity: {report['metadata']['severity']}")
print(json.dumps(report, indent=2))
```
### Multi-turn legal assistant
```bash
# Legal document review with session persistence
session_id=$(claude -p "Start legal review session" --output-format json | jq -r '.session_id')
# Review contract in multiple steps
claude -p --resume "$session_id" "Review contract.pdf for liability clauses"
claude -p --resume "$session_id" "Check compliance with GDPR requirements"
claude -p --resume "$session_id" "Generate executive summary of risks"
```
```ts
import { query } from "@anthropic-ai/claude-code";
async function legalReview() {
// Start legal review session
let sessionId: string;
for await (const message of query({
prompt: "Start legal review session",
options: { maxTurns: 1 }
})) {
if (message.type === "system" && message.subtype === "init") {
sessionId = message.session_id;
}
}
// Multi-step review using same session
const steps = [
"Review contract.pdf for liability clauses",
"Check compliance with GDPR requirements",
"Generate executive summary of risks"
];
for (const step of steps) {
for await (const message of query({
prompt: step,
options: { resumeSessionId: sessionId, maxTurns: 2 }
})) {
if (message.type === "result") {
console.log(`Step: ${step}`);
console.log(message.result);
}
}
}
}
```
```python
import asyncio
from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions
async def legal_review():
"""Legal document review with persistent session and streaming"""
async with ClaudeSDKClient(
options=ClaudeCodeOptions(
system_prompt="You are a corporate lawyer. Provide detailed legal analysis.",
max_turns=2
)
) as client:
# Multi-step review in same session
steps = [
"Review contract.pdf for liability clauses",
"Check compliance with GDPR requirements",
"Generate executive summary of risks"
]
review_results = []
for step in steps:
print(f"\nš {step}\n")
await client.query(step)
step_result = []
async for message in client.receive_response():
if hasattr(message, 'content'):
for block in message.content:
if hasattr(block, 'text'):
text = block.text
print(text, end='', flush=True)
step_result.append(text)
if type(message).__name__ == "ResultMessage":
review_results.append({
'step': step,
'analysis': ''.join(step_result),
'cost': message.total_cost_usd
})
# Summary
total_cost = sum(r['cost'] for r in review_results)
print(f"\n\nā
Legal review complete. Total cost: ${total_cost:.4f}")
return review_results
# Usage
results = await legal_review()
```
## Python-Specific Best Practices
### Key Patterns
```python
import asyncio
from claude_code_sdk import ClaudeSDKClient, ClaudeCodeOptions
# Always use context managers
async with ClaudeSDKClient() as client:
await client.query("Analyze this code")
async for msg in client.receive_response():
# Process streaming messages
pass
# Run multiple agents concurrently
async with ClaudeSDKClient() as reviewer, ClaudeSDKClient() as tester:
await asyncio.gather(
reviewer.query("Review main.py"),
tester.query("Write tests for main.py")
)
# Error handling
from claude_code_sdk import CLINotFoundError, ProcessError
try:
async with ClaudeSDKClient() as client:
# Your code here
pass
except CLINotFoundError:
print("Install CLI: npm install -g @anthropic-ai/claude-code")
except ProcessError as e:
print(f"Process error: {e}")
# Collect full response with metadata
async def get_response(client, prompt):
await client.query(prompt)
text = []
async for msg in client.receive_response():
if hasattr(msg, 'content'):
for block in msg.content:
if hasattr(block, 'text'):
text.append(block.text)
if type(msg).__name__ == "ResultMessage":
return {'text': ''.join(text), 'cost': msg.total_cost_usd}
```
### IPython/Jupyter Tips
```python
# In Jupyter, use await directly in cells
client = ClaudeSDKClient()
await client.connect()
await client.query("Analyze data.csv")
async for msg in client.receive_response():
print(msg)
await client.disconnect()
# Create reusable helper functions
async def stream_print(client, prompt):
await client.query(prompt)
async for msg in client.receive_response():
if hasattr(msg, 'content'):
for block in msg.content:
if hasattr(block, 'text'):
print(block.text, end='', flush=True)
```
## Best practices
- **Use JSON output format** for programmatic parsing of responses:
```bash
# Parse JSON response with jq
result=$(claude -p "Generate code" --output-format json)
code=$(echo "$result" | jq -r '.result')
cost=$(echo "$result" | jq -r '.cost_usd')
```
- **Handle errors gracefully** - check exit codes and stderr:
```bash
if ! claude -p "$prompt" 2>error.log; then
echo "Error occurred:" >&2
cat error.log >&2
exit 1
fi
```
- **Use session management** for maintaining context in multi-turn conversations
- **Consider timeouts** for long-running operations:
```bash
timeout 300 claude -p "$complex_prompt" || echo "Timed out after 5 minutes"
```
- **Respect rate limits** when making multiple requests by adding delays between calls
## Related resources
- [CLI usage and controls](/en/docs/claude-code/cli-reference) - Complete CLI documentation
- [GitHub Actions integration](/en/docs/claude-code/github-actions) - Automate your GitHub workflow with Claude
- [Common workflows](/en/docs/claude-code/common-workflows) - Step-by-step guides for common use cases