VAPI Integration
This tutorial requires a working Fishjam backend. If you haven't set one up yet, please check the Backend Quick Start.
This guide shows how to add a VAPI voice AI agent to a Fishjam room. Unlike custom agent integrations (such as Gemini), 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:
- Create a VAPI call using the VAPI SDK. This gives you a
callId. - Pass the call ID to Fishjam via
createVapiAgent()/create_vapi_agent(). Fishjam joins the call and streams audio to and from the room.
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:
fishjamIdandmanagementToken. You can get them at fishjam.io/app. - VAPI Private API Key: Obtainable from the VAPI Dashboard.
- VAPI Assistant ID (optional): Create an assistant in the VAPI Dashboard, or provide a transient assistant configuration inline.
Installation
- TypeScript
- Python
npm install @fishjam-cloud/js-server-sdk @vapi-ai/server-sdk
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.
- TypeScript
- Python
import {VapiClient ,Vapi } from '@vapi-ai/server-sdk'; constvapiClient = newVapiClient ({token :process .env .VAPI_API_KEY !, }); constcall = awaitvapiClient .calls .create ({assistantId :process .env .VAPI_ASSISTANT_ID !,transport : {provider : 'vapi.websocket',audioFormat : {format : 'pcm_s16le',container : 'raw',sampleRate : 16000, }, }, }) asVapi .Call ;
import os from vapi import Vapi vapi_client = Vapi(token=os.environ["VAPI_API_KEY"]) 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.
- TypeScript
- Python
import {FishjamClient } from '@fishjam-cloud/js-server-sdk'; constfishjamClient = newFishjamClient ({fishjamId :process .env .FISHJAM_ID !,managementToken :process .env .FISHJAM_TOKEN !, }); constroom = awaitfishjamClient .createRoom (); awaitfishjamClient .createVapiAgent (room .id , {callId :call .id ,apiKey :process .env .VAPI_API_KEY !, });
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() 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 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 for details.