API Onboarding
Lucy 2.5 · Realtime video editing

From zero to live AI video

Before you build, take a 60-second sneak peek — Lucy edits your camera feed live. Type a prompt, drop a reference image, tap a preset. Then scroll: five short steps to this exact demo, running in your own app.

Start building
Step 01

Install the SDK

One package. This guide builds in the browser with JavaScript — everything maps 1:1 to Python and Android.

npm install @decartai/sdk

SDK references: JavaScript · Python · Android

Step 02

Get your API key

Create a key in the platform dashboard — it takes under a minute. Already have one? Skip straight to step 03.

platform.decart.ai/api-keysSign in → API Keys → Create API key
.env.local
# .env.local — keep the key out of source control
DECART_API_KEY=your-api-key-here

How this page does itthe sneak peek above runs on this same flow. Our key lives on the server; your browser only ever received a short-lived token scoped to lucy-2.5 and a single 60-second session. Steal the pattern from step 06's production note.

Step 03

Connect and start editing

Grab a camera stream, open the connection, and Lucy starts editing. The transformed stream arrives in onRemoteStream — wire it to a video element and you're live.

import { createDecartClient, models } from "@decartai/sdk";

const model = models.realtime("lucy-2.5");

// 1. Capture the camera at the model's native format
const stream = await navigator.mediaDevices.getUserMedia({
  video: {
    frameRate: model.fps,
    width: model.width,
    height: model.height,
  },
});

// 2. Create a client and connect — the edited stream comes back via callback
const client = createDecartClient({
  apiKey: "your-api-key-here",
});

const realtimeClient = await client.realtime.connect(stream, {
  model,
  mirror: "auto",
  onRemoteStream: (transformedStream) => {
    document.getElementById("output").srcObject = transformedStream;
  },
  // Pass the first edit here so the very first frame is already transformed
  initialState: {
    prompt: {
      text: "Change the wall's color to light blue, natural consistent paint finish.",
      enhance: true,
    },
  },
});

// 3. Edit the live stream at any time
await realtimeClient.set({
  prompt: "Substitute the character in the video with the person in the reference image.",
  image: characterImage,
  enhance: true,
});

First frame, already transformedpass the prompt and/or reference image in initialState so the very first generated frame uses it — otherwise viewers briefly see the raw feed.

Step 04

Updating the reference image

Prompts describe the edit; a reference image shows it. Swap either at any time — mid-session, no reconnect.

// Change to a new character
await realtimeClient.set({
  prompt: "Substitute the character in the video with the person in the reference image.",
  image: newCharacterImage,
  enhance: true,
});

// Set a new image only (clears any previous prompt)
await realtimeClient.set({ image: newCharacterImage });

// Set a new prompt only (clears any previous image)
await realtimeClient.set({ prompt: "Add dark sunglasses to the person's face." });

// Clear the reference image (fall back to text-only editing)
await realtimeClient.set({ image: null });

set() replaces the entire statefields you omit are cleared. Always include every field you want to keep — that keeps prompt and image in sync with no intermediate states.

512×512 or larger

JPEG, PNG, or WebP. Sharper reference, sharper result.

Clear, even lighting

Avoid harsh shadows or reflections that hide the details you care about.

Match the framing

Full-body video? Use a full-body reference, not a head-and-shoulders crop.

No occlusion

Keep the key features fully visible — hidden parts can't be extracted.

Step 05

Prompt like a director

Updating the prompt is one call. Lucy responds best when the prompt follows the pattern for its edit type — steal from the cookbook below; every example copies with a click and works in the demo up top.

// Text-only edit — update the prompt mid-session, no reconnect needed
await realtimeClient.set({
  prompt: "Substitute the character in the video with a futuristic robot.",
  enhance: true, // Lucy expands terse prompts into rich visual direction
});

// Prompt + reference image — tell Lucy what to take from the image
await realtimeClient.set({
  prompt: "Substitute the character in the video with the warrior from the attached image.",
  image: referenceImage, // File | Blob
  enhance: true,
});

Character swap

Substitute the character in the video with <description>.

Replace

Change <object> with <description of replacement>.

Remove

Remove <object> from the scene.

Add

Add <description of object> to <where to add it>.

Background

Change the background to <description of new background>.

Style

Change the style of the video to <style description>.

VFX

Add <effect description> to <location in scene>.

Describe what you want to seeskip negative instructions (“don't add a hat”) and vague asks (“make it royal”) — name the object, the look, and the location. For layered edits and troubleshooting, see the full prompting guide.

Step 06

The complete example

The demo at the top of this page, reduced to its skeleton: camera in on the left, Lucy out on the right, one prompt line underneath. Two files and a terminal — running in about a minute.

# Scaffold a tiny Vite app and add the SDK
npm create vite@latest my-lucy-demo -- --template vanilla
cd my-lucy-demo
npm install @decartai/sdk

# Add your key (from step 02)
echo "VITE_DECART_API_KEY=your-api-key-here" > .env.local

# Replace index.html and main.js with the two files below, then:
npm run dev

Before you shipa key in VITE_* env ends up in the browser bundle — fine on localhost, not in production. Mint short-lived client tokens on your backend instead:

client-side auth — the pattern this page uses
// Backend: mint a short-lived token with your permanent key
const token = await client.tokens.create();

// Frontend: connect with the token instead of the real key
const frontendClient = createDecartClient({ apiKey: token.apiKey });
More ways to use it

Beyond the camera

Lucy edits any MediaStream — the camera is just the default. Point it at an uploaded video, a shared screen, or anything else you can turn into a stream.

video file as input
// Use an uploaded video file as the input instead of the camera
const video = document.createElement("video");
video.src = URL.createObjectURL(fileInput.files[0]); // <input type="file" accept="video/*">
video.muted = true;
video.loop = true;
video.playsInline = true;
await video.play();

// captureStream() turns the playing <video> into a MediaStream Lucy can edit
// (Safari calls it webkitCaptureStream)
const fileStream = (video.captureStream ?? video.webkitCaptureStream).call(video);

const realtimeClient = await client.realtime.connect(fileStream, {
  model,
  onRemoteStream: (transformedStream) => {
    document.getElementById("output-video").srcObject = transformedStream;
  },
  initialState: {
    prompt: { text: "Change the style of the video to Claymation.", enhance: true },
  },
});
screen share as input
// Edit a shared screen, window, or tab in realtime
const screenStream = await navigator.mediaDevices.getDisplayMedia({
  video: { frameRate: model.fps, width: model.width, height: model.height },
});

const realtimeClient = await client.realtime.connect(screenStream, {
  model,
  onRemoteStream: (transformedStream) => {
    document.getElementById("output-video").srcObject = transformedStream;
  },
});

Same session, same controlseverything from steps 04–05 — prompts, reference images, set() — works identically whatever the input source is.

Keep going

This page is the fast path. The docs are the full map.

Connection lifecycle, client tokens, portrait mode, self-anchoring, virtual try-on, and the complete prompting guide — all in the platform docs.

Building with an agent?

Send your agent this page's URL — the entire guide is served as markdown at /llms.txtor copy it straight to your clipboard.