# Letta REST API vs Letta Code SDK: 8 Key Differences in the Claude-Subconscious Plugin

> Compare Letta REST API vs SDK for Claude-Subconscious plugin. Discover differences in manual HTTP handling versus high-level session abstraction with real-time streaming and automatic auth.

- Repository: [Letta/claude-subconscious](https://github.com/letta-ai/claude-subconscious)
- Tags: comparison
- Published: 2026-03-26

---

**The Letta REST API requires manual HTTP handling and polling for responses, while the Letta Code SDK provides a high-level session abstraction with real-time streaming, automatic authentication, and configurable client-side tool access.**

The `letta-ai/claude-subconscious` repository implements dual integration patterns for connecting Claude Code with Letta agents. When deciding between the **Letta REST API** and **Letta Code SDK**, developers must consider differences in abstraction level, streaming capabilities, and tool permissions that fundamentally change how the plugin synchronizes conversation state.

## Architectural Overview: Low-Level Control vs. Session Abstraction

### Raw HTTP Implementation with the REST API

The REST API approach implements direct HTTP communication using Node’s native `fetch` to interact with Letta endpoints such as `/conversations/` and `/agents/`. In [`scripts/conversation_utils.ts`](https://github.com/letta-ai/claude-subconscious/blob/main/scripts/conversation_utils.ts), the `createConversation()` function constructs URLs via `buildLettaApiUrl()` and manually assembles request headers including the `Authorization: Bearer <API-KEY>` credential.

This method requires the plugin to handle XML payload formatting, JSON response parsing, and HTTP error status codes without abstraction layers. The [`send_messages_to_letta.ts`](https://github.com/letta-ai/claude-subconscious/blob/main/send_messages_to_letta.ts) script writes temporary JSON files to disk and tracks message indices manually when preparing the `<claude_code_session_update>` payload for transmission.

### High-Level Session Management with the Code SDK

The Code SDK approach leverages the optional `@letta-ai/letta-code-sdk` package to create a persistent session object. In [`scripts/send_worker_sdk.ts`](https://github.com/letta-ai/claude-subconscious/blob/main/scripts/send_worker_sdk.ts), the worker dynamically imports the SDK and invokes `resumeSession()` with a conversation ID and configuration options. The SDK encapsulates authentication by reading the `LETTA_API_KEY` environment variable internally, eliminating manual header management.

The session object exposes `session.send()` for transmitting messages and `session.stream()` for receiving real-time assistant responses, abstracting the underlying HTTP transport and payload serialization.

## Key Implementation Differences

### Authentication Patterns

**REST API:** Each request must explicitly include the bearer token in the request headers. The `createConversation()` function in [`scripts/conversation_utils.ts`](https://github.com/letta-ai/claude-subconscious/blob/main/scripts/conversation_utils.ts) (lines 20-28) demonstrates this pattern by passing the `apiKey` parameter into the `Authorization` header for every fetch call.

**Code SDK:** Authentication is transparent. The SDK reads `LETTA_API_KEY` from environment variables during session initialization, as implemented in [`scripts/send_worker_sdk.ts`](https://github.com/letta-ai/claude-subconscious/blob/main/scripts/send_worker_sdk.ts) (lines 40-46). Callers do not manage headers or credential injection.

### Message Transport and Payload Handling

**REST API:** The plugin bears full responsibility for payload construction. In [`send_messages_to_letta.ts`](https://github.com/letta-ai/claude-subconscious/blob/main/send_messages_to_letta.ts), the code manually formats XML envelopes containing the transcript updates, writes these to temporary files, and later parses the conversation history JSON to extract responses.

**Code SDK:** Message formatting happens automatically. The worker passes the raw XML string to `session.send()`, which handles serialization and transmission. The SDK manages conversation state and memory synchronization without requiring temporary file intermediates.

### Response Retrieval: Polling vs. Streaming

**REST API:** Responses are retrieved via periodic polling. The [`pretool_sync.ts`](https://github.com/letta-ai/claude-subconscious/blob/main/pretool_sync.ts) and [`sync_letta_memory.ts`](https://github.com/letta-ai/claude-subconscious/blob/main/sync_letta_memory.ts) scripts implement `fetchNewMessages()` to query the conversation endpoint later, checking for new assistant messages to inject into [`CLAUDE.md`](https://github.com/letta-ai/claude-subconscious/blob/main/CLAUDE.md). This creates latency between message generation and availability.

**Code SDK:** Responses stream in real-time. The [`send_worker_sdk.ts`](https://github.com/letta-ai/claude-subconscious/blob/main/send_worker_sdk.ts) implementation uses `for await (const msg of session.stream())` (lines 73-93) to process assistant chunks as they arrive, enabling immediate feedback without polling loops.

## Tool Access and Capability Differences

### Client-Side Tool Permissions

The REST API integration cannot access Letta’s client-side tools such as `Read`, `Grep`, `Glob`, or `web_search`. The plugin is restricted to text-only send/receive operations when using HTTP endpoints directly.

The Code SDK exposes configurable tool permissions through the `LETTA_SDK_TOOLS` environment variable. In [`scripts/send_worker_sdk.ts`](https://github.com/letta-ai/claude-subconscious/blob/main/scripts/send_worker_sdk.ts) (lines 69-87), the worker configures `allowedTools` and `disallowedTools` arrays based on the requested mode:

- **`read-only`**: Enables `Read`, `Grep`, `Glob`, `web_search`, and `fetch_webpage`
- **`full`**: Allows all client-side capabilities including `Bash`, `Edit`, and `Write`
- **`off`**: Disables all tools for text-only interaction

### Dependency and Import Strategy

**REST API:** Zero external dependencies required. The implementation relies solely on Node.js built-in `fetch`, making it suitable for lightweight deployments where minimizing package size is critical.

**Code SDK:** Requires the optional `@letta-ai/letta-code-sdk` npm package. The plugin safeguards against missing dependencies by using dynamic imports in [`send_worker_sdk.ts`](https://github.com/letta-ai/claude-subconscious/blob/main/send_worker_sdk.ts), allowing the plugin to run in REST-only mode if the SDK is not installed.

## Code Examples: REST API vs. SDK

### Creating Conversations via REST

The following implementation from [`scripts/conversation_utils.ts`](https://github.com/letta-ai/claude-subconscious/blob/main/scripts/conversation_utils.ts) demonstrates direct API communication for conversation initialization:

```typescript
import { buildLettaApiUrl } from './letta_api_url.js';

export async function createConversation(
  apiKey: string,
  agentId: string,
  log: LogFn = noopLog,
): Promise<string> {
  const url = buildLettaApiUrl('/conversations/', { agent_id: agentId });

  log(`Creating new conversation for agent ${agentId}`);

  const response = await fetch(url, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${apiKey}`,
      'Content-Type': 'application/json',
    },
  });

  if (!response.ok) {
    const err = await response.text();
    throw new Error(`Failed to create conversation: ${response.status} ${err}`);
  }

  const conversation: Conversation = await response.json();
  log(`Created conversation: ${conversation.id}`);
  return conversation.id;
}

```

This function corresponds to lines 20-28 in the source file and represents the typical pattern for REST-based agent configuration in [`agent_config.ts`](https://github.com/letta-ai/claude-subconscious/blob/main/agent_config.ts).

### Streaming Messages via SDK

The background worker in [`scripts/send_worker_sdk.ts`](https://github.com/letta-ai/claude-subconscious/blob/main/scripts/send_worker_sdk.ts) implements the SDK approach for live transcript updates:

```typescript
import { resumeSession } from '@letta-ai/letta-code-sdk';

async function sendViaSdk(payload: SdkPayload): Promise<boolean> {
  const sessionOptions: Record<string, unknown> = {
    disallowedTools: ['AskUserQuestion', 'EnterPlanMode', 'ExitPlanMode'],
    permissionMode: 'bypassPermissions',
    cwd: payload.cwd,
    skillSources: [],
    systemInfoReminder: false,
    sleeptime: { trigger: 'off' },
  };

  if (payload.sdkToolsMode === 'read-only') {
    sessionOptions.allowedTools = ['Read', 'Grep', 'Glob', 'web_search', 'fetch_webpage'];
  } else if (payload.sdkToolsMode === 'off') {
    sessionOptions.disallowedTools = [
      ...sessionOptions.disallowedTools,
      'Read',
      'Grep',
      'Glob',
      'Bash',
      'Edit',
      'Write',
      'Task',
    ];
  }

  const session = resumeSession(payload.conversationId, sessionOptions);

  try {
    await session.send(payload.message);

    for await (const msg of session.stream()) {
      if (msg.type === 'assistant') {
        // Process streaming assistant chunks
      } else if (msg.type === 'error') {
        // Handle SDK error objects
        return false;
      }
    }
    return true;
  } finally {
    session.close();
  }
}

```

This implementation (lines 40-78) shows how the SDK handles authentication, tool configuration, and real-time streaming without manual HTTP intervention.

## When to Use Each Integration Method

**Choose the REST API when:**
- Implementing background synchronization tasks like [`sync_letta_memory.ts`](https://github.com/letta-ai/claude-subconscious/blob/main/sync_letta_memory.ts) or [`pretool_sync.ts`](https://github.com/letta-ai/claude-subconscious/blob/main/pretool_sync.ts)
- Minimizing external dependencies is required
- Performing administrative operations such as creating conversations or updating agent configurations via [`agent_config.ts`](https://github.com/letta-ai/claude-subconscious/blob/main/agent_config.ts)

**Choose the Code SDK when:**
- Sending live transcript updates from [`send_messages_to_letta.ts`](https://github.com/letta-ai/claude-subconscious/blob/main/send_messages_to_letta.ts) to the Letta agent
- Requiring real-time streaming responses instead of polling
- Enabling client-side tool access (Read, Grep, Bash) for the agent
- Simplifying authentication management in worker threads

## Summary

- **Communication Style:** The REST API uses manual HTTP requests with `fetch`, while the SDK provides a `resumeSession()` abstraction that hides network complexity.
- **Authentication:** REST requires explicit `Authorization` headers per request; the SDK reads `LETTA_API_KEY` automatically from environment variables.
- **Response Handling:** REST relies on periodic polling via `fetchNewMessages()` in sync scripts; the SDK streams responses in real-time through `session.stream()`.
- **Tool Access:** REST supports text-only interaction; the SDK enables configurable client-side tools via `LETTA_SDK_TOOLS` environment settings.
- **Dependencies:** REST requires no external packages; the SDK uses optional dynamic imports of `@letta-ai/letta-code-sdk`.
- **Use Cases:** REST is preferred for memory synchronization and conversation management; the SDK is optimized for live messaging with tool capabilities.

## Frequently Asked Questions

### Can I use both the REST API and Code SDK in the same plugin instance?

Yes. The `letta-ai/claude-subconscious` plugin employs both methods simultaneously according to their strengths. The REST API handles background synchronization tasks in [`sync_letta_memory.ts`](https://github.com/letta-ai/claude-subconscious/blob/main/sync_letta_memory.ts) and [`pretool_sync.ts`](https://github.com/letta-ai/claude-subconscious/blob/main/pretool_sync.ts), while the Code SDK manages live transcript streaming via [`send_worker_sdk.ts`](https://github.com/letta-ai/claude-subconscious/blob/main/send_worker_sdk.ts). The dynamic import strategy ensures the plugin functions even if the SDK package is not installed.

### Do I need to install the Letta Code SDK to use the plugin?

No. The `@letta-ai/letta-code-sdk` package is an optional dependency. The plugin defaults to REST API functionality for all operations if the SDK is absent. The [`send_worker_sdk.ts`](https://github.com/letta-ai/claude-subconscious/blob/main/send_worker_sdk.ts) script uses dynamic imports to load the SDK only when available, falling back to REST-based alternatives or graceful degradation.

### Which integration method supports real-time streaming of agent responses?

Only the **Letta Code SDK** supports real-time streaming. The `session.stream()` method in [`scripts/send_worker_sdk.ts`](https://github.com/letta-ai/claude-subconscious/blob/main/scripts/send_worker_sdk.ts) (lines 73-93) yields assistant messages as they are generated. The REST API requires polling mechanisms like `fetchNewMessages()` to check for new content, introducing delays between message creation and retrieval.

### How do I enable client-side tools like Read and Grep for the Letta agent?

Client-side tools require the **Code SDK** and the `LETTA_SDK_TOOLS` environment variable. Set this variable to `read-only` to enable safe tools (`Read`, `Grep`, `Glob`, `web_search`), `full` to allow all capabilities including `Bash` and `Edit`, or `off` to disable tools entirely. The REST API cannot access these client-side capabilities.