Skip to main content

API Quick Start

Get started with API-based control - the recommended approach for production applications.

Recommended for Production

This approach keeps your Kaltura Session secure on the backend and provides better separation of concerns.

Architecture Overview

┌─────────────┐      ┌─────────────┐      ┌──────────────┐
│ Browser │◄────►│ Backend │◄────►│ Avatar API │
│ │ │ │ │ │
│ Client SDK │ │ Your Server │ │ (Kaltura) │
│ (Display) │ │ (Control) │ │ │
└─────────────┘ └─────────────┘ └──────────────┘
token Kaltura Session sessions

Step 1: Get Your Kaltura Session (KS)

You'll need a Kaltura Session to authenticate with the Avatar API.

Generate a KS: Follow the Kaltura Session Creation Guide

Store your KS securely in environment variables or generate a new KS as needed.:

# .env
AVATAR_KS=your-kaltura-session-here
Security

The Kaltura Session should only exist on your backend server, never in client-side code.

Step 2: Install Dependencies

Backend:

npm install node-fetch # or use built-in fetch in Node.js 18+

Frontend:

npm install @unisphere/models-sdk-js

Step 3: Create Backend Endpoint

Create an Express server (or use your existing backend):

server.js

import express from 'express';
import fetch from 'node-fetch';

const app = express();
app.use(express.json());

const AVATAR_KS = process.env.AVATAR_KS; // Secure!
const AVATAR_BASE_URL = 'https://api.avatar.us.kaltura.ai/v1/avatar-session';

// Create avatar session
app.post('/api/avatar/create-session', async (req, res) => {
try {
const { avatarId, voiceId } = req.body;

// Call Kaltura API
const response = await fetch(`${AVATAR_BASE_URL}/create`, {
method: 'POST',
headers: {
Authorization: `KS ${AVATAR_KS}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
visualConfig: { avatarId },
voiceConfig: voiceId ? { id: voiceId } : undefined,
}),
});

if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}

const data = await response.json();
// Return sessionId and token to frontend
res.json({
sessionId: data.sessionId,
token: data.token,
});
} catch (error) {
console.error('Failed to create session:', error);
res.status(500).json({ error: 'Failed to create avatar session' });
}
});

// Make avatar speak
app.post('/api/avatar/say-text', async (req, res) => {
try {
const { sessionId, token, text, turnId, isFinal } = req.body;

const response = await fetch(`${AVATAR_BASE_URL}/${sessionId}/say-text`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ text, turnId, isFinal }),
});

if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}

const data = await response.json();
res.json(data);
} catch (error) {
console.error('Failed to say text:', error);
res.status(500).json({ error: 'Failed to make avatar speak' });
}
});

// Interrupt avatar
app.post('/api/avatar/interrupt', async (req, res) => {
try {
const { sessionId, token } = req.body;

const response = await fetch(`${AVATAR_BASE_URL}/${sessionId}/interrupt`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
},
});

if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}

const data = await response.json();
res.json(data);
} catch (error) {
console.error('Failed to interrupt:', error);
res.status(500).json({ error: 'Failed to interrupt avatar' });
}
});

// End session
app.post('/api/avatar/end-session', async (req, res) => {
try {
const { sessionId, token } = req.body;

const response = await fetch(`${AVATAR_BASE_URL}/${sessionId}/end`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
},
});

if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}

const data = await response.json();
res.json(data);
} catch (error) {
console.error('Failed to end session:', error);
res.status(500).json({ error: 'Failed to end session' });
}
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});

.env

AVATAR_KS=your-api-key-here

Step 3: Create Frontend

index.html

<!DOCTYPE html>
<html>
<head>
<title>Avatar Demo - API Control</title>
<style>
#avatar-container {
width: 512px;
height: 512px;
border: 2px solid #ccc;
border-radius: 8px;
overflow: hidden;
}
button {
margin: 5px;
padding: 10px 20px;
}
#status {
margin-top: 10px;
font-weight: bold;
}
</style>
</head>
<body>
<h1>Kaltura Avatar - API Control</h1>

<div id="avatar-container"></div>

<div style="margin-top: 20px;">
<button id="start-btn">Start Avatar</button>
<button id="speak-btn" disabled>Make Avatar Speak</button>
<button id="interrupt-btn" disabled>Interrupt</button>
<button id="end-btn" disabled>End Session</button>
</div>

<div id="status">Not started</div>

<script src="main.js" type="module"></script>
</body>
</html>

main.js

import { KalturaAvatarSession } from '@unisphere/models-sdk-js';

let session = null;
let sessionId = null;
let token = null;

// Utility to generate unique turn IDs
function generateTurnId() {
return `turn-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}

// UI Elements
const startBtn = document.getElementById('start-btn');
const speakBtn = document.getElementById('speak-btn');
const interruptBtn = document.getElementById('interrupt-btn');
const endBtn = document.getElementById('end-btn');
const statusEl = document.getElementById('status');

// Start avatar
startBtn.addEventListener('click', async () => {
try {
statusEl.textContent = 'Creating session...';
startBtn.disabled = true;

// Step 1: Call your backend to create session
const response = await fetch('/api/avatar/create-session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
avatarId: 'avatar-123',
voiceId: 'voice-456',
}),
});

const data = await response.json();
sessionId = data.sessionId;
token = data.token;

console.log('Session created:', sessionId);

// Step 2: Use Client SDK for display only
session = new KalturaAvatarSession(token, {
baseUrl: 'https://api.avatar.us.kaltura.ai/v1/avatar-session',
});

// Initialize client to get WebRTC config
await fetch(`https://api.avatar.us.kaltura.ai/v1/avatar-session/${sessionId}/init-client`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
},
});

// Attach avatar to container (display only)
session.attachAvatar('avatar-container');

statusEl.textContent = 'Avatar ready!';
speakBtn.disabled = false;
interruptBtn.disabled = false;
endBtn.disabled = false;

// Welcome message via backend
await fetch('/api/avatar/say-text', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
sessionId,
token,
text: 'Hello! I am controlled from your backend server.',
turnId: generateTurnId(),
isFinal: true,
}),
});
} catch (error) {
console.error('Failed to start avatar:', error);
statusEl.textContent = 'Error starting avatar';
startBtn.disabled = false;
}
});

// Make avatar speak (via backend)
speakBtn.addEventListener('click', async () => {
try {
await fetch('/api/avatar/say-text', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
sessionId,
token,
text: 'This message is sent from your backend server via the API.',
turnId: generateTurnId(),
isFinal: true,
}),
});

console.log('Avatar speaking');
} catch (error) {
console.error('Failed to make avatar speak:', error);
}
});

// Interrupt (via backend)
interruptBtn.addEventListener('click', async () => {
try {
await fetch('/api/avatar/interrupt', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sessionId, token }),
});

console.log('Avatar interrupted');
} catch (error) {
console.error('Failed to interrupt:', error);
}
});

// End session (via backend)
endBtn.addEventListener('click', async () => {
try {
await fetch('/api/avatar/end-session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sessionId, token }),
});

console.log('Session ended');
statusEl.textContent = 'Session ended';

speakBtn.disabled = true;
interruptBtn.disabled = true;
endBtn.disabled = true;
startBtn.disabled = false;
} catch (error) {
console.error('Failed to end session:', error);
}
});

Step 4: Run the Application

Start backend:

node server.js

Start frontend (if using separate dev server):

npm run dev

Open your browser to http://localhost:3000 and click "Start Avatar"!

What Just Happened?

  1. Frontend calls your backend to create a session
  2. Backend calls Kaltura API with secure Kaltura Session
  3. Backend returns sessionId and token to frontend
  4. Frontend uses Client SDK for display only (with token)
  5. All control actions go through your backend
  6. Kaltura Session stays secure on the server

Key Benefits

Secure: Kaltura Session never exposed to browser
Separation: Backend controls, frontend displays
Validation: Backend can validate before calling API
Audit trail: Backend can log all actions
Flexible: Backend can add business logic

Next Steps

Explore the API

See Complete Examples

Production Deployment

Troubleshooting

401 Unauthorized

  • Check Kaltura Session is correct
  • Verify token is being sent in Authorization header
  • Ensure token hasn't expired

Session not found (404)

  • Verify sessionId is correct
  • Check if session was already ended
  • Ensure session was created successfully

CORS errors

// Add CORS middleware to Express
import cors from 'cors';
app.use(cors());

For more issues, see the Troubleshooting Guide.