From 061c9fc1b1787a9c6e7820599798e2ee6a91dc36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?= Date: Mon, 13 Apr 2026 13:27:25 +0200 Subject: [PATCH 1/4] Add vapi integration --- docs/integrations/vapi-integration.mdx | 195 ++++++++++++++++++ package.json | 1 + spelling.txt | 2 + .../api/server-python/fishjam/index.md | 14 +- .../api/server-python/fishjam/room/index.md | 30 +-- .../integrations/vapi-integration.mdx | 195 ++++++++++++++++++ yarn.lock | 8 + 7 files changed, 423 insertions(+), 22 deletions(-) create mode 100644 docs/integrations/vapi-integration.mdx create mode 100644 versioned_docs/version-0.26.0/integrations/vapi-integration.mdx diff --git a/docs/integrations/vapi-integration.mdx b/docs/integrations/vapi-integration.mdx new file mode 100644 index 00000000..be685156 --- /dev/null +++ b/docs/integrations/vapi-integration.mdx @@ -0,0 +1,195 @@ +--- +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 +Single-listener limitation +The VAPI peer listens to only one participant at a time. When that participant disconnects, VAPI automatically connects to another peer in the room. All peers 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 + + fishjam_client = FishjamClient( + fishjam_id=os.environ["FISHJAM_ID"], + management_token=os.environ["FISHJAM_TOKEN"], + ) + + room = fishjam_client.create_room() + + # [!code highlight:4] + fishjam_client.create_vapi_agent( + room.id, + call_id=call.id, + api_key=os.environ["VAPI_API_KEY"], + ) + ``` + + + + +## 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..9beaea82 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@swmansion/smelter-web-wasm": "^0.2.1", "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", + "@vapi-ai/server-sdk": "^1.1.0", "clsx": "^2.0.0", "docusaurus-lunr-search": "3.6.0", "expo-camera": "^16.1.8", 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..be685156 --- /dev/null +++ b/versioned_docs/version-0.26.0/integrations/vapi-integration.mdx @@ -0,0 +1,195 @@ +--- +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 +Single-listener limitation +The VAPI peer listens to only one participant at a time. When that participant disconnects, VAPI automatically connects to another peer in the room. All peers 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 + + fishjam_client = FishjamClient( + fishjam_id=os.environ["FISHJAM_ID"], + management_token=os.environ["FISHJAM_TOKEN"], + ) + + room = fishjam_client.create_room() + + # [!code highlight:4] + fishjam_client.create_vapi_agent( + room.id, + call_id=call.id, + api_key=os.environ["VAPI_API_KEY"], + ) + ``` + + + + +## 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" From 1048fae2fc1517801ee6b87321c7200af3aba8d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?= Date: Mon, 13 Apr 2026 13:41:56 +0200 Subject: [PATCH 2/4] Move vapi to dev deps --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9beaea82..07af34ef 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,6 @@ "@swmansion/smelter-web-wasm": "^0.2.1", "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", - "@vapi-ai/server-sdk": "^1.1.0", "clsx": "^2.0.0", "docusaurus-lunr-search": "3.6.0", "expo-camera": "^16.1.8", @@ -59,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", From 308acc03b2bedb2ccb71591d25a26337b720e457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?= Date: Mon, 13 Apr 2026 14:03:48 +0200 Subject: [PATCH 3/4] Fix python code --- docs/integrations/vapi-integration.mdx | 11 ++++------- .../version-0.26.0/integrations/vapi-integration.mdx | 11 ++++------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/docs/integrations/vapi-integration.mdx b/docs/integrations/vapi-integration.mdx index be685156..65fe5a3a 100644 --- a/docs/integrations/vapi-integration.mdx +++ b/docs/integrations/vapi-integration.mdx @@ -164,7 +164,7 @@ Pass the call ID and your VAPI API key to Fishjam. Fishjam handles the rest. ```python import os - from fishjam import FishjamClient + from fishjam import FishjamClient, PeerOptionsVapi fishjam_client = FishjamClient( fishjam_id=os.environ["FISHJAM_ID"], @@ -173,12 +173,9 @@ Pass the call ID and your VAPI API key to Fishjam. Fishjam handles the rest. room = fishjam_client.create_room() - # [!code highlight:4] - fishjam_client.create_vapi_agent( - room.id, - call_id=call.id, - api_key=os.environ["VAPI_API_KEY"], - ) + # [!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) ``` diff --git a/versioned_docs/version-0.26.0/integrations/vapi-integration.mdx b/versioned_docs/version-0.26.0/integrations/vapi-integration.mdx index be685156..65fe5a3a 100644 --- a/versioned_docs/version-0.26.0/integrations/vapi-integration.mdx +++ b/versioned_docs/version-0.26.0/integrations/vapi-integration.mdx @@ -164,7 +164,7 @@ Pass the call ID and your VAPI API key to Fishjam. Fishjam handles the rest. ```python import os - from fishjam import FishjamClient + from fishjam import FishjamClient, PeerOptionsVapi fishjam_client = FishjamClient( fishjam_id=os.environ["FISHJAM_ID"], @@ -173,12 +173,9 @@ Pass the call ID and your VAPI API key to Fishjam. Fishjam handles the rest. room = fishjam_client.create_room() - # [!code highlight:4] - fishjam_client.create_vapi_agent( - room.id, - call_id=call.id, - api_key=os.environ["VAPI_API_KEY"], - ) + # [!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) ``` From bfa400a7fb6e1226213a28a2808afcfb9611cadc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Ro=C5=BCnawski?= Date: Mon, 13 Apr 2026 15:29:28 +0200 Subject: [PATCH 4/4] Recommend 1-on-1 usage --- docs/integrations/vapi-integration.mdx | 3 +-- .../version-0.26.0/integrations/vapi-integration.mdx | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/integrations/vapi-integration.mdx b/docs/integrations/vapi-integration.mdx index 65fe5a3a..397bed25 100644 --- a/docs/integrations/vapi-integration.mdx +++ b/docs/integrations/vapi-integration.mdx @@ -25,8 +25,7 @@ The workflow has two steps: 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 -Single-listener limitation -The VAPI peer listens to only one participant at a time. When that participant disconnects, VAPI automatically connects to another peer in the room. All peers hear VAPI's responses. +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 diff --git a/versioned_docs/version-0.26.0/integrations/vapi-integration.mdx b/versioned_docs/version-0.26.0/integrations/vapi-integration.mdx index 65fe5a3a..397bed25 100644 --- a/versioned_docs/version-0.26.0/integrations/vapi-integration.mdx +++ b/versioned_docs/version-0.26.0/integrations/vapi-integration.mdx @@ -25,8 +25,7 @@ The workflow has two steps: 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 -Single-listener limitation -The VAPI peer listens to only one participant at a time. When that participant disconnects, VAPI automatically connects to another peer in the room. All peers hear VAPI's responses. +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