diff --git a/docs/integrations/vapi-integration.mdx b/docs/integrations/vapi-integration.mdx
new file mode 100644
index 00000000..397bed25
--- /dev/null
+++ b/docs/integrations/vapi-integration.mdx
@@ -0,0 +1,191 @@
+---
+sidebar_position: 2
+title: VAPI Integration
+description: Add a VAPI voice AI agent to a Fishjam room with a single API call.
+---
+
+import Tabs from "@theme/Tabs";
+import TabItem from "@theme/TabItem";
+
+# VAPI Integration
+
+:::info
+This tutorial requires a working Fishjam backend. If you haven't set one up yet, please check the [Backend Quick Start](../tutorials/backend-quick-start).
+:::
+
+This guide shows how to add a [VAPI](https://vapi.ai/) voice AI agent to a Fishjam room.
+Unlike custom agent integrations (such as [Gemini](./gemini-live-integration)), there is no need to build a WebSocket bridge yourself —
+Fishjam connects to VAPI internally. You only need to create a VAPI call and pass its ID to Fishjam.
+
+## Overview
+
+The workflow has two steps:
+
+1. **Create a VAPI call** using the VAPI SDK. This gives you a `callId`.
+2. **Pass the call ID to Fishjam** via `createVapiAgent()` / `create_vapi_agent()`. Fishjam joins the call and streams audio to and from the room.
+
+:::note
+The VAPI peer receives audio from only one peer at a time, so this integration works best in 1-on-1 calls. All peers in the room hear VAPI's responses.
+:::
+
+## Prerequisites
+
+You will need:
+
+- **Fishjam Server Credentials:** `fishjamId` and `managementToken`. You can get them at [fishjam.io/app](https://fishjam.io/app).
+- **VAPI Private API Key:** Obtainable from the [VAPI Dashboard](https://dashboard.vapi.ai/).
+- **VAPI Assistant ID** _(optional)_**:** Create an assistant in the [VAPI Dashboard](https://dashboard.vapi.ai/), or provide a transient assistant configuration inline.
+
+### Installation
+
+
+
+
+ ```bash
+ npm install @fishjam-cloud/js-server-sdk @vapi-ai/server-sdk
+ ```
+
+
+
+
+
+ ```bash
+ pip install fishjam-server-sdk vapi
+ ```
+
+
+
+
+## Implementation
+
+### Step 1: Create a VAPI Call
+
+Use the VAPI SDK to create a call with `vapi.websocket` transport and `pcm_s16le` audio at `16000` Hz.
+You can reference an existing assistant by its ID, or create a transient assistant inline by providing the full configuration via the `assistant` field instead of `assistantId`.
+
+
+
+
+ ```ts
+ import { VapiClient, Vapi } from '@vapi-ai/server-sdk';
+
+ const vapiClient = new VapiClient({
+ token: process.env.VAPI_API_KEY!,
+ });
+
+ // [!code highlight:11]
+ const call = await vapiClient.calls.create({
+ assistantId: process.env.VAPI_ASSISTANT_ID!,
+ transport: {
+ provider: 'vapi.websocket',
+ audioFormat: {
+ format: 'pcm_s16le',
+ container: 'raw',
+ sampleRate: 16000,
+ },
+ },
+ }) as Vapi.Call;
+ ```
+
+
+
+
+
+ ```python
+ import os
+ from vapi import Vapi
+
+ vapi_client = Vapi(token=os.environ["VAPI_API_KEY"])
+
+ # [!code highlight:11]
+ call = vapi_client.calls.create(
+ assistant_id=os.environ["VAPI_ASSISTANT_ID"],
+ transport={
+ "provider": "vapi.websocket",
+ "audioFormat": {
+ "format": "pcm_s16le",
+ "container": "raw",
+ "sampleRate": 16000,
+ },
+ },
+ )
+ ```
+
+
+
+
+### Step 2: Add the VAPI Peer to Fishjam
+
+Pass the call ID and your VAPI API key to Fishjam. Fishjam handles the rest.
+
+
+
+
+ ```ts
+ import { VapiClient, Vapi } from '@vapi-ai/server-sdk';
+
+ const vapiClient = new VapiClient({
+ token: process.env.VAPI_API_KEY!,
+ });
+
+ const call = await vapiClient.calls.create({
+ assistantId: process.env.VAPI_ASSISTANT_ID!,
+ transport: {
+ provider: 'vapi.websocket',
+ audioFormat: {
+ format: 'pcm_s16le',
+ container: 'raw',
+ sampleRate: 16000,
+ },
+ },
+ }) as Vapi.Call;
+
+ // ---cut---
+ import { FishjamClient } from '@fishjam-cloud/js-server-sdk';
+
+ const fishjamClient = new FishjamClient({
+ fishjamId: process.env.FISHJAM_ID!,
+ managementToken: process.env.FISHJAM_TOKEN!,
+ });
+
+ const room = await fishjamClient.createRoom();
+
+ // [!code highlight:4]
+ await fishjamClient.createVapiAgent(room.id, {
+ callId: call.id,
+ apiKey: process.env.VAPI_API_KEY!,
+ });
+ ```
+
+
+
+
+
+ ```python
+ import os
+ from fishjam import FishjamClient, PeerOptionsVapi
+
+ fishjam_client = FishjamClient(
+ fishjam_id=os.environ["FISHJAM_ID"],
+ management_token=os.environ["FISHJAM_TOKEN"],
+ )
+
+ room = fishjam_client.create_room()
+
+ # [!code highlight:2]
+ options = PeerOptionsVapi(api_key=os.environ["VAPI_API_KEY"], call_id=call.id)
+ peer = fishjam_client.create_vapi_agent(room.id, options)
+ ```
+
+
+
+
+## That's it
+
+Once both steps are complete, the VAPI assistant is live in the room — peers can speak to it and hear its responses.
+
+The VAPI peer's lifetime is tied to the underlying WebSocket connection. If there is a prolonged period of silence or the call ends for any other reason, the peer will disconnect and be removed from the room. You can detect this by listening for `PeerDisconnected` and `PeerDeleted` server notifications. If the peer crashes (e.g. due to an invalid transport configuration or call ID), a `PeerCrashed` notification is sent instead. See [Listening to events](../how-to/backend/server-setup#listening-to-events) for how to subscribe to these notifications.
+
+## Billing
+
+The VAPI peer is billed as a single Fishjam peer. VAPI's own usage pricing applies separately — see [VAPI pricing](https://vapi.ai/pricing) for details.
diff --git a/package.json b/package.json
index 5c53b3d8..07af34ef 100644
--- a/package.json
+++ b/package.json
@@ -58,6 +58,7 @@
"@docusaurus/types": "^3.10.0",
"@google/genai": "^1.35.0",
"@shikijs/twoslash": "^3.6.0",
+ "@vapi-ai/server-sdk": "^1.1.0",
"cspell": "^9.1.2",
"docusaurus-plugin-llms": "^0.3.0",
"docusaurus-plugin-typedoc": "1.4.0",
diff --git a/spelling.txt b/spelling.txt
index 589433d7..7384deaf 100644
--- a/spelling.txt
+++ b/spelling.txt
@@ -107,3 +107,5 @@ Memberof
unmutes
websocat
RTCPIP
+VAPI
+vapi
diff --git a/versioned_docs/version-0.26.0/api/server-python/fishjam/index.md b/versioned_docs/version-0.26.0/api/server-python/fishjam/index.md
index 485b15ad..3e97ad8c 100644
--- a/versioned_docs/version-0.26.0/api/server-python/fishjam/index.md
+++ b/versioned_docs/version-0.26.0/api/server-python/fishjam/index.md
@@ -355,14 +355,14 @@ Options specific to the VAPI peer
Attributes:
- api_key (str): VAPI API key
- call_id (str): VAPI call ID
-- subscribe_mode (Union[Unset, SubscribeMode]): Configuration of peer's subscribing policy
+- subscribe_mode (SubscribeMode | Unset): Configuration of peer's subscribing policy
### __init__
```python
def __init__(
api_key: str,
call_id: str,
- subscribe_mode: Union[Unset, SubscribeMode] =
+ subscribe_mode: SubscribeMode | Unset =
)
```
Method generated by attrs for class PeerOptionsVapi.
@@ -381,7 +381,7 @@ call_id: str
### subscribe_mode
```python
-subscribe_mode: Union[Unset, SubscribeMode]
+subscribe_mode: SubscribeMode | Unset
```
@@ -575,18 +575,18 @@ Describes peer status
Attributes:
- id (str): Assigned peer id Example: 4a1c1164-5fb7-425d-89d7-24cdb8fff1cf.
-- metadata (Union['PeerMetadata', None]): Custom metadata set by the peer Example: \{'name': 'FishjamUser'\}.
+- metadata (None | PeerMetadata): Custom metadata set by the peer Example: \{'name': 'FishjamUser'\}.
- status (PeerStatus): Informs about the peer status Example: disconnected.
- subscribe_mode (SubscribeMode): Configuration of peer's subscribing policy
- subscriptions (Subscriptions): Describes peer's subscriptions in manual mode
-- tracks (list['Track']): List of all peer's tracks
+- tracks (list[Track]): List of all peer's tracks
- type_ (PeerType): Peer type Example: webrtc.
### __init__
```python
def __init__(
id: str,
- metadata: Optional[PeerMetadata],
+ metadata: None | PeerMetadata,
status: PeerStatus,
subscribe_mode: SubscribeMode,
subscriptions: Subscriptions,
@@ -604,7 +604,7 @@ id: str
### metadata
```python
-metadata: Optional[PeerMetadata]
+metadata: None | PeerMetadata
```
diff --git a/versioned_docs/version-0.26.0/api/server-python/fishjam/room/index.md b/versioned_docs/version-0.26.0/api/server-python/fishjam/room/index.md
index 79d1f076..9d2a35af 100644
--- a/versioned_docs/version-0.26.0/api/server-python/fishjam/room/index.md
+++ b/versioned_docs/version-0.26.0/api/server-python/fishjam/room/index.md
@@ -15,52 +15,52 @@ class RoomConfig:
Room configuration
Attributes:
-- max_peers (Union[None, Unset, int]): Maximum amount of peers allowed into the room Example: 10.
-- public (Union[Unset, bool]): True if livestream viewers can omit specifying a token. Default: False.
-- room_type (Union[Unset, RoomType]): The use-case of the room. If not provided, this defaults to conference.
-- video_codec (Union[Unset, VideoCodec]): Enforces video codec for each peer in the room
-- webhook_url (Union[None, Unset, str]): URL where Fishjam notifications will be sent Example:
+- max_peers (int | None | Unset): Maximum amount of peers allowed into the room Example: 10.
+- public (bool | Unset): True if livestream viewers can omit specifying a token. Default: False.
+- room_type (RoomType | Unset): The use-case of the room. If not provided, this defaults to conference.
+- video_codec (VideoCodec | Unset): Enforces video codec for each peer in the room
+- webhook_url (None | str | Unset): URL where Fishjam notifications will be sent Example:
https://backend.address.com/fishjam-notifications-endpoint.
### __init__
```python
def __init__(
- max_peers: Union[NoneType, Unset, int] = ,
- public: Union[Unset, bool] = False,
- room_type: Union[Unset, RoomType] = ,
- video_codec: Union[Unset, VideoCodec] = ,
- webhook_url: Union[NoneType, Unset, str] =
+ max_peers: int | None | Unset = ,
+ public: bool | Unset = False,
+ room_type: RoomType | Unset = ,
+ video_codec: VideoCodec | Unset = ,
+ webhook_url: None | str | Unset =
)
```
Method generated by attrs for class RoomConfig.
### max_peers
```python
-max_peers: Union[NoneType, Unset, int]
+max_peers: int | None | Unset
```
### public
```python
-public: Union[Unset, bool]
+public: bool | Unset
```
### room_type
```python
-room_type: Union[Unset, RoomType]
+room_type: RoomType | Unset
```
### video_codec
```python
-video_codec: Union[Unset, VideoCodec]
+video_codec: VideoCodec | Unset
```
### webhook_url
```python
-webhook_url: Union[NoneType, Unset, str]
+webhook_url: None | str | Unset
```
diff --git a/versioned_docs/version-0.26.0/integrations/vapi-integration.mdx b/versioned_docs/version-0.26.0/integrations/vapi-integration.mdx
new file mode 100644
index 00000000..397bed25
--- /dev/null
+++ b/versioned_docs/version-0.26.0/integrations/vapi-integration.mdx
@@ -0,0 +1,191 @@
+---
+sidebar_position: 2
+title: VAPI Integration
+description: Add a VAPI voice AI agent to a Fishjam room with a single API call.
+---
+
+import Tabs from "@theme/Tabs";
+import TabItem from "@theme/TabItem";
+
+# VAPI Integration
+
+:::info
+This tutorial requires a working Fishjam backend. If you haven't set one up yet, please check the [Backend Quick Start](../tutorials/backend-quick-start).
+:::
+
+This guide shows how to add a [VAPI](https://vapi.ai/) voice AI agent to a Fishjam room.
+Unlike custom agent integrations (such as [Gemini](./gemini-live-integration)), there is no need to build a WebSocket bridge yourself —
+Fishjam connects to VAPI internally. You only need to create a VAPI call and pass its ID to Fishjam.
+
+## Overview
+
+The workflow has two steps:
+
+1. **Create a VAPI call** using the VAPI SDK. This gives you a `callId`.
+2. **Pass the call ID to Fishjam** via `createVapiAgent()` / `create_vapi_agent()`. Fishjam joins the call and streams audio to and from the room.
+
+:::note
+The VAPI peer receives audio from only one peer at a time, so this integration works best in 1-on-1 calls. All peers in the room hear VAPI's responses.
+:::
+
+## Prerequisites
+
+You will need:
+
+- **Fishjam Server Credentials:** `fishjamId` and `managementToken`. You can get them at [fishjam.io/app](https://fishjam.io/app).
+- **VAPI Private API Key:** Obtainable from the [VAPI Dashboard](https://dashboard.vapi.ai/).
+- **VAPI Assistant ID** _(optional)_**:** Create an assistant in the [VAPI Dashboard](https://dashboard.vapi.ai/), or provide a transient assistant configuration inline.
+
+### Installation
+
+
+
+
+ ```bash
+ npm install @fishjam-cloud/js-server-sdk @vapi-ai/server-sdk
+ ```
+
+
+
+
+
+ ```bash
+ pip install fishjam-server-sdk vapi
+ ```
+
+
+
+
+## Implementation
+
+### Step 1: Create a VAPI Call
+
+Use the VAPI SDK to create a call with `vapi.websocket` transport and `pcm_s16le` audio at `16000` Hz.
+You can reference an existing assistant by its ID, or create a transient assistant inline by providing the full configuration via the `assistant` field instead of `assistantId`.
+
+
+
+
+ ```ts
+ import { VapiClient, Vapi } from '@vapi-ai/server-sdk';
+
+ const vapiClient = new VapiClient({
+ token: process.env.VAPI_API_KEY!,
+ });
+
+ // [!code highlight:11]
+ const call = await vapiClient.calls.create({
+ assistantId: process.env.VAPI_ASSISTANT_ID!,
+ transport: {
+ provider: 'vapi.websocket',
+ audioFormat: {
+ format: 'pcm_s16le',
+ container: 'raw',
+ sampleRate: 16000,
+ },
+ },
+ }) as Vapi.Call;
+ ```
+
+
+
+
+
+ ```python
+ import os
+ from vapi import Vapi
+
+ vapi_client = Vapi(token=os.environ["VAPI_API_KEY"])
+
+ # [!code highlight:11]
+ call = vapi_client.calls.create(
+ assistant_id=os.environ["VAPI_ASSISTANT_ID"],
+ transport={
+ "provider": "vapi.websocket",
+ "audioFormat": {
+ "format": "pcm_s16le",
+ "container": "raw",
+ "sampleRate": 16000,
+ },
+ },
+ )
+ ```
+
+
+
+
+### Step 2: Add the VAPI Peer to Fishjam
+
+Pass the call ID and your VAPI API key to Fishjam. Fishjam handles the rest.
+
+
+
+
+ ```ts
+ import { VapiClient, Vapi } from '@vapi-ai/server-sdk';
+
+ const vapiClient = new VapiClient({
+ token: process.env.VAPI_API_KEY!,
+ });
+
+ const call = await vapiClient.calls.create({
+ assistantId: process.env.VAPI_ASSISTANT_ID!,
+ transport: {
+ provider: 'vapi.websocket',
+ audioFormat: {
+ format: 'pcm_s16le',
+ container: 'raw',
+ sampleRate: 16000,
+ },
+ },
+ }) as Vapi.Call;
+
+ // ---cut---
+ import { FishjamClient } from '@fishjam-cloud/js-server-sdk';
+
+ const fishjamClient = new FishjamClient({
+ fishjamId: process.env.FISHJAM_ID!,
+ managementToken: process.env.FISHJAM_TOKEN!,
+ });
+
+ const room = await fishjamClient.createRoom();
+
+ // [!code highlight:4]
+ await fishjamClient.createVapiAgent(room.id, {
+ callId: call.id,
+ apiKey: process.env.VAPI_API_KEY!,
+ });
+ ```
+
+
+
+
+
+ ```python
+ import os
+ from fishjam import FishjamClient, PeerOptionsVapi
+
+ fishjam_client = FishjamClient(
+ fishjam_id=os.environ["FISHJAM_ID"],
+ management_token=os.environ["FISHJAM_TOKEN"],
+ )
+
+ room = fishjam_client.create_room()
+
+ # [!code highlight:2]
+ options = PeerOptionsVapi(api_key=os.environ["VAPI_API_KEY"], call_id=call.id)
+ peer = fishjam_client.create_vapi_agent(room.id, options)
+ ```
+
+
+
+
+## That's it
+
+Once both steps are complete, the VAPI assistant is live in the room — peers can speak to it and hear its responses.
+
+The VAPI peer's lifetime is tied to the underlying WebSocket connection. If there is a prolonged period of silence or the call ends for any other reason, the peer will disconnect and be removed from the room. You can detect this by listening for `PeerDisconnected` and `PeerDeleted` server notifications. If the peer crashes (e.g. due to an invalid transport configuration or call ID), a `PeerCrashed` notification is sent instead. See [Listening to events](../how-to/backend/server-setup#listening-to-events) for how to subscribe to these notifications.
+
+## Billing
+
+The VAPI peer is billed as a single Fishjam peer. VAPI's own usage pricing applies separately — see [VAPI pricing](https://vapi.ai/pricing) for details.
diff --git a/yarn.lock b/yarn.lock
index de09dec8..32743bc2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7823,6 +7823,13 @@ __metadata:
languageName: node
linkType: hard
+"@vapi-ai/server-sdk@npm:^1.1.0":
+ version: 1.1.0
+ resolution: "@vapi-ai/server-sdk@npm:1.1.0"
+ checksum: 10c0/72eaa4e099455e1a7567c4cb69b34322e6aca21860a3ae6688f45babc0f578bd1dd6c76a3ae2bd4ddc8a8ac63361a0daa5b8a07a3c560ed2b0d880683bb6b0d9
+ languageName: node
+ linkType: hard
+
"@webassemblyjs/ast@npm:1.14.1, @webassemblyjs/ast@npm:^1.14.1":
version: 1.14.1
resolution: "@webassemblyjs/ast@npm:1.14.1"
@@ -11901,6 +11908,7 @@ __metadata:
"@swmansion/smelter-web-wasm": "npm:^0.2.1"
"@types/react": "npm:^19.1.8"
"@types/react-dom": "npm:^19.1.6"
+ "@vapi-ai/server-sdk": "npm:^1.1.0"
clsx: "npm:^2.0.0"
cspell: "npm:^9.1.2"
docusaurus-lunr-search: "npm:3.6.0"