# Lucy 2.5 — API Onboarding (Decart) > From zero to a working realtime video-editing demo: install the SDK, get an API key, connect a camera stream, and edit it live with text prompts and reference images. Lucy 2.5 edits any element of a live video at 720p over WebRTC — add/replace/remove objects, swap characters, change backgrounds, restyle, VFX. This file is the machine-readable version of the Lucy API onboarding page. If you are a coding agent: everything needed to build a working demo is below. Full API reference: https://docs.platform.decart.ai/models/realtime/lucy-2.5 · Complete docs index: https://docs.platform.decart.ai/llms.txt The finished product of this guide: a page with two videos side by side — the raw camera on the left, Lucy's edited output on the right — and a prompt box that applies live edits to the stream. ## 1) Install the SDK JavaScript: ```bash npm install @decartai/sdk ``` Python: ```bash pip install decart ``` Android (Kotlin): ```kotlin // settings.gradle.kts — add the JitPack repository dependencyResolutionManagement { repositories { google() mavenCentral() maven { url = uri("https://jitpack.io") } } } // app build.gradle.kts — add the dependency dependencies { implementation("com.github.DecartAI:decart-android:0.2.0") } ``` ## 2) Get an API key Create a key at https://platform.decart.ai/api-keys (sign in → API Keys → Create API key). Already have one? Skip this step. Keep it server-side or in an untracked env file: ```bash # .env.local — keep the key out of source control DECART_API_KEY=your-api-key-here ``` ## 3) Connect and start editing JavaScript: ```typescript 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, }); ``` Python: ```python import asyncio from decart import DecartClient, models from decart.types import ModelState, Prompt async def main(): async with DecartClient(api_key="your-api-key-here") as client: model = models.realtime("lucy-2.5") realtime = await client.realtime.connect( stream=media_stream, model=model, on_remote_stream=lambda s: display(s), initial_state=ModelState( prompt=Prompt( text="Change the wall's color to light blue, natural consistent paint finish.", enhance=True, ), ), ) with open("character.jpg", "rb") as f: await realtime.set( prompt="Substitute the character in the video with the person in the reference image.", image=f, enhance=True, ) asyncio.run(main()) ``` Tip: pass the prompt and/or reference image in `initialState` so the first frame is already transformed — otherwise viewers briefly see the raw camera feed. ## 4) Updating the reference image Change the reference image at any time without reconnecting. `set()` atomically replaces the session state — fields you omit are cleared, so always include every field you want to keep. JavaScript: ```typescript // 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 }); ``` Python: ```python # Change to a new character with open("new_character.jpg", "rb") as f: await realtime.set( prompt="Substitute the character in the video with the person in the reference image.", image=f, enhance=True, ) # Set a new image only (clears any previous prompt) with open("another.jpg", "rb") as f: await realtime.set(image=f) # Set a new prompt only (clears any previous image) await realtime.set(prompt="Add dark sunglasses to the person's face.") ``` Reference image best practices: ≥512×512px (JPEG/PNG/WebP), clear even lighting, key features fully visible, framing matched to the source video. ## 5) Prompting Lucy Updating the prompt is one call: ```typescript // 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, }); ``` ```python # Text-only edit — update the prompt mid-session, no reconnect needed await realtime.set( prompt="Substitute the character in the video with a futuristic robot.", enhance=True, ) # Prompt + reference image — tell Lucy what to take from the image with open("warrior.jpg", "rb") as f: await realtime.set( prompt="Substitute the character in the video with the warrior from the attached image.", image=f, enhance=True, ) ``` Lucy 2.5 responds best when prompts follow the pattern for each edit type: | Edit type | Prompt template | Copy-paste examples | | --- | --- | --- | | Character swap | `Substitute the character in the video with .` | `"Substitute the character in the video with a futuristic robot."` · `"Substitute the character in the video with the warrior from the attached image."` *(pair with a reference image via `set({ image })`)* | | Replace | `Change with .` | `"Change my shirt with a black tuxedo, crisp white dress shirt, and a black bow tie."` · `"Replace my clothes with the attached image."` *(pair with a reference image via `set({ image })`)* | | Remove | `Remove from the scene.` | `"Remove my head from the scene."` · `"Remove my glasses."` | | Add | `Add to .` | `"Add a black top hat on top of my head."` · `"Add the item from the attached image to the desk beside me."` *(pair with a reference image via `set({ image })`)* | | Background | `Change the background to .` | `"Change the background to a sandy beach with clear blue water and a bright sunny sky."` · `"Change the background to a neon-lit city street at night with reflections on wet pavement."` | | Style | `Change the style of the video to
``` main.js: ```javascript import { createDecartClient, models } from "@decartai/sdk"; const model = models.realtime("lucy-2.5"); // 1. Camera in — ask for the model's native format const stream = await navigator.mediaDevices.getUserMedia({ video: { frameRate: model.fps, width: model.width, height: model.height }, }); document.getElementById("input-video").srcObject = stream; // 2. Connect — Lucy's edited stream comes back through onRemoteStream const client = createDecartClient({ apiKey: import.meta.env.VITE_DECART_API_KEY, // fine for local dev; see note below }); const realtimeClient = await client.realtime.connect(stream, { model, mirror: "auto", onRemoteStream: (transformedStream) => { document.getElementById("output-video").srcObject = transformedStream; }, initialState: { prompt: { text: "Restyle to animated movie", enhance: true }, }, }); // 3. The prompt line — every submit becomes a live edit document.getElementById("prompt-form").addEventListener("submit", async (event) => { event.preventDefault(); const text = document.getElementById("prompt").value.trim(); if (text) { await realtimeClient.set({ prompt: text, enhance: true }); } }); // Tidy up when the tab closes window.addEventListener("beforeunload", () => { realtimeClient.disconnect(); stream.getTracks().forEach((track) => track.stop()); }); ``` Production note — don't ship your permanent API key to browsers. Mint short-lived client tokens on your backend: ```typescript // 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 feed Lucy Any `MediaStream` works as input — not just cameras. Video file as input: ```typescript // Use an uploaded video file as the input instead of the camera const video = document.createElement("video"); video.src = URL.createObjectURL(fileInput.files[0]); // video.muted = true; video.loop = true; video.playsInline = true; await video.play(); // captureStream() turns the playing