Documentation Index
Fetch the complete documentation index at: https://mintlify.com/DecartAI/sdk/llms.txt
Use this file to discover all available pages before exploring further.
This example demonstrates how to build a secure real-time video transformation application using Next.js. It uses the App Router with client tokens to keep your API key secure on the server.
What You’ll Build
A Next.js application that:
- Issues short-lived client tokens from a server API route
- Uses client tokens in the browser to connect to Decart’s real-time API
- Captures and transforms webcam video in real-time
- Never exposes your API key to the client
Prerequisites
- Node.js 18 or higher
- A Decart API key
- A webcam for testing
Setup
Clone and navigate to the example
git clone https://github.com/decartai/sdk
cd sdk/examples/nextjs-realtime
Configure your API key
Create a .env.local file and add your API key:DECART_API_KEY=your-api-key-here
Use .env.local in Next.js to ensure environment variables are not exposed to the client.
Install dependencies
From the repository root: Start the development server
Architecture
Browser → /api/realtime-token → Server (creates token) → Decart API
← Client Token ←
→ Realtime Connection (using client token) → Decart API
The client token is short-lived and scoped to real-time API access only, keeping your main API key secure.
Server: Token Generation API Route
The /api/realtime-token/route.ts endpoint creates client tokens:
import { createDecartClient } from "@decartai/sdk";
import { NextResponse } from "next/server";
const DECART_API_KEY = process.env.DECART_API_KEY;
export async function POST() {
try {
if (!DECART_API_KEY) {
return NextResponse.json(
{ error: "DECART_API_KEY is not set" },
{ status: 500 }
);
}
const client = createDecartClient({
apiKey: DECART_API_KEY,
});
const token = await client.tokens.create();
return NextResponse.json(token);
} catch (error) {
console.error("Failed to create client token:", error);
return NextResponse.json(
{ error: "Failed to create client token" },
{ status: 500 }
);
}
}
Client: Main Page Component
The app/page.tsx component manages the prompt state:
"use client";
import { useState } from "react";
import { VideoStream } from "../components/video-stream";
export default function Home() {
const [prompt, setPrompt] = useState("anime style, vibrant colors");
return (
<main style={{ padding: "2rem", fontFamily: "system-ui" }}>
<h1>Decart Realtime Demo</h1>
<div style={{ marginBottom: "1rem" }}>
<label>
Style prompt:
<input
type="text"
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
style={{ marginLeft: "0.5rem", width: "300px", padding: "0.5rem" }}
/>
</label>
</div>
<VideoStream prompt={prompt} />
</main>
);
}
Client: VideoStream Component
The components/video-stream.tsx component fetches a token and connects:
"use client";
import {
createDecartClient,
type DecartSDKError,
models,
type RealTimeClient,
} from "@decartai/sdk";
import { useEffect, useRef, useState } from "react";
interface VideoStreamProps {
prompt: string;
}
export function VideoStream({ prompt }: VideoStreamProps) {
const inputRef = useRef<HTMLVideoElement>(null);
const outputRef = useRef<HTMLVideoElement>(null);
const realtimeClientRef = useRef<RealTimeClient | null>(null);
const [status, setStatus] = useState<string>("idle");
useEffect(() => {
let mounted = true;
async function start() {
try {
const model = models.realtime("mirage_v2");
setStatus("requesting camera...");
const stream = await navigator.mediaDevices.getUserMedia({
video: {
frameRate: model.fps,
width: model.width,
height: model.height,
},
});
if (!mounted) return;
if (inputRef.current) {
inputRef.current.srcObject = stream;
}
// Fetch client token from our backend API
const tokenResponse = await fetch("/api/realtime-token", {
method: "POST",
});
if (!tokenResponse.ok) {
throw new Error("Failed to get client token");
}
const { apiKey } = await tokenResponse.json();
if (!mounted) return;
setStatus("connecting...");
const client = createDecartClient({ apiKey });
const realtimeClient = await client.realtime.connect(stream, {
model,
onRemoteStream: (transformedStream: MediaStream) => {
if (outputRef.current) {
outputRef.current.srcObject = transformedStream;
}
},
initialState: {
prompt: { text: prompt, enhance: true },
},
});
realtimeClientRef.current = realtimeClient;
// Subscribe to events
realtimeClient.on("connectionChange", (state) => {
setStatus(state);
});
realtimeClient.on("error", (error: DecartSDKError) => {
setStatus(`error: ${error.message}`);
});
} catch (error) {
setStatus(`error: ${error}`);
}
}
start();
return () => {
mounted = false;
realtimeClientRef.current?.disconnect();
};
}, []);
// Update prompt when it changes
useEffect(() => {
if (realtimeClientRef.current?.isConnected()) {
realtimeClientRef.current.setPrompt(prompt, { enhance: true });
}
}, [prompt]);
return (
<div>
<p>Status: {status}</p>
<div style={{ display: "flex", gap: "1rem" }}>
<div>
<h3>Input</h3>
<video ref={inputRef} autoPlay muted playsInline width={400} />
</div>
<div>
<h3>Styled Output</h3>
<video ref={outputRef} autoPlay playsInline width={400} />
</div>
</div>
</div>
);
}
Key Concepts
Client Tokens
Client tokens provide secure, temporary access to the real-time API:
// Server-side: Create token
const token = await client.tokens.create();
// Client-side: Fetch token
const response = await fetch("/api/realtime-token", { method: "POST" });
const { apiKey } = await response.json();
// Use token to create client
const client = createDecartClient({ apiKey });
Benefits of Client Tokens
- Security: Your main API key never leaves the server
- Scoped Access: Tokens only work with the real-time API
- Time-Limited: Tokens expire automatically
- Simple: Drop-in replacement for API keys in client code
Next.js App Router
This example uses Next.js App Router features:
- Server Actions: API routes in
app/api/ directory
- Client Components: Interactive UI with
"use client" directive
- Environment Variables:
.env.local for server-side secrets
Error Handling
Handle token fetch and connection errors:
const tokenResponse = await fetch("/api/realtime-token", {
method: "POST",
});
if (!tokenResponse.ok) {
throw new Error("Failed to get client token");
}
Available Models
You can use different real-time models:
mirage_v2 - MirageLSD video restyling (recommended)
mirage - Original MirageLSD model
lucy_v2v_720p_rt - Lucy for video editing (add/change objects)
lucy_2_rt - Lucy 2 with reference image support