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

# SDK Direct

> Embed the Decart SDK in your app for the simplest integration path

Your application embeds the Decart SDK directly. End users connect to Decart for both signaling (WebSocket) and media (WebRTC). You handle authentication and billing on your side.

This is the simplest integration path — no proxy infrastructure, no protocol translation, full media quality.

## When to use this path

* You want the fastest integration with zero infrastructure overhead
  It's OK for end users to connect to Decart endpoints directly (visible in network traffic)
* You're building a wrapper app, marketplace listing, or plugin that surfaces Decart models

<Tip>
  If you already use Decart's batch models (video editing, image editing), SDK Direct for realtime works the same way — same API key, same SDK, same billing.
</Tip>

## Characteristics

| Property            | Value                                                      |
| ------------------- | ---------------------------------------------------------- |
| White-label         | No — end users see Decart endpoints and SDK                |
| Frame access        | No                                                         |
| Provider visibility | Session events only (`generationTick`, `connectionChange`) |
| Client requirements | Browser or native app with WebRTC + Decart SDK             |
| Your infrastructure | Backend for token minting only                             |

<Check>
  Media quality is identical to using Decart directly. There is no degradation — the SDK connects straight to Decart.
</Check>

## Architecture

```mermaid theme={null}
sequenceDiagram
    participant B as Your Backend
    participant D as Decart
    participant F as Your Frontend<br/>(Decart SDK)

    B->>D: POST /v1/client/tokens (your API key)
    D-->>B: { apiKey: "ek_...", expiresAt }
    B->>F: Pass client token

    F->>D: WS connect (signaling)
    F->>D: WebRTC media (P2P)
    D->>F: WebRTC media (P2P)

    Note over F,D: Media flows p2p over WebRTC
```

## How it works

<Steps>
  <Step title="Create a client token on your backend">
    Use your platform API key to mint a short-lived client token. This keeps your permanent key server-side.

    ```typescript theme={null}
    // Your backend
    const client = createDecartClient({ apiKey: process.env.DECART_API_KEY });
    const token = await client.tokens.create();
    // Return token.apiKey to your frontend
    ```
  </Step>

  <Step title="Connect from the frontend">
    Your frontend uses the client token to connect. The SDK handles WebSocket signaling, WebRTC negotiation, ICE candidates, and auto-reconnection.

    ```typescript theme={null}
    import { createDecartClient, models } from "@decartai/sdk";

    const model = models.realtime("lucy-2.1");
    const stream = await navigator.mediaDevices.getUserMedia({
      video: { frameRate: model.fps, width: model.width, height: model.height },
    });

    const client = createDecartClient({ apiKey: tokenFromBackend });

    const rt = await client.realtime.connect(stream, {
      model,
      onRemoteStream: (remoteStream) => {
        videoElement.srcObject = remoteStream;
      },
    });

    await rt.set({ prompt: "Anime style", enhance: true });
    ```
  </Step>

  <Step title="Control the generation">
    Use `set()` to replace the session state at any time. Include all fields you want to keep — omitted fields are cleared:

    ```typescript theme={null}
    // Set a new prompt (clears any previous image)
    await rt.set({ prompt: "Cyberpunk neon city" });

    // Set a reference image with a prompt
    await rt.set({
      prompt: "Transform into this character",
      image: selectedFile,  // File, Blob, base64 string, or null to clear
    });

    // Disable prompt enhancement for exact control
    await rt.set({ prompt: "Exact prompt text", enhance: false });
    ```
  </Step>

  <Step title="Listen to events">
    Track session state and usage with event listeners:

    ```typescript theme={null}
    rt.on("connectionChange", (state) => {
      // "connecting" | "connected" | "generating" | "disconnected" | "reconnecting"
      updateStatusIndicator(state);
    });

    rt.on("generationTick", ({ seconds }) => {
      // Total elapsed generation time (cumulative, not a delta)
      updateUsageDisplay(seconds);
    });

    rt.on("error", (error) => {
      // DecartSDKError with message and context
      console.error("Decart error:", error.message);
    });
    ```
  </Step>

  <Step title="Disconnect when done">
    Clean up the session when the user leaves. This closes the WebSocket and WebRTC connections.

    ```typescript theme={null}
    rt.disconnect();
    ```
  </Step>
</Steps>

## API reference

### `connect(stream, options)`

Creates a new realtime session and establishes the WebRTC connection.

```typescript theme={null}
const rt = await client.realtime.connect(stream, {
  model: models.realtime("lucy-2.1"),
  onRemoteStream: (stream) => { videoElement.srcObject = stream; },
  initialState: {
    prompt: { text: "Anime style", enhance: true },
    image: selectedFile,
  },
});
```

<ParamField body="model" type="ModelDefinition" required>
  The realtime model — use `models.realtime("model_id")`.
</ParamField>

<ParamField body="onRemoteStream" type="(stream: MediaStream) => void" required>
  Called when the remote video/audio stream is ready.
</ParamField>

<ParamField body="initialState" type="object">
  Pre-configure a prompt and/or image before the first frame.
</ParamField>

<ParamField body="customizeOffer" type="(offer: RTCSessionDescriptionInit) => Promise<void>">
  Modify the SDP offer before sending (advanced).
</ParamField>

The `initialState` option lets you set a prompt and image in the same round-trip as session creation — the model starts generating with your configuration from the first frame.

### `set(input)`

Replace the session state with a new prompt, reference image, or both. Fields not included are cleared. At least one of `prompt` or `image` is required. Awaits server acknowledgment — throws if rejected by moderation or on timeout.

```typescript theme={null}
await rt.set({ prompt: "Anime style", enhance: true, image: file });
```

<ParamField body="prompt" type="string">
  The text prompt to apply.
</ParamField>

<ParamField body="enhance" type="boolean" default="true">
  Enhance the prompt automatically. Set to `false` for exact prompt text.
</ParamField>

<ParamField body="image" type="Blob | File | string | null">
  Reference image. Pass `null` to clear the current image. Strings are treated as base64.
</ParamField>

### `setPrompt(prompt, options?)`

Set the prompt and wait for server acknowledgment. Throws if the prompt is rejected by moderation or the request times out (15 seconds).

```typescript theme={null}
try {
  await rt.setPrompt("Anime style", { enhance: true });
} catch (error) {
  // Prompt was rejected by moderation or timed out
  console.error(error.message);
}
```

<ParamField body="enhance" type="boolean" default="true">
  Enhance the prompt automatically.
</ParamField>

### `setImage(image, options?)`

Set a reference image and wait for server acknowledgment. Throws if the image is rejected by moderation or the request times out.

```typescript theme={null}
try {
  await rt.setImage(selectedFile, {
    prompt: "Transform into this character",
    enhance: true,
  });
} catch (error) {
  // Image was rejected by moderation or timed out
  console.error(error.message);
}
```

<ParamField body="prompt" type="string">
  Set a prompt alongside the image.
</ParamField>

<ParamField body="enhance" type="boolean" default="true">
  Enhance the prompt automatically.
</ParamField>

<ParamField body="timeout" type="number" default="15000">
  Timeout in milliseconds.
</ParamField>

<Warning>
  Reference images must be under 10 MB. The SDK rejects oversized images before sending them.
</Warning>

### `disconnect()`

End the session and clean up all WebRTC and WebSocket connections.

```typescript theme={null}
rt.disconnect();
```

### `isConnected()`

Returns `true` if the SDK has an active connection.

```typescript theme={null}
if (rt.isConnected()) {
  await rt.set({ prompt: "New prompt" });
}
```

### `getConnectionState()`

Returns the current connection state.

```typescript theme={null}
const state = rt.getConnectionState();
// "connecting" | "connected" | "generating" | "disconnected" | "reconnecting"
```

### `sessionId`

The current session identifier, or `null` if not connected.

```typescript theme={null}
console.log("Session:", rt.sessionId);
```

### Events

Listen to events with `rt.on(event, listener)` and remove listeners with `rt.off(event, listener)`.

| Event              | Payload               | Description                                                                                                  |
| ------------------ | --------------------- | ------------------------------------------------------------------------------------------------------------ |
| `connectionChange` | `ConnectionState`     | Connection state changed — `"connecting"`, `"connected"`, `"generating"`, `"disconnected"`, `"reconnecting"` |
| `generationTick`   | `{ seconds: number }` | Total elapsed generation time (cumulative). Use for billing.                                                 |
| `error`            | `DecartSDKError`      | An error occurred. The error has a `message` property with details.                                          |

<Note>
  The SDK automatically handles ICE restarts and WebRTC reconnection. You don't need to manage the WebRTC lifecycle — just listen to `connectionChange` to update your UI.
</Note>

## Error handling

The SDK surfaces errors through the `error` event and by throwing from async methods like `set()`, `setPrompt()`, and `setImage()`.

```typescript theme={null}
// Global error listener
rt.on("error", (error) => {
  console.error("Decart error:", error.message);

  if (!rt.isConnected()) {
    // Connection lost — show reconnecting UI or clean up
    showReconnectingState();
  }
});

// Per-call error handling
try {
  await rt.setPrompt("Transform into anime character");
} catch (error) {
  // Moderation rejection or timeout
  showNotification("Prompt not accepted. Try a different description.");
}
```

Common error scenarios:

* **Moderation rejection** — prompt or image violates content policy. Caught by `set()`, `setPrompt()`, and `setImage()` (all throw on rejection).
* **Connection lost** — network issue or server disconnect. The SDK attempts to reconnect automatically. Listen to `connectionChange` for state transitions.
* **Invalid API key** — the session fails to connect. Caught during `connect()`.
* **Insufficient credits** — the session ends with a `connectionChange` to `"disconnected"`.

## Next steps

<CardGroup cols={2}>
  <Card title="Client Tokens" icon="key" href="/getting-started/client-tokens">
    Set up secure token rotation for frontend auth
  </Card>

  <Card title="Streaming Best Practices" icon="gauge-high" href="/models/realtime/streaming-best-practices">
    Optimize camera setup, prompts, and error handling
  </Card>
</CardGroup>
