---
title: API Quick Start
url: https://docs.kaltura.com/docs/conversational-agents/agentic-avatars-sdk/quick-start-api
---


## API Quick Start

Get up and running with Kaltura Avatar using API-based control — the recommended approach for production applications.

:::tip 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](/docs/getting-started/authentication/ks-overview) to generate a KS, then store it in your environment:

```bash
# .env
AVATAR_KS=your-kaltura-session-here
```

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

### Step 2: Install Dependencies

**Backend:**

```bash
npm install express cors
```

**Frontend:**

```bash
npm install @unisphere/models-sdk-js
```

### Step 3: Create Backend Endpoints

Create `server.js`:

```javascript
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`:**

```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`:**

```javascript
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

```bash
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:

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

### Next Steps

- [Control API Reference](/docs/conversational-agents/agentic-avatars-sdk/control-api-overview) — complete API documentation
- [Client SDK Reference](/docs/conversational-agents/agentic-avatars-sdk/client-sdk-installation) — SDK documentation
- [Examples](/docs/conversational-agents/agentic-avatars-sdk/examples-simple-client-side) — complete code samples
