---
title: Remote client API reference | Letta Docs
description: Remote environment discovery, authentication, and hosted WebSocket transport details for custom Letta Code clients.
---

Use this reference after the [Remote client API overview](/letta-agent/remote-websocket-api/index.md).

The Remote client API uses the same v2 command and event vocabulary as [App Server](/letta-agent/app-server/index.md), but over a hosted remote-environment transport:

- App Server is the direct local control plane for a `letta app-server` process.
- Remote client API is the hosted or self-hosted transport for connecting to a Letta Code environment that is already online as a remote device.

For shared command lifecycle details like `input`, `sync`, `abort_message`, approval responses, `stream_delta`, `update_loop_status`, and device capability commands, use the [App Server protocol lifecycle](/letta-agent/app-server/protocol-lifecycle/index.md) as the canonical reference. This page only documents the remote-specific pieces.

## Remote-specific flow

Most hosted remote clients follow this sequence:

1. List online environments and choose a `connectionId`.
2. Open the hosted status WebSocket for that connection.
3. Send v2 frames such as `input`, `sync`, and `abort_message` over the socket.
4. ACK sequenced transport messages.
5. Reconnect and call `sync` if the WebSocket drops.

Unlike App Server clients, remote clients do not send `runtime_start`. The remote environment is already registered; the client scopes work with `agentId` and `conversationId` in the WebSocket URL and with the `runtime` object on scoped commands.

## Authentication

REST requests use the normal Letta API bearer token:

```
Authorization: Bearer $LETTA_API_KEY
```

For WebSockets, use an `Authorization` header when your WebSocket implementation supports headers:

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

Browser clients cannot set WebSocket headers. For browser clients, pass the token in the URL:

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

Only use URL tokens over TLS and avoid logging full WebSocket URLs.

## REST endpoints

### List environments

Use this endpoint 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 hosted status WebSocket.

## WebSocket endpoint

Connect to the hosted 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}
```

| Parameter        | Meaning                                                             |
| ---------------- | ------------------------------------------------------------------- |
| `connectionId`   | Online remote environment connection returned by the REST endpoint. |
| `agentId`        | Persistent Letta agent ID to control.                               |
| `conversationId` | Conversation/thread ID for the runtime scope.                       |
| `channel`        | Hosted status stream channel. Use `stream`.                         |
| `token`          | Browser-only fallback when headers are unavailable.                 |

## Frame shape

Every WebSocket frame is a JSON object with a `type` field.

```
{
  "type": "input",
  "runtime": {
    "agent_id": "agent-...",
    "conversation_id": "conv-..."
  },
  "payload": {
    "kind": "create_message",
    "messages": [
      {
        "role": "user",
        "content": "Inspect this repository and summarize the risks."
      }
    ]
  }
}
```

| 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                  | Hosted 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.                                                         |

## Transport ACKs

Hosted remote messages that include `seq` should be acknowledged:

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

ACKs are transport-level bookkeeping and do not have a direct response. If a client reconnects after missing messages, call `sync` for an authoritative state replay.

## Shared command lifecycle

Use the App Server protocol lifecycle for the shared v2 command and event model:

| Task                                                                   | Canonical docs                                                                                                             |
| ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| Send a user turn                                                       | [`input` with `payload.kind: "create_message"`](/letta-agent/app-server/protocol-lifecycle#sending-turns/index.md)         |
| Respond to approvals                                                   | [`input` with `payload.kind: "approval_response"`](/letta-agent/app-server/protocol-lifecycle#approval-responses/index.md) |
| Replay state after reconnects                                          | [`sync`](/letta-agent/app-server/protocol-lifecycle#sync/index.md)                                                         |
| Abort active work                                                      | [`abort_message`](/letta-agent/app-server/protocol-lifecycle#abort/index.md)                                               |
| Handle streamed output and runtime state                               | [Streaming and completion](/letta-agent/app-server/protocol-lifecycle#streaming-and-completion/index.md)                   |
| Use filesystem, memory, model, terminal, schedule, or channel commands | [Device capability commands](/letta-agent/app-server/protocol-lifecycle#device-capability-commands/index.md)               |

Remote clients should preserve unknown fields and tolerate new event types. The protocol is a discriminated-union stream, not JSON-RPC.

## Remote-specific replies

Some hosted remote replies are transport-specific:

| Send command    | Receive event                  | Notes                                                                                                     |
| --------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------- |
| `ping`          | `pong`                         | Keepalive for the hosted WebSocket.                                                                       |
| `ack`           | No direct response             | Acknowledges a hosted transport `seq`.                                                                    |
| `sync`          | State events                   | Replay arrives as normal events such as `update_device_status`, `update_loop_status`, and `update_queue`. |
| `abort_message` | `cancel_ack` or abort response | Match by `request_id` and treat the runtime as authoritative after the next status update.                |

## Reliability

- ACK every hosted transport message that includes `seq`.
- Track `event_seq` to detect gaps in device events.
- Use `idempotency_key` to deduplicate replayed events.
- After reconnecting, send `sync` with the current `runtime`.
- Treat `stream_delta.delta.message_type: "stop_reason"` as normal turn completion.
- Treat `loop_error`, `error_message`, and `run_request_error` as failures.
- Log unknown event types instead of crashing; the protocol can add events over time.

## Related pages

- [Remote client API overview](/letta-agent/remote-websocket-api/index.md) - Remote concepts and a minimal client.
- [Self-hosted remotes](/letta-agent/remote-client-byor/index.md) - Connect directly without the hosted environment router.
- [App Server protocol lifecycle](/letta-agent/app-server/protocol-lifecycle/index.md) - Canonical shared command/event lifecycle.
