Skip to main content
Use client tokens for browser and mobile realtime connections. Client tokens are short-lived keys you create on your backend, then pass to your frontend for client.realtime.connect().
Never expose your permanent API key (sk-...) in client-side code. Use client tokens (ek_...) for all browser and mobile realtime sessions.

Why client tokens

  • Safe to send to browsers and mobile apps
  • Short-lived (configurable TTL, 1–3600 seconds, default 60s)
  • Optional model scoping (restrict which models the key can access)
  • Limited scope (cannot create new tokens)
  • Expiration blocks new connections, but does not disconnect active realtime sessions

End-to-end flow

1

Create token on your backend

Your backend uses your permanent API key to create a client token.
2

Return token to the frontend

Return apiKey and expiresAt from your backend endpoint.
3

Connect with the client token

Your frontend uses the token as apiKey when connecting to realtime.

Options

All options are optional. Without options, tokens use a 60-second TTL and are unrestricted.
ParameterTypeDescription
expiresInnumberTTL in seconds (1–3600, default 60)
allowedModelsstring[]Restrict which models the key can access (max 20)
constraintsobjectOperational limits (see below)
metadataobjectCustom key-value pairs to attach to the token
Constraints object:
{
  "realtime": {
    "maxSessionDuration": 120  // max seconds per WebSocket session (min 10)
  }
}
expiresIn vs maxSessionDuration — these control different things. expiresIn sets how long the token can be used to start new connections. Once a realtime session is established, the token’s expiration does not terminate it. maxSessionDuration caps how long an individual realtime session can remain active, regardless of token expiration. Use both together for full control: e.g. a 5-minute token window with a 2-minute max per session.

Model scoping

Pass allowedModels to restrict which models a token can be used with. The bouncer verifies model permissions when the client connects — if the model isn’t in the allowed list, the connection is rejected. Tokens created without allowedModels are unrestricted and work with any model.

Backend examples

// app/api/realtime-token/route.ts
import { createDecartClient } from "@decartai/sdk";
import { NextResponse } from "next/server";

const client = createDecartClient({
  apiKey: process.env.DECART_API_KEY,
});

export async function POST() {
  try {
    // Basic — 60s TTL, unrestricted
    const token = await client.tokens.create();
    return NextResponse.json(token);
  } catch {
    return NextResponse.json({ error: "Failed to create token" }, { status: 500 });
  }
}

Frontend example

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

const model = models.realtime("lucy_2_rt");

async function connectRealtime() {
  const tokenResponse = await fetch("/api/realtime-token", { method: "POST" });
  const { apiKey } = await tokenResponse.json();

  const stream = await navigator.mediaDevices.getUserMedia({
    video: { frameRate: model.fps, width: model.width, height: model.height },
    audio: true,
  });

  const client = createDecartClient({ apiKey });

  return client.realtime.connect(stream, {
    model,
    onRemoteStream: (remoteStream) => {
      document.getElementById("output").srcObject = remoteStream;
    },
    initialState: {
      prompt: { text: "Anime style", enhance: true },
    },
  });
}

Rotation strategy

  • Create tokens on demand when users open a realtime session
  • Use shorter TTLs (e.g. 60s) for tighter security; use longer TTLs (e.g. 300–600s) when refresh overhead matters
  • Refresh token before starting a new session if the old token is near expiry
  • Do not persist client tokens in local storage
  • Revoke permanent keys in dashboard if you suspect leakage
For production readiness, also follow the realtime reliability guide in Streaming Best Practices.