Skip to main content

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.

See the Claude adapter guide for a working example using this protocol with Runloop.
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

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

MethodDescriptionRequest Message
querySend user message, receive response streamUserMessage
control_requestSend control request (initialize, interrupt)ControlRequest
control_responseRespond to tool permission requestsControlResponse

Query

Send a user message and receive a stream of responses until a Result message indicates completion. UserMessage:
{
  "type": "user",
  "message": {
    "role": "user",
    "content": [
      { "type": "text", "text": "What is 2 + 2?" }
    ]
  },
  "session_id": "550e8400-e29b-41d4-a716-446655440000"
}
Fields:
FieldTypeRequiredDescription
type"user"yesMessage type discriminator
message.role"user"yesRole identifier
message.contentContentBlock[]yesArray of content blocks
session_idstring (UUID)noSession identifier
ContentBlock variants:
// 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):
{
  "type": "control_request",
  "request_id": "init-uuid",
  "request": {
    "subtype": "initialize",
    "protocolVersion": "1.0",
    "features": []
  }
}
Payload (Interrupt - stop current response):
{
  "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):
{
  "type": "control_response",
  "response": {
    "subtype": "success",
    "request_id": "uuid-from-control-request",
    "response": {
      "behavior": "allow",
      "updatedInput": { "command": "ls -la" }
    }
  }
}
Payload (Allow with permissions):
{
  "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):
{
  "type": "control_response",
  "response": {
    "subtype": "success",
    "request_id": "uuid-from-control-request",
    "response": {
      "behavior": "deny",
      "message": "Reason for denial"
    }
  }
}
Payload (Deny with interrupt):
{
  "type": "control_response",
  "response": {
    "subtype": "success",
    "request_id": "uuid-from-control-request",
    "response": {
      "behavior": "deny",
      "message": "Stopping execution",
      "interrupt": true
    }
  }
}
Response Fields:
FieldTypeDescription
behavior"allow" | "deny"Whether to allow or deny the tool use
updatedInputobjectTool input to use (required for allow)
updatedPermissionsarrayPermission rules to add (optional, allow only)
messagestringDenial reason (required for deny)
interruptbooleanStop 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.
{
  "type": "control_request",
  "request_id": "uuid",
  "request": {
    "subtype": "can_use_tool",
    "tool_name": "Bash",
    "tool_use_id": "toolu_xxx",
    "input": {
      "command": "ls -la"
    }
  }
}
Fields:
FieldTypeDescription
request_idstringUnique ID to reference in response
request.subtype"can_use_tool"Request type discriminator
request.tool_namestringName of tool (Bash, Read, Write, Edit, etc.)
request.tool_use_idstringID of the tool_use content block
request.inputobjectTool-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:
{
  "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):
{
  "type": "control_response",
  "response": {
    "subtype": "success",
    "request_id": "uuid",
    "response": {
      "behavior": "allow",
      "updatedInput": { "plan": "..." }
    }
  }
}
Response (Revise):
{
  "type": "control_response",
  "response": {
    "subtype": "success",
    "request_id": "uuid",
    "response": {
      "behavior": "deny",
      "message": "Please revise the plan to include error handling"
    }
  }
}
Response (Reject):
{
  "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:
{
  "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):
{
  "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):
{
  "type": "control_response",
  "response": {
    "subtype": "success",
    "request_id": "uuid",
    "response": {
      "behavior": "deny",
      "message": "User skipped the question"
    }
  }
}

HookCallback

Notification about a hook event.
{
  "type": "control_request",
  "request_id": "uuid",
  "request": {
    "subtype": "hook_callback",
    "hook_name": "pre-commit",
    "hook_data": { }
  }
}

McpMessage

MCP (Model Context Protocol) server communication.
{
  "type": "control_request",
  "request_id": "uuid",
  "request": {
    "subtype": "mcp_message",
    "server_name": "server-name",
    "message": { }
  }
}

SDKControlInterrupt

Claude acknowledges an interrupt request.
{
  "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):
{
  "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):
{
  "type": "system",
  "subtype": "status",
  "session_id": "uuid",
  "status": "compacting",
  "uuid": "message-uuid"
}
Compact Boundary:
{
  "type": "system",
  "subtype": "compact_boundary",
  "session_id": "uuid",
  "compact_metadata": {
    "pre_tokens": 155285,
    "trigger": "auto"
  },
  "uuid": "message-uuid"
}
Task Started:
{
  "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:
{
  "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:
{
  "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.
{
  "type": "user",
  "message": {
    "role": "user",
    "content": [{ "type": "text", "text": "..." }]
  },
  "session_id": "uuid"
}

Assistant

Claude’s response.
{
  "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:
{
  "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):
{
  "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):
{
  "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:
FieldTypeRequiredDescription
subtypestringyes"success", "error_max_turns", or "error_during_execution"
is_errorbooleanyesWhether the result represents an error
duration_msnumberyesTotal duration in milliseconds
duration_api_msnumberyesAPI call duration in milliseconds
num_turnsnumberyesNumber of agentic turns
session_idstringyesSession identifier
total_cost_usdnumberyesTotal cost in USD
errorsstring[]noError messages (when is_error is true)
permission_denialsarraynoTools blocked due to permission denials
uuidstringnoUnique identifier for this message

Error

Anthropic API error.
{
  "type": "error",
  "error": {
    "type": "overloaded_error",
    "message": "The API is temporarily overloaded"
  }
}

RateLimitEvent

Rate limit status.
{
  "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:
FieldTypeRequiredDescription
statusstringyes"allowed", "allowed_warning", or "rejected"
resetsAtnumbernoUnix timestamp when the rate limit resets
rateLimitTypestringno"five_hour", "hourly", or "seven_day"
utilizationnumbernoUtilization ratio (0.0 to 1.0)
overageStatusstringno"allowed" or "rejected"
overageDisabledReasonstringno"org_level_disabled" or "out_of_credits"
isUsingOveragebooleanyesWhether overage billing is active

ControlRequest

See Request Types section above.

ControlResponse

Acknowledgment for SDK-initiated control requests (e.g., initialize handshake).
{
  "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