Skip to main content
View as Markdown

API Quick Start - Agentic Avatars SDK

API Quick Start​

Get up and running with Kaltura Avatar using 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 (Client SDK) ←→ Your Backend Server ←→ Kaltura Avatar API
(display only) (control + KS)

The browser uses a token, the backend holds the Kaltura Session, and sessions flow between your backend and Kaltura's API.

Step 1: Get Your Kaltura Session (KS)​

Follow the Kaltura Session Guide to generate a KS, then store it in your environment:

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

The KS should only reside on the backend server. Never include it in client-side code.

Step 2: Install Dependencies​

Backend:

npm install express cors

Frontend:

npm install @unisphere/models-sdk-js

Step 3: Create Backend Endpoints​

Create server.js:

import express from 'express';
import cors from 'cors';

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

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

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

const response = await fetch(`$\{BASE_URL\}/create`, {
method: 'POST',
headers: {
Authorization: `ks $\{AVATAR_KS\}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
clientId: 'kaltura-avatar-sdk',
visualConfig: { id: visualId },
voiceConfig: voiceId
? { id: voiceId, modelId: 'eleven_flash_v2_5' }
: undefined,
}),
});

const data = await response.json();
res.json({ sessionId: data.sessionId, token: data.token });
} catch (error) {
console.error('Create session failed:', error);
res.status(500).json({ error: 'Failed to create 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(`$\{BASE_URL\}/${sessionId}/say-text`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ text, turnId, isFinal }),
});

const data = await response.json();
res.json(data);
} catch (error) {
console.error('Say text failed:', error);
res.status(500).json({ error: 'Failed to send text' });
}
});

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

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

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

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

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

const data = await response.json();
res.json(data);
} catch (error) {
console.error('End session failed:', 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\}`));

Step 4: Create Frontend​

index.html:

<!DOCTYPE html>
<html>
<head>
<title>Kaltura Avatar — API Control</title>
<style>
#avatar-container {
width: 512px;
height: 512px;
border: 2px solid #ccc;
border-radius: 8px;
overflow: hidden;
}
</style>
</head>
<body>
<h1>Kaltura Avatar (API-Controlled)</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>
<p>Status: <span id="status">Not started</span></p>
<script src="main.js" type="module"></script>
</body>
</html>

main.js:

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

let session;
let sessionId;
let token;

function generateTurnId() {
return `turn-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
}

document.getElementById('start-btn').addEventListener('click', async () => {
try {
document.getElementById('status').textContent = 'Creating session...';

// 1. Create session via backend
const createRes = await fetch('/api/avatar/create-session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
visualId: 'visual-123',
voiceId: 'voice-456',
}),
});

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

// 2. Create SDK instance (NO apiKey — backend holds the KS)
session = new KalturaAvatarSession(undefined, {
baseUrl: 'https://api.avatar.us.kaltura.ai/',
});

// 3. Initialize with backend-provided credentials
await session.initSession(
{ sessionId, token },
{ videoContainerId: 'avatar-container' }
);

document.getElementById('status').textContent = 'Connected';
document.getElementById('speak-btn').disabled = false;
document.getElementById('interrupt-btn').disabled = false;
document.getElementById('end-btn').disabled = false;
document.getElementById('start-btn').disabled = true;

// 4. Send 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 your Kaltura Avatar.',
turnId: generateTurnId(),
isFinal: true,
}),
});
} catch (error) {
console.error('Failed to start:', error);
document.getElementById('status').textContent = 'Error: ' + error.message;
}
});

document.getElementById('speak-btn').addEventListener('click', async () => {
await fetch('/api/avatar/say-text', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
sessionId,
token,
text: 'This is a test message from the avatar.',
turnId: generateTurnId(),
isFinal: true,
}),
});
});

document.getElementById('interrupt-btn').addEventListener('click', async () => {
await fetch('/api/avatar/interrupt', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sessionId, token }),
});
console.log('Avatar interrupted');
});

document.getElementById('end-btn').addEventListener('click', async () => {
await fetch('/api/avatar/end-session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ sessionId, token }),
});
document.getElementById('status').textContent = 'Session ended';
document.getElementById('speak-btn').disabled = true;
document.getElementById('interrupt-btn').disabled = true;
document.getElementById('end-btn').disabled = true;
document.getElementById('start-btn').disabled = false;
});

Step 5: Run the Application​

node server.js

Navigate to http://localhost:3000 and click Start Avatar.

What Just Happened?​

  1. Frontend called backend — requested session creation
  2. Backend called Kaltura — with secure KS in Authorization header
  3. Backend returned session info — sessionId and JWT token
  4. Frontend used Client SDK — for display only (no KS needed)
  5. All control went through backend — say-text, interrupt, end
  6. KS stayed secure — never exposed to the browser

Key Benefits​

  • Secure: KS never exposed to the browser
  • Separation: Backend controls, frontend displays
  • Validation: Backend can validate before API calls
  • Audit trail: Backend can log all actions
  • Flexible: Backend can incorporate business logic

Troubleshooting​

401 Unauthorized​

  • Verify the Kaltura Session is correct
  • Confirm the token is sent in the Authorization header
  • Check for token expiration

Session not found (404)​

  • Verify the sessionId is correct
  • Check whether the session was already ended
  • Ensure session creation succeeded

CORS errors​

Add CORS middleware to Express:

import cors from 'cors';
app.use(cors());

Next Steps​