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?
- Frontend called backend — requested session creation
- Backend called Kaltura — with secure KS in Authorization header
- Backend returned session info — sessionId and JWT token
- Frontend used Client SDK — for display only (no KS needed)
- All control went through backend — say-text, interrupt, end
- 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
- Control API Reference — complete API documentation
- Client SDK Reference — SDK documentation
- Examples — complete code samples