Skip to main content
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
If you already use Decart’s batch models (video generation, image generation), SDK Direct for realtime works the same way — same API key, same SDK, same billing.

Characteristics

PropertyValue
White-labelNo — end users see Decart endpoints and SDK
Frame accessNo
Provider visibilitySession events only (generationTick, connectionChange)
Client requirementsBrowser or native app with WebRTC + Decart SDK
Your infrastructureBackend for token minting only
Media quality is identical to using Decart directly. There is no degradation — the SDK connects straight to Decart.

Architecture

How it works

1

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.
// Your backend
const client = createDecartClient({ apiKey: process.env.DECART_API_KEY });
const token = await client.tokens.create();
// Return token.apiKey to your frontend
2

Connect from the frontend

Your frontend uses the client token to connect. The SDK handles WebSocket signaling, WebRTC negotiation, ICE candidates, and auto-reconnection.
import { createDecartClient, models } from "@decartai/sdk";

const model = models.realtime("lucy_2_rt");
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 });
3

Control the generation

Use set() to change the prompt, reference image, or both at any time during the session:
// Change the prompt
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 });
4

Listen to events

Track session state and usage with event listeners:
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);
});
5

Disconnect when done

Clean up the session when the user leaves. This closes the WebSocket and WebRTC connections.
rt.disconnect();

API reference

connect(stream, options)

Creates a new realtime session and establishes the WebRTC connection.
const rt = await client.realtime.connect(stream, {
  model: models.realtime("lucy_2_rt"),
  onRemoteStream: (stream) => { videoElement.srcObject = stream; },
  initialState: {
    prompt: { text: "Anime style", enhance: true },
    image: selectedFile,
  },
});
model
ModelDefinition
required
The realtime model — use models.realtime("model_id").
onRemoteStream
(stream: MediaStream) => void
required
Called when the remote video/audio stream is ready.
initialState
object
Pre-configure a prompt and/or image before the first frame.
customizeOffer
(offer: RTCSessionDescriptionInit) => Promise<void>
Modify the SDP offer before sending (advanced).
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)

Update the prompt, reference image, or both. At least one of prompt or image is required. Awaits server acknowledgment — throws if rejected by moderation or on timeout.
await rt.set({ prompt: "Anime style", enhance: true, image: file });
prompt
string
The text prompt to apply.
enhance
boolean
default:"true"
Enhance the prompt automatically. Set to false for exact prompt text.
image
Blob | File | string | null
Reference image. Pass null to clear the current image. Strings are treated as base64.

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).
try {
  await rt.setPrompt("Anime style", { enhance: true });
} catch (error) {
  // Prompt was rejected by moderation or timed out
  console.error(error.message);
}
enhance
boolean
default:"true"
Enhance the prompt automatically.

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.
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);
}
prompt
string
Set a prompt alongside the image.
enhance
boolean
default:"true"
Enhance the prompt automatically.
timeout
number
default:"15000"
Timeout in milliseconds.
Reference images must be under 10 MB. The SDK rejects oversized images before sending them.

disconnect()

End the session and clean up all WebRTC and WebSocket connections.
rt.disconnect();

isConnected()

Returns true if the SDK has an active connection.
if (rt.isConnected()) {
  await rt.set({ prompt: "New prompt" });
}

getConnectionState()

Returns the current connection state.
const state = rt.getConnectionState();
// "connecting" | "connected" | "generating" | "disconnected" | "reconnecting"

sessionId

The current session identifier, or null if not connected.
console.log("Session:", rt.sessionId);

Events

Listen to events with rt.on(event, listener) and remove listeners with rt.off(event, listener).
EventPayloadDescription
connectionChangeConnectionStateConnection state changed — "connecting", "connected", "generating", "disconnected", "reconnecting"
generationTick{ seconds: number }Total elapsed generation time (cumulative). Use for billing.
errorDecartSDKErrorAn error occurred. The error has a message property with details.
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.

Error handling

The SDK surfaces errors through the error event and by throwing from async methods like set(), setPrompt(), and setImage().
// 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