> ## Documentation Index
> Fetch the complete documentation index at: https://docs.runloop.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Claude Code SDK Protocol Specification

> Wire protocol specification for interacting with Claude Code via JSON Lines

<Tip>
  See the [Claude adapter guide](/docs/axons/broker/claude) for a working example using this protocol with Runloop.
</Tip>

This document specifies the client-side API for interacting with Claude Code via the JSON Lines protocol.

**Official documentation**: [https://platform.claude.com/docs/en/agent-sdk/user-input](https://platform.claude.com/docs/en/agent-sdk/user-input)

## Overview

The SDK provides a bidirectional communication channel with Claude Code:

* **Input**: Messages sent from the SDK to Claude
* **Output**: Messages received from Claude

All messages are JSON Lines (newline-delimited JSON) over stdin/stdout.

***

## Client Methods

| Method             | Description                                  | Request Message   |
| ------------------ | -------------------------------------------- | ----------------- |
| `query`            | Send user message, receive response stream   | `UserMessage`     |
| `control_request`  | Send control request (initialize, interrupt) | `ControlRequest`  |
| `control_response` | Respond to tool permission requests          | `ControlResponse` |

***

### Query

Send a user message and receive a stream of responses until a `Result` message indicates completion.

**UserMessage:**

```json theme={null}
{
  "type": "user",
  "message": {
    "role": "user",
    "content": [
      { "type": "text", "text": "What is 2 + 2?" }
    ]
  },
  "session_id": "550e8400-e29b-41d4-a716-446655440000"
}
```

**Fields:**

| Field             | Type             | Required | Description                |
| ----------------- | ---------------- | -------- | -------------------------- |
| `type`            | `"user"`         | yes      | Message type discriminator |
| `message.role`    | `"user"`         | yes      | Role identifier            |
| `message.content` | `ContentBlock[]` | yes      | Array of content blocks    |
| `session_id`      | `string (UUID)`  | no       | Session identifier         |

**ContentBlock variants:**

```json theme={null}
// Text
{ "type": "text", "text": "Hello world" }

// Image (base64)
{
  "type": "image",
  "source": {
    "type": "base64",
    "media_type": "image/png",
    "data": "<base64-encoded-data>"
  }
}
```

***

### Control Request

Send a control request to Claude.

**Payload (Initialize - enable tool approval protocol):**

```json theme={null}
{
  "type": "control_request",
  "request_id": "init-uuid",
  "request": {
    "subtype": "initialize",
    "protocolVersion": "1.0",
    "features": []
  }
}
```

**Payload (Interrupt - stop current response):**

```json theme={null}
{
  "type": "control_request",
  "request_id": "interrupt-uuid",
  "request": {
    "subtype": "interrupt"
  }
}
```

***

### Control Response

Respond to a `ControlRequest` received from Claude. All responses use the `subtype: "success"` envelope with a nested `response` object.

**Payload (Allow):**

```json theme={null}
{
  "type": "control_response",
  "response": {
    "subtype": "success",
    "request_id": "uuid-from-control-request",
    "response": {
      "behavior": "allow",
      "updatedInput": { "command": "ls -la" }
    }
  }
}
```

**Payload (Allow with permissions):**

```json theme={null}
{
  "type": "control_response",
  "response": {
    "subtype": "success",
    "request_id": "uuid-from-control-request",
    "response": {
      "behavior": "allow",
      "updatedInput": { "command": "ls -la" },
      "updatedPermissions": [
        {
          "type": "addRules",
          "rules": [{ "toolName": "Bash" }],
          "behavior": "allow",
          "destination": "session"
        }
      ]
    }
  }
}
```

**Payload (Deny):**

```json theme={null}
{
  "type": "control_response",
  "response": {
    "subtype": "success",
    "request_id": "uuid-from-control-request",
    "response": {
      "behavior": "deny",
      "message": "Reason for denial"
    }
  }
}
```

**Payload (Deny with interrupt):**

```json theme={null}
{
  "type": "control_response",
  "response": {
    "subtype": "success",
    "request_id": "uuid-from-control-request",
    "response": {
      "behavior": "deny",
      "message": "Stopping execution",
      "interrupt": true
    }
  }
}
```

**Response Fields:**

| Field                | Type                  | Description                                    |
| -------------------- | --------------------- | ---------------------------------------------- |
| `behavior`           | `"allow"` \| `"deny"` | Whether to allow or deny the tool use          |
| `updatedInput`       | `object`              | Tool input to use (required for allow)         |
| `updatedPermissions` | `array`               | Permission rules to add (optional, allow only) |
| `message`            | `string`              | Denial reason (required for deny)              |
| `interrupt`          | `boolean`             | Stop agent execution (optional, deny only)     |

***

## Request Types (Claude → SDK)

When tool approval is enabled, Claude sends `ControlRequest` messages to the SDK.

### `CanUseTool`

Claude requests permission to use a tool.

```json theme={null}
{
  "type": "control_request",
  "request_id": "uuid",
  "request": {
    "subtype": "can_use_tool",
    "tool_name": "Bash",
    "tool_use_id": "toolu_xxx",
    "input": {
      "command": "ls -la"
    }
  }
}
```

**Fields:**

| Field                 | Type             | Description                                  |
| --------------------- | ---------------- | -------------------------------------------- |
| `request_id`          | `string`         | Unique ID to reference in response           |
| `request.subtype`     | `"can_use_tool"` | Request type discriminator                   |
| `request.tool_name`   | `string`         | Name of tool (Bash, Read, Write, Edit, etc.) |
| `request.tool_use_id` | `string`         | ID of the tool\_use content block            |
| `request.input`       | `object`         | Tool-specific input parameters               |

### `ExitPlanMode` (Plan Review)

When Claude runs in plan mode, it sends this request asking the user to approve, revise, or reject the plan before executing.

**Request:**

```json theme={null}
{
  "type": "control_request",
  "request_id": "uuid",
  "request": {
    "subtype": "can_use_tool",
    "tool_name": "ExitPlanMode",
    "tool_use_id": "toolu_xxx",
    "input": {
      "plan": "## Plan\n\n1. First step\n2. Second step\n..."
    }
  }
}
```

**Response (Execute Plan):**

```json theme={null}
{
  "type": "control_response",
  "response": {
    "subtype": "success",
    "request_id": "uuid",
    "response": {
      "behavior": "allow",
      "updatedInput": { "plan": "..." }
    }
  }
}
```

**Response (Revise):**

```json theme={null}
{
  "type": "control_response",
  "response": {
    "subtype": "success",
    "request_id": "uuid",
    "response": {
      "behavior": "deny",
      "message": "Please revise the plan to include error handling"
    }
  }
}
```

**Response (Reject):**

```json theme={null}
{
  "type": "control_response",
  "response": {
    "subtype": "success",
    "request_id": "uuid",
    "response": {
      "behavior": "deny",
      "message": "User rejected the plan"
    }
  }
}
```

### `AskUserQuestion` (Structured Questions)

Claude can ask structured multiple-choice questions for user clarification.

**Request:**

```json theme={null}
{
  "type": "control_request",
  "request_id": "uuid",
  "request": {
    "subtype": "can_use_tool",
    "tool_name": "AskUserQuestion",
    "tool_use_id": "toolu_xxx",
    "input": {
      "questions": [
        {
          "header": "Clarify",
          "question": "What do you mean by 'the box'?",
          "multiSelect": false,
          "options": [
            { "label": "A file/directory", "description": "A file or folder named 'box'" },
            { "label": "A Docker container" }
          ]
        }
      ]
    }
  }
}
```

**Response (Answer):**

```json theme={null}
{
  "type": "control_response",
  "response": {
    "subtype": "success",
    "request_id": "uuid",
    "response": {
      "behavior": "allow",
      "updatedInput": {
        "questions": [
          {
            "header": "Clarify",
            "question": "What do you mean by 'the box'?",
            "multiSelect": false,
            "options": [
              { "label": "A file/directory", "description": "A file or folder named 'box'" },
              { "label": "A Docker container" }
            ]
          }
        ],
        "answers": {
          "What do you mean by 'the box'?": "A Docker container"
        }
      }
    }
  }
}
```

**Response (Skip):**

```json theme={null}
{
  "type": "control_response",
  "response": {
    "subtype": "success",
    "request_id": "uuid",
    "response": {
      "behavior": "deny",
      "message": "User skipped the question"
    }
  }
}
```

### `HookCallback`

Notification about a hook event.

```json theme={null}
{
  "type": "control_request",
  "request_id": "uuid",
  "request": {
    "subtype": "hook_callback",
    "hook_name": "pre-commit",
    "hook_data": { }
  }
}
```

### `McpMessage`

MCP (Model Context Protocol) server communication.

```json theme={null}
{
  "type": "control_request",
  "request_id": "uuid",
  "request": {
    "subtype": "mcp_message",
    "server_name": "server-name",
    "message": { }
  }
}
```

### `SDKControlInterrupt`

Claude acknowledges an interrupt request.

```json theme={null}
{
  "type": "control_request",
  "request_id": "uuid",
  "request": {
    "subtype": "sdk_control_interrupt"
  }
}
```

***

## Output Types (Claude → SDK)

These are the message types received from Claude via `receive()`.

### `System`

Initialization, status, and task messages.

**Init (session start):**

```json theme={null}
{
  "type": "system",
  "subtype": "init",
  "session_id": "uuid",
  "cwd": "/path/to/project",
  "model": "claude-sonnet-4",
  "tools": ["Bash", "Read", "Write", "Edit", "Glob", "Grep"],
  "mcp_servers": [],
  "slash_commands": ["compact", "cost", "review"],
  "agents": ["Bash", "Explore", "Plan"],
  "plugins": [{ "name": "plugin-name", "path": "/path/to/plugin" }],
  "skills": [],
  "claude_code_version": "2.1.52",
  "apiKeySource": "none",
  "output_style": "default",
  "permissionMode": "default"
}
```

**Status (e.g., compacting):**

```json theme={null}
{
  "type": "system",
  "subtype": "status",
  "session_id": "uuid",
  "status": "compacting",
  "uuid": "message-uuid"
}
```

**Compact Boundary:**

```json theme={null}
{
  "type": "system",
  "subtype": "compact_boundary",
  "session_id": "uuid",
  "compact_metadata": {
    "pre_tokens": 155285,
    "trigger": "auto"
  },
  "uuid": "message-uuid"
}
```

**Task Started:**

```json theme={null}
{
  "type": "system",
  "subtype": "task_started",
  "session_id": "uuid",
  "task_id": "task-id",
  "task_type": "local_agent",
  "tool_use_id": "toolu_xxx",
  "description": "Task description",
  "uuid": "message-uuid"
}
```

**Task Progress:**

```json theme={null}
{
  "type": "system",
  "subtype": "task_progress",
  "session_id": "uuid",
  "task_id": "task-id",
  "tool_use_id": "toolu_xxx",
  "description": "Current activity",
  "last_tool_name": "Read",
  "usage": {
    "duration_ms": 13996,
    "tool_uses": 9,
    "total_tokens": 38779
  },
  "uuid": "message-uuid"
}
```

**Task Notification:**

```json theme={null}
{
  "type": "system",
  "subtype": "task_notification",
  "session_id": "uuid",
  "task_id": "task-id",
  "status": "completed",
  "summary": "Task completed successfully",
  "output_file": "/path/to/output",
  "tool_use_id": "toolu_xxx",
  "usage": {
    "duration_ms": 172300,
    "tool_uses": 11,
    "total_tokens": 42005
  },
  "uuid": "message-uuid"
}
```

### `User`

Echo of the user message sent.

```json theme={null}
{
  "type": "user",
  "message": {
    "role": "user",
    "content": [{ "type": "text", "text": "..." }]
  },
  "session_id": "uuid"
}
```

### `Assistant`

Claude's response.

```json theme={null}
{
  "type": "assistant",
  "message": {
    "id": "msg_xxx",
    "role": "assistant",
    "model": "claude-sonnet-4-20250514",
    "content": [
      { "type": "text", "text": "Here's my response..." },
      {
        "type": "tool_use",
        "id": "toolu_xxx",
        "name": "Bash",
        "input": { "command": "ls -la" }
      },
      {
        "type": "thinking",
        "thinking": "Let me analyze this..."
      }
    ],
    "stop_reason": "end_turn",
    "stop_sequence": null,
    "usage": {
      "input_tokens": 1500,
      "output_tokens": 200,
      "cache_creation_input_tokens": 0,
      "cache_read_input_tokens": 1200,
      "service_tier": "standard",
      "cache_creation": {
        "ephemeral_1h_input_tokens": 0,
        "ephemeral_5m_input_tokens": 0
      }
    }
  },
  "session_id": "uuid",
  "uuid": "message-uuid",
  "parent_tool_use_id": null
}
```

### `Result`

Query completion.

**Success:**

```json theme={null}
{
  "type": "result",
  "subtype": "success",
  "is_error": false,
  "duration_ms": 5000,
  "duration_api_ms": 4500,
  "num_turns": 3,
  "session_id": "uuid",
  "total_cost_usd": 0.05,
  "uuid": "message-uuid",
  "permission_denials": []
}
```

**Error (max turns exceeded):**

```json theme={null}
{
  "type": "result",
  "subtype": "error_max_turns",
  "is_error": true,
  "duration_ms": 60000,
  "duration_api_ms": 55000,
  "num_turns": 100,
  "session_id": "uuid",
  "total_cost_usd": 1.50,
  "errors": ["Maximum turns exceeded"],
  "permission_denials": []
}
```

**Error (during execution):**

```json theme={null}
{
  "type": "result",
  "subtype": "error_during_execution",
  "is_error": true,
  "duration_ms": 0,
  "duration_api_ms": 0,
  "num_turns": 0,
  "session_id": "uuid",
  "total_cost_usd": 0,
  "errors": ["No conversation found with session ID: ..."],
  "permission_denials": []
}
```

**Result Fields:**

| Field                | Type       | Required | Description                                                     |
| -------------------- | ---------- | -------- | --------------------------------------------------------------- |
| `subtype`            | `string`   | yes      | `"success"`, `"error_max_turns"`, or `"error_during_execution"` |
| `is_error`           | `boolean`  | yes      | Whether the result represents an error                          |
| `duration_ms`        | `number`   | yes      | Total duration in milliseconds                                  |
| `duration_api_ms`    | `number`   | yes      | API call duration in milliseconds                               |
| `num_turns`          | `number`   | yes      | Number of agentic turns                                         |
| `session_id`         | `string`   | yes      | Session identifier                                              |
| `total_cost_usd`     | `number`   | yes      | Total cost in USD                                               |
| `errors`             | `string[]` | no       | Error messages (when `is_error` is true)                        |
| `permission_denials` | `array`    | no       | Tools blocked due to permission denials                         |
| `uuid`               | `string`   | no       | Unique identifier for this message                              |

### `Error`

Anthropic API error.

```json theme={null}
{
  "type": "error",
  "error": {
    "type": "overloaded_error",
    "message": "The API is temporarily overloaded"
  }
}
```

### `RateLimitEvent`

Rate limit status.

```json theme={null}
{
  "type": "rate_limit_event",
  "session_id": "uuid",
  "uuid": "message-uuid",
  "rate_limit_info": {
    "status": "allowed",
    "resetsAt": 1771390800,
    "rateLimitType": "five_hour",
    "utilization": 0.85,
    "overageStatus": "rejected",
    "overageDisabledReason": "org_level_disabled",
    "isUsingOverage": false
  }
}
```

**RateLimitInfo Fields:**

| Field                   | Type      | Required | Description                                       |
| ----------------------- | --------- | -------- | ------------------------------------------------- |
| `status`                | `string`  | yes      | `"allowed"`, `"allowed_warning"`, or `"rejected"` |
| `resetsAt`              | `number`  | no       | Unix timestamp when the rate limit resets         |
| `rateLimitType`         | `string`  | no       | `"five_hour"`, `"hourly"`, or `"seven_day"`       |
| `utilization`           | `number`  | no       | Utilization ratio (0.0 to 1.0)                    |
| `overageStatus`         | `string`  | no       | `"allowed"` or `"rejected"`                       |
| `overageDisabledReason` | `string`  | no       | `"org_level_disabled"` or `"out_of_credits"`      |
| `isUsingOverage`        | `boolean` | yes      | Whether overage billing is active                 |

### `ControlRequest`

See [Request Types](#request-types-claude--sdk) section above.

### `ControlResponse`

Acknowledgment for SDK-initiated control requests (e.g., initialize handshake).

```json theme={null}
{
  "type": "control_response",
  "response": {
    "subtype": "success",
    "request_id": "init-uuid"
  }
}
```

***

## Typical Flow

```
SDK                                Claude
 |                                    |
 |-- UserMessage ------------------->|
 |                                    |
 |<-- System (init) -----------------|
 |<-- Assistant (response) ----------|
 |<-- ControlRequest (CanUseTool) ---|
 |                                    |
 |-- ControlResponse (allow) ------->|
 |                                    |
 |<-- Assistant (tool result) -------|
 |<-- Result (completion) -----------|
```

***

## Wire Protocol

* **Transport**: stdin/stdout of Claude CLI process
* **Format**: JSON Lines (one JSON object per line, newline-delimited)
* **Encoding**: UTF-8
* **Buffer size**: 10MB recommended for stdout reader

***

## Version Compatibility

The protocol is unstable. Current tested version: **2.1.52**
