API Quick Start
Get started with API-based control - the recommended approach for production applications.
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
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?
- Frontend calls your backend to create a session
- Backend calls Kaltura API with secure Kaltura Session
- Backend returns
sessionIdandtokento frontend - Frontend uses Client SDK for display only (with token)
- All control actions go through your backend
- 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
- Control API Overview - Understand the API architecture
- Authentication - Kaltura Session and JWT tokens
- API Endpoints - Complete endpoint reference
See Complete Examples
- Server Controlled Example - Complete Node.js + React example
- Error Recovery - Robust error handling
Production Deployment
- Production Deployment Guide - Best practices
- Troubleshooting - Common issues and solutions
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.