---
title: Remote Client API Reference | Letta Docs
description: Reference for Letta Code remote client WebSocket commands, responses, and events.
---

Use this reference after the [Remote Client API quick start](/letta-code/remote-websocket-api/index.md).

## Authentication

Use an `Authorization` header when your WebSocket client supports headers:

```
Authorization: Bearer <token>
```

Browser clients cannot set WebSocket headers. For browsers, put the token in the URL:

```
?token=<token>
```

## REST endpoints

### List environments

Use this to find an online Letta Code environment and get its `connectionId`.

**Request**

```
GET /v1/environments?onlineOnly=true
Authorization: Bearer $LETTA_API_KEY
```

**Response**

```
{
  "connections": [
    {
      "id": "env-db-id",
      "connectionId": "conn-...",
      "deviceId": "device-abc123",
      "connectionName": "Work laptop",
      "organizationId": "org-...",
      "connectedAt": 1780950000000,
      "lastHeartbeat": 1780950030000,
      "lastSeenAt": 1780950000000,
      "firstSeenAt": 1780940000000,
      "currentMode": "standard",
      "metadata": {
        "workingDirectory": "/workspace/project",
        "gitBranch": "main"
      }
    }
  ],
  "hasNextPage": false
}
```

Use `connectionId` to open the status WebSocket.

## WebSocket endpoint

Connect to the status WebSocket:

```
wss://api.letta.com/v1/environments/{connectionId}/status/ws?agentId={agentId}&conversationId={conversationId}&channel=stream
```

Browser fallback with query-token auth:

```
wss://api.letta.com/v1/environments/{connectionId}/status/ws?agentId={agentId}&conversationId={conversationId}&channel=stream&token={token}
```

## Shared fields

| Field                     | Direction                        | Meaning                                                            |
| ------------------------- | -------------------------------- | ------------------------------------------------------------------ |
| `type`                    | Both                             | Message type. Commands and events are distinguished by `type`.     |
| `request_id`              | Client → device, device → client | Client-generated ID for request/response matching.                 |
| `runtime.agent_id`        | Client → device                  | Persistent Letta agent ID.                                         |
| `runtime.conversation_id` | Client → device                  | Conversation/thread ID.                                            |
| `seq`                     | Server → client                  | Transport sequence number. ACK with `{ "type": "ack", "seq": n }`. |
| `event_seq`               | Device → client                  | Device event sequence number. Use to detect missed events.         |
| `idempotency_key`         | Device → client                  | Event dedupe key.                                                  |

## Response events

Most one-shot commands return a response event with the same `request_id`. Match responses to requests by `request_id`.

Filesystem responses:

| Send command        | Receive event                |
| ------------------- | ---------------------------- |
| `list_in_directory` | `list_in_directory_response` |
| `get_tree`          | `get_tree_response`          |
| `read_file`         | `read_file_response`         |
| `write_file`        | `write_file_response`        |
| `edit_file`         | `edit_file_response`         |
| `search_files`      | `search_files_response`      |
| `grep_in_files`     | `grep_in_files_response`     |

Memory and MemFS responses:

| Send command         | Receive event                 |
| -------------------- | ----------------------------- |
| `list_memory`        | `list_memory_response`        |
| `memory_history`     | `memory_history_response`     |
| `memory_file_at_ref` | `memory_file_at_ref_response` |
| `memory_commit_diff` | `memory_commit_diff_response` |
| `read_memory_file`   | `read_memory_file_response`   |
| `write_memory_file`  | `write_memory_file_response`  |
| `delete_memory_file` | `delete_memory_file_response` |
| `enable_memfs`       | `enable_memfs_response`       |

Model and provider responses:

| Send command             | Receive event                     |
| ------------------------ | --------------------------------- |
| `list_models`            | `list_models_response`            |
| `update_model`           | `update_model_response`           |
| `list_connect_providers` | `list_connect_providers_response` |
| `connect_provider`       | `connect_provider_response`       |
| `disconnect_provider`    | `disconnect_provider_response`    |

Git, experiments, secrets, and skills responses:

| Send command      | Receive event              |
| ----------------- | -------------------------- |
| `search_branches` | `search_branches_response` |
| `checkout_branch` | `checkout_branch_response` |
| `get_experiments` | `get_experiments_response` |
| `set_experiment`  | `set_experiment_response`  |
| `secret_list`     | `secret_list_response`     |
| `secret_apply`    | `secret_apply_response`    |
| `skill_enable`    | `skill_enable_response`    |
| `skill_disable`   | `skill_disable_response`   |

Channels responses:

| Send command             | Receive event                     |
| ------------------------ | --------------------------------- |
| `channels_list`          | `channels_list_response`          |
| `channel_accounts_list`  | `channel_accounts_list_response`  |
| `channel_account_create` | `channel_account_create_response` |
| `channel_account_update` | `channel_account_update_response` |
| `channel_account_bind`   | `channel_account_bind_response`   |
| `channel_account_unbind` | `channel_account_unbind_response` |
| `channel_account_delete` | `channel_account_delete_response` |
| `channel_account_start`  | `channel_account_start_response`  |
| `channel_account_stop`   | `channel_account_stop_response`   |
| `channel_get_config`     | `channel_get_config_response`     |
| `channel_set_config`     | `channel_set_config_response`     |
| `channel_start`          | `channel_start_response`          |
| `channel_stop`           | `channel_stop_response`           |
| `channel_pairings_list`  | `channel_pairings_list_response`  |
| `channel_pairing_bind`   | `channel_pairing_bind_response`   |
| `channel_routes_list`    | `channel_routes_list_response`    |
| `channel_targets_list`   | `channel_targets_list_response`   |
| `channel_target_bind`    | `channel_target_bind_response`    |
| `channel_route_remove`   | `channel_route_remove_response`   |

Other acknowledgements and non-`_response` replies:

| Send command                  | Receive event                                                                         |
| ----------------------------- | ------------------------------------------------------------------------------------- |
| `ping`                        | `pong`                                                                                |
| `abort_message`               | `cancel_ack`                                                                          |
| `change_cwd`                  | `cwd_changed`                                                                         |
| `recover_pending_approvals`   | `recover_pending_approvals_ack`                                                       |
| `sync`                        | State events such as `update_device_status`, `update_loop_status`, and `update_queue` |
| `input` with `create_message` | Streaming events such as `update_loop_status`, `stream_delta`, and `control_request`  |
| `change_device_state`         | State events such as `update_device_status`                                           |

## Commands

### `ping`

Keep the status WebSocket alive.

**Send**

```
{ "type": "ping" }
```

**Receive**

```
{ "type": "pong", "timestamp": 1780950030000 }
```

### `ack`

Acknowledge any server message that includes `seq`.

**Send**

```
{ "type": "ack", "seq": 42 }
```

**Receive**

No direct response.

### `sync`

Ask the device to replay current state for the runtime.

**Send**

```
{
  "type": "sync",
  "runtime": {
    "agent_id": "agent-...",
    "conversation_id": "conv-..."
  },
  "recover_approvals": true
}
```

**Receive**

- `update_device_status`
- `update_loop_status`
- `update_queue`
- `control_request` or pending approvals, when `recover_approvals` is `true`

### `input` with `create_message`

Send a user message and start a streaming turn.

**Send**

```
{
  "type": "input",
  "runtime": {
    "agent_id": "agent-...",
    "conversation_id": "conv-..."
  },
  "payload": {
    "kind": "create_message",
    "messages": [
      {
        "role": "user",
        "content": "What files should I inspect first?",
        "client_message_id": "cm_01J_demo"
      }
    ],
    "supports_control_response": true
  }
}
```

**Receive**

- `update_loop_status`
- `stream_delta`
- `control_request`
- `update_queue`
- `error` or `run_request_error`

The turn is idle when `update_loop_status.loop_status.status` is `WAITING_ON_INPUT`.

### `input` with `approval_response`

Approve or deny a tool request.

**Send allow**

```
{
  "type": "input",
  "runtime": {
    "agent_id": "agent-...",
    "conversation_id": "conv-..."
  },
  "payload": {
    "kind": "approval_response",
    "request_id": "req-...",
    "decision": {
      "behavior": "allow",
      "message": "Approved"
    }
  }
}
```

**Send deny**

```
{
  "type": "input",
  "runtime": {
    "agent_id": "agent-...",
    "conversation_id": "conv-..."
  },
  "payload": {
    "kind": "approval_response",
    "request_id": "req-...",
    "decision": {
      "behavior": "deny",
      "message": "Do not run this command."
    }
  }
}
```

**Receive**

Streaming resumes with `stream_delta` and `update_loop_status` events.

### `abort_message`

Cancel an active run.

**Send**

```
{
  "type": "abort_message",
  "runtime": {
    "agent_id": "agent-...",
    "conversation_id": "conv-..."
  },
  "request_id": "abort-...",
  "run_id": "run-..."
}
```

**Receive**

```
{
  "type": "cancel_ack",
  "seq": 16,
  "request_id": "abort-...",
  "accepted": true,
  "run_id": "run-..."
}
```

### `list_models`

Fetch models available to the device.

**Send**

```
{
  "type": "list_models",
  "request_id": "models-..."
}
```

**Receive**

```
{
  "type": "list_models_response",
  "seq": 20,
  "request_id": "models-...",
  "success": true,
  "entries": [
    {
      "id": "model-entry-id",
      "handle": "openai/gpt-4o-mini",
      "label": "GPT-4o mini",
      "description": "OpenAI GPT-4o mini",
      "isDefault": true,
      "isFeatured": true,
      "free": false
    }
  ],
  "available_handles": ["openai/gpt-4o-mini"]
}
```

### `update_model`

Set the active model for the runtime.

**Send by handle**

```
{
  "type": "update_model",
  "request_id": "update-model-...",
  "runtime": {
    "agent_id": "agent-...",
    "conversation_id": "conv-..."
  },
  "payload": {
    "model_handle": "openai/gpt-4o-mini"
  }
}
```

**Send by model entry ID**

```
{
  "type": "update_model",
  "request_id": "update-model-...",
  "runtime": {
    "agent_id": "agent-...",
    "conversation_id": "conv-..."
  },
  "payload": {
    "model_id": "model-entry-id"
  }
}
```

**Receive**

```
{
  "type": "update_model_response",
  "seq": 21,
  "request_id": "update-model-...",
  "success": true,
  "runtime": {
    "agent_id": "agent-...",
    "conversation_id": "conv-..."
  },
  "applied_to": "conversation",
  "model_handle": "openai/gpt-4o-mini",
  "model_settings": {
    "max_tokens": 4096
  }
}
```

### `change_device_state`

Change runtime/device state.

**Send permission mode**

```
{
  "type": "change_device_state",
  "runtime": {
    "agent_id": "agent-...",
    "conversation_id": "conv-..."
  },
  "payload": {
    "mode": "acceptEdits"
  }
}
```

**Send current working directory**

```
{
  "type": "change_device_state",
  "runtime": {
    "agent_id": "agent-...",
    "conversation_id": "conv-..."
  },
  "payload": {
    "cwd": "/workspace/project"
  }
}
```

**Send git operation**

```
{
  "type": "change_device_state",
  "runtime": {
    "agent_id": "agent-...",
    "conversation_id": "conv-..."
  },
  "payload": {
    "git_op": {
      "kind": "checkout",
      "branch": "feature/foo"
    }
  }
}
```

**Receive**

- `update_device_status`
- `cwd_changed` for some legacy CWD flows

### `search_branches`

Search local git branches on the device.

**Send**

```
{
  "type": "search_branches",
  "request_id": "branches-...",
  "query": "feature"
}
```

**Receive**

```
{
  "type": "search_branches_response",
  "request_id": "branches-...",
  "success": true,
  "branches": ["feature/foo"]
}
```

### `checkout_branch`

Checkout or create a branch on the device.

**Send checkout**

```
{
  "type": "checkout_branch",
  "request_id": "checkout-...",
  "branch": "feature/foo"
}
```

**Send create and checkout**

```
{
  "type": "checkout_branch",
  "request_id": "checkout-...",
  "branch": "feature/new-branch",
  "create": true
}
```

**Receive**

```
{
  "type": "checkout_branch_response",
  "request_id": "checkout-...",
  "success": true,
  "branch": "feature/foo"
}
```

### File commands

Use these for direct filesystem operations on the device.

| Command             | Response                     |
| ------------------- | ---------------------------- |
| `list_in_directory` | `list_in_directory_response` |
| `read_file`         | `read_file_response`         |
| `write_file`        | `write_file_response`        |
| `edit_file`         | `edit_file_response`         |

Each request should include `request_id`. Match the response by the same `request_id`.

### Terminal commands

Use these for PTY control on the device.

| Command           | Purpose                            |
| ----------------- | ---------------------------------- |
| `terminal_spawn`  | Create a terminal session.         |
| `terminal_input`  | Write input to a terminal session. |
| `terminal_resize` | Resize a terminal session.         |
| `terminal_kill`   | Stop a terminal session.           |

## Events

### `update_device_status`

Device state snapshot.

```
{
  "type": "update_device_status",
  "seq": 1,
  "runtime": {
    "agent_id": "agent-...",
    "conversation_id": "conv-..."
  },
  "event_seq": 10,
  "idempotency_key": "device-event-10",
  "device_status": {
    "is_online": true,
    "is_processing": false,
    "current_permission_mode": "standard",
    "current_working_directory": "/workspace/project",
    "git_context": {
      "branch": "main",
      "recent_branches": ["feature/foo", "fix/bar"]
    },
    "memory_directory": "~/.letta/agents/.../memory",
    "letta_code_version": "0.27.7",
    "supported_commands": ["input", "sync", "change_device_state"]
  }
}
```

### `update_loop_status`

Execution lifecycle state.

```
{
  "type": "update_loop_status",
  "runtime": {
    "agent_id": "agent-...",
    "conversation_id": "conv-..."
  },
  "event_seq": 11,
  "idempotency_key": "device-event-11",
  "loop_status": {
    "status": "PROCESSING_API_RESPONSE",
    "active_run_ids": ["run-..."]
  }
}
```

Common statuses:

- `SENDING_API_REQUEST`
- `WAITING_FOR_API_RESPONSE`
- `RETRYING_API_REQUEST`
- `PROCESSING_API_RESPONSE`
- `EXECUTING_CLIENT_SIDE_TOOL`
- `EXECUTING_COMMAND`
- `WAITING_ON_APPROVAL`
- `WAITING_ON_INPUT`

### `stream_delta`

Incremental assistant/tool/reasoning content.

```
{
  "type": "stream_delta",
  "runtime": {
    "agent_id": "agent-...",
    "conversation_id": "conv-..."
  },
  "event_seq": 12,
  "idempotency_key": "device-event-12",
  "delta": {
    "message_type": "assistant_message",
    "content": "The WebSocket protocol lives in ..."
  }
}
```

### `control_request`

Tool approval request.

```
{
  "type": "control_request",
  "seq": 14,
  "request_id": "req-...",
  "request": {
    "subtype": "can_use_tool",
    "tool_name": "Bash",
    "tool_call_id": "toolu_...",
    "input": {
      "cmd": "npm test"
    }
  }
}
```

### Other events

Status/control events:

| Event               | Meaning                                        |
| ------------------- | ---------------------------------------------- |
| `pong`              | Heartbeat response.                            |
| `cancel_ack`        | Abort acknowledgement.                         |
| `cwd_changed`       | Legacy CWD change acknowledgement.             |
| `error`             | Generic runtime or routing error.              |
| `run_request_error` | Error starting or running a turn.              |
| `status`            | Legacy online/mode/heartbeat status frame.     |
| `state_response`    | Legacy full state snapshot.                    |
| `run_started`       | Legacy run started event.                      |
| `run_completed`     | Legacy run completed event.                    |
| `mode_changed`      | Legacy permission-mode change acknowledgement. |
| `message`           | Legacy raw message frame.                      |

Queue events:

| Event                  | Meaning                                   |
| ---------------------- | ----------------------------------------- |
| `update_queue`         | Protocol V2 queue state snapshot.         |
| `queue_item_enqueued`  | Legacy queue item added.                  |
| `queue_batch_dequeued` | Legacy queue batch removed for execution. |
| `queue_blocked`        | Legacy queue blocked event.               |
| `queue_cleared`        | Legacy queue cleared event.               |
| `queue_item_dropped`   | Legacy queue item dropped event.          |

Filesystem events and responses:

| Event                        | Meaning                            |
| ---------------------------- | ---------------------------------- |
| `list_in_directory_response` | Response to `list_in_directory`.   |
| `get_tree_response`          | Response to `get_tree`.            |
| `read_file_response`         | Response to `read_file`.           |
| `write_file_response`        | Response to `write_file`.          |
| `edit_file_response`         | Response to `edit_file`.           |
| `file_ops`                   | File operation patch/update event. |
| `file_changed`               | Watched file changed event.        |
| `search_files_response`      | Response to `search_files`.        |
| `grep_in_files_response`     | Response to `grep_in_files`.       |

Memory and MemFS events and responses:

| Event                         | Meaning                           |
| ----------------------------- | --------------------------------- |
| `list_memory_response`        | Response to `list_memory`.        |
| `memory_history_response`     | Response to `memory_history`.     |
| `memory_file_at_ref_response` | Response to `memory_file_at_ref`. |
| `memory_commit_diff_response` | Response to `memory_commit_diff`. |
| `read_memory_file_response`   | Response to `read_memory_file`.   |
| `write_memory_file_response`  | Response to `write_memory_file`.  |
| `delete_memory_file_response` | Response to `delete_memory_file`. |
| `memory_updated`              | Memory files changed on disk.     |
| `enable_memfs_response`       | Response to `enable_memfs`.       |

Model and provider responses:

| Event                             | Meaning                               |
| --------------------------------- | ------------------------------------- |
| `list_models_response`            | Response to `list_models`.            |
| `update_model_response`           | Response to `update_model`.           |
| `list_connect_providers_response` | Response to `list_connect_providers`. |
| `connect_provider_response`       | Response to `connect_provider`.       |
| `disconnect_provider_response`    | Response to `disconnect_provider`.    |

Terminal events:

| Event              | Meaning                   |
| ------------------ | ------------------------- |
| `terminal_spawned` | Terminal session created. |
| `terminal_output`  | Terminal output bytes.    |
| `terminal_exited`  | Terminal session exited.  |

Git/search/experiment/secrets responses:

| Event                      | Meaning                        |
| -------------------------- | ------------------------------ |
| `search_branches_response` | Response to `search_branches`. |
| `checkout_branch_response` | Response to `checkout_branch`. |
| `get_experiments_response` | Response to `get_experiments`. |
| `set_experiment_response`  | Response to `set_experiment`.  |
| `secret_list_response`     | Response to `secret_list`.     |
| `secret_apply_response`    | Response to `secret_apply`.    |

Skill and legacy approval responses:

| Event                           | Meaning                                          |
| ------------------------------- | ------------------------------------------------ |
| `skill_enable_response`         | Response to `skill_enable`.                      |
| `skill_disable_response`        | Response to `skill_disable`.                     |
| `recover_pending_approvals_ack` | Acknowledgement for `recover_pending_approvals`. |

Channels responses and push events:

| Event                             | Meaning                               |
| --------------------------------- | ------------------------------------- |
| `channels_list_response`          | Response to `channels_list`.          |
| `channel_accounts_list_response`  | Response to `channel_accounts_list`.  |
| `channel_account_create_response` | Response to `channel_account_create`. |
| `channel_account_update_response` | Response to `channel_account_update`. |
| `channel_account_bind_response`   | Response to `channel_account_bind`.   |
| `channel_account_unbind_response` | Response to `channel_account_unbind`. |
| `channel_account_delete_response` | Response to `channel_account_delete`. |
| `channel_account_start_response`  | Response to `channel_account_start`.  |
| `channel_account_stop_response`   | Response to `channel_account_stop`.   |
| `channel_get_config_response`     | Response to `channel_get_config`.     |
| `channel_set_config_response`     | Response to `channel_set_config`.     |
| `channel_start_response`          | Response to `channel_start`.          |
| `channel_stop_response`           | Response to `channel_stop`.           |
| `channel_pairings_list_response`  | Response to `channel_pairings_list`.  |
| `channel_pairing_bind_response`   | Response to `channel_pairing_bind`.   |
| `channel_routes_list_response`    | Response to `channel_routes_list`.    |
| `channel_targets_list_response`   | Response to `channel_targets_list`.   |
| `channel_target_bind_response`    | Response to `channel_target_bind`.    |
| `channel_route_remove_response`   | Response to `channel_route_remove`.   |
| `channels_updated`                | Channels changed.                     |
| `channel_accounts_updated`        | Channel accounts changed.             |
| `channel_pairings_updated`        | Channel pairings changed.             |
| `channel_routes_updated`          | Channel routes changed.               |
| `channel_targets_updated`         | Channel targets changed.              |

## Reliability

- Send `ping` every 30 seconds.
- Send `ack` for every message with numeric `seq`.
- Track `event_seq`; send `sync` if you detect a gap.
- Deduplicate events with `idempotency_key` when present.
- Send `sync` with `recover_approvals: true` after initial attach or reconnect.
- Treat `WAITING_ON_INPUT` as idle.
