Skip to main content

Overview

vaani_flutter_sdk dispatches a Vaani agent through the Vaani External API, then joins the room the agent was sent to over WebRTC. You give it an API key and an agent ID. It is a Dart port of the WebRTC and dispatch parts of the Python SDK, with the same endpoints, field names and error types. Source: VaaniResearchLab/vaani-flutter-sdk. Included: call state, live transcripts, mute, speaking indicators, echo cancellation and noise suppression, and typed errors.

Installation

The package is not on pub.dev yet. Add it as a git dependency:
Or, with a local checkout, as a path dependency:
Requirements: Flutter 3.38+, Dart 3.10+ (required by the WebRTC dependency), iOS 13.0+, Android minSdk 21+.
This package runs the live call in your app. Call history, transcripts, recordings, campaigns and agent settings are handled through the Vaani portal or your backend. See Call History, Transcripts and Agent Settings.

iOS

Add to ios/Runner/Info.plist:
UIBackgroundModes: audio keeps the call running when the app goes to the background. Leave it out if the call should stop when the user leaves the app.

Android

Add to android/app/src/main/AndroidManifest.xml:
The SDK asks for the microphone permission when the mic is first enabled.

Authentication

Every request sends your API key as the X-API-Key header. Generate a key from platform.vaani.dev → Settings → API Keys. The key is shown only once.
Call close() when you are done to free the HTTP connection. If you passed your own httpClient, close() leaves it open.
You don’t need a VaaniClient for a normal call: VaaniVoiceSession.withApiKey creates one and closes it in dispose().
A string compiled into an iOS or Android binary can be extracted. An API key in the app is fine for prototypes and internal builds. For production, see Keep the API key off the device.

Quickstart

VaaniVoiceSession is a ChangeNotifier, so it works with ListenableBuilder, Provider or Riverpod:
A session is single-use: calling start() twice throws a StateError. Create a new VaaniVoiceSession for each conversation. The example app has a full call screen.

Voice Sessions

Creating a Session

VaaniVoiceSession.withApiKey(...) parameters: The VaaniVoiceSession(...) constructor takes credentialsProvider (required), roomOptions, enableMicrophone, audioOptions and agentJoinTimeout with the same defaults, plus:

Lifecycle

start() completes once the room is joined, not when the agent arrives. Watch session.state: The session ends by itself when the agent leaves the room, when the room disconnects, or (session flow) when Vaani sends session_ended. It moves to failed if no agent joins within agentJoinTimeout or Vaani sends agent_timeout.

Session Properties

isUserSpeaking and userAudioLevel are computed on the server, so they also prove the user’s audio is arriving. Use them to debug “the agent can’t hear me”.

Microphone

Live Transcripts

transcripts is a broadcast stream. Subscribe before start() to catch the agent’s greeting. If the agent sends each transcript over two channels, the SDK keeps one and drops the duplicate.

Using the Room Directly

session.room is the underlying WebRTC room. Use it for anything the session doesn’t wrap, such as audio tracks, RPC, data messages and custom events. If you add your own listeners to it, dispose them yourself; the session only cleans up its own.

Dispatch Flows

“Dispatch” means asking Vaani to send the agent into a room and getting back credentials to join it. There are two ways.
One request: POST /api/trigger-call/ with medium: "webrtc". Use it for per-call voice, language or prompt overrides. end() leaves the room.

trigger-call Options

Fields left null are not sent, so the server default applies. bgNoiseVolume and voiceSpeed outside their ranges fail an assert in debug builds and are rejected by the API with 422. ModifyAgentConfig(persona:, training:, experience:, analysis:) takes one map per section, in the same shape as the agent configuration. Sections you leave out are unchanged, and the override applies to this call only.

Session Flow

1

Create a session

session/create returns a sessionToken and a shareable sessionUrl.
2

Connect and get room credentials

session/connect exchanges the session token for the server URL, participant token and room name. Vaani dispatches the agent at this point.
3

Join the room

The SDK joins the room, enables the mic and subscribes to session events.
4

End the session

end() calls session/disconnect. With explicitEnd: true (the default) the session ends now; with false it stays resumable for gracePeriod.
VaaniVoiceSession.withApiKey(useSessionFlow: true, ...) runs all four steps:
withApiKey always uses ui_type: "fullpage". To set widget, build the provider yourself:
gracePeriod and sessionTtl outside their ranges are rejected with 422 (VaaniValidationException). Once a session has ended or expired, session calls fail with 410 (VaaniSessionExpiredException).

Real-Time Session Events

In the session flow, session.sessionEvents streams events from the session WebSocket:
The session-events socket is public, like connect and status: the session token in the URL is the credential. A wrong or ended token is refused, and the error arrives on sessionEvents. For live call transcripts use session.transcripts, not this socket.

Keep the API Key Off the Device

withApiKey is fine for prototypes and internal builds. In production, keep the key on your backend: the backend dispatches the agent and returns only the room credentials. Backend (Python, with the Python SDK):
App:
If your backend uses the session flow, subclass VaaniCredentialsProvider and override release() so the session ends with the call:
release() runs once when the call ends, whether through end(), an error, the agent leaving or dispose(). Errors it throws are ignored, because Vaani expires sessions on its own. VaaniRoomCredentials:

Echo Cancellation and Noise Suppression

VaaniVoiceSession sets the microphone’s voice processing explicitly through audioOptions, so a dependency upgrade cannot change it silently. By default everything is on.
VaaniAudioOptions.raw() turns everything off, for raw capture or your own audio pipeline.
The processing modes wrap an API that the WebRTC library marks experimental; the on/off flags are stable. Judge echo cancellation on a real device on speakerphone, since simulators and emulators cannot show it. On Android, EnableBuiltInAEC(1) and EnableBuiltInNS(1) in logcat confirm the hardware effects are on.

Lower-Level API

VaaniClient calls the dispatch and session endpoints directly, without joining a room.

Dispatch a WebRTC Call

triggerWebRtcCall and triggerTelephonyCall return a TriggerCallResponse: token, roomName, connectionUrl and liveCaptionsUrl for WebRTC; callId, roomName, liveCaptionsUrl, agentName, dispatchId and callDbId for telephony. The full response is in .raw. dispatchWebRtc throws a VaaniException if the response has no token, room name or URL.

Outbound Phone Calls

The same endpoint places phone calls. This usually belongs on a backend, but it is available:

Session Endpoints

client.sessions.createAndConnect(...) takes the same parameters as create and returns VaaniRoomCredentials with sessionToken set. WebRtcSessionStatus (from get and disconnect) has status, expiresAt, uiType, roomName, agentId, clientId, metadata and gracePeriodEndsAt.

Other Endpoints

client.request(method, path, body:, query:) sends an authenticated JSON request to any endpoint the SDK doesn’t wrap, with the same error mapping:

Error Handling

Every error extends VaaniException, which carries message (usually the API’s detail) and statusCode.
start() throws on dispatch or connection failures. Failures after that (such as the agent timeout) don’t throw: state becomes failed and session.error holds the exception, so check it in your listener.

Troubleshooting

When you contact Vaani support, include session.roomName (e.g. webrtc-1794481532-5effcd48). It identifies the call.

Call History, Transcripts and Agent Settings

The Flutter SDK runs the live call. Everything around the call is done in the Vaani portal or from your backend: For example, your backend can fetch a call’s transcript and summary after the app reports its roomName:
These features need your API key, so keep them on your backend, not in the app. For a one-off call to another endpoint, client.request(method, path, body:, query:) is available.