PolyVoice places live, two-way WebRTC voice calls to a PolyAI agent — the companion to the iOS SDK’s chat product. It ships as a separate product/pod so chat-only apps never link the WebRTC binary, and it reuses the messaging Configuration plus the same CallState / PolyError vocabulary, so there are no new concepts if you already run chat.
Calls are user-initiated: the user taps to call your agent. Inbound (push-triggered) calls are not supported.
Source on GitHub
polyai/ios-sdk — includes the full PolyVoice technical guide and runnable voice example apps.
Installation
The SDK is pre-1.0, so pin to the next minor version — a minor bump is allowed to include breaking changes.- Swift Package Manager
- CocoaPods
Add the package and depend on the In Xcode, this means ticking both the PolyMessaging and PolyVoice libraries for your app target when adding the package.
PolyVoice product (alongside PolyMessaging):PolyVoice transitively pulls the WebRTC xcframework; PolyMessaging stays source-only. With CocoaPods the WebRTC dependency lives solely in PolyVoice, so a chat-only install pulls nothing extra.
Quickstart
CallPolyVoice.call from the main actor (it won’t compile otherwise):
CallState, PolyError, and Configuration are the same types from PolyMessaging.
Credentials
A voice call needs two credentials, both on your agent in Agent Studio › Connector Settings (the same connector you use for chat):Region: calls default to the US gateway. For a UK / EUW / other-region agent, set the environment on the shared
Configuration — e.g. Configuration(apiKey: …, environment: .cluster("…")) — the same Configuration you use for chat.Custom / self-hosted gateway: pass VoiceOptions(webrtcToken:, signalingHost:) to point at a specific gateway host (required when the environment is .custom).Microphone permission
A call needs the microphone. AddNSMicrophoneUsageDescription to your app’s Info.plist — a call without it crashes on iOS, it does not fail gracefully. The system prompts the user on the first call; the SDK activates the AVAudioSession for you (under CallKit, the system activates it instead — the permission requirement is unchanged).
Backgrounding
To keep a call running while your app is in the background (the norm for a voice call), enable theaudio background mode in your Info.plist:
voip background mode alongside audio.
Audio routing
The call is accessory-aware by default: a connected wired or Bluetooth headset is used automatically (and followed if connected or removed mid-call); otherwise it falls back to the loudspeaker. SetVoiceOptions(speakerphone: false) to fall back to the earpiece instead.
iOS keeps one active output and routes accessories for you, so the output an app reliably controls is speaker ↔ earpiece. Observe the live route via call.audioStates and switch with call.setAudioDevice(_:):
availableDevices also lists connected headsets and Bluetooth devices (.kind is .earpiece / .speakerphone / .wiredHeadset / .bluetooth) for display. To let users pick among connected outputs the iOS-standard way, drop in the system route picker (AVRoutePickerView).
CallKit
Opt in withVoiceOptions(callKit: true) to run a call as a system call: the green in-call indicator, lock-screen / AirPods / car-Bluetooth controls, phone-call audio priority, and hold arbitration when a cellular call arrives.
In this mode the SDK never activates or deactivates the audio session itself — CallKit does — and your CXProviderDelegate must forward three moments to the SDK. Without this forwarding, the call connects but has no sound.
- Declare the
voipbackground mode (alongsideaudio) inUIBackgroundModes. Without it everyCXCallControllertransaction is refused (com.apple.CallKit.error.requesttransaction Code=1) and the call never starts. - Request, don’t command: start / end / mute go through
CXCallControlleractions and are executed in the matchingperformcallback, so the system can arbitrate and the system UI stays in sync. Remote endings (the agent hangs up, a failure) are reported viareportCall(with:endedAt:reason:)instead. - Never call
AVAudioSession.setActive(true)during a CallKit call — a self-activated session blocks CallKit’s elevated activation anddidActivatenever fires (the classic “call connects, no audio” bug). - System interruptions move to CallKit: a cellular call arrives as a hold action plus
didDeactivate, not as the SDK’s interruption handling. After the interrupting call ends, iOS may not resume you automatically — offer a manual un-hold path. - Simulator: CallKit is broken there (iOS 17+ auto-ends calls). Gate on
targetEnvironment(simulator)and fall back to a plain call. - China: Apple rejects CallKit UI for the Chinese App Store. Keep
callKit:behind a region or remote-config gate if you ship there.
Resilience
- Connectivity: STUN/TURN servers are fetched from the gateway per call, so calls connect behind symmetric NAT / CGNAT (falls back to public STUN if the fetch fails).
- Reconnect: a dropped signaling socket reconnects automatically (backoff 1s / 2s / 4s) on the same session before the call is failed.
- Interruptions: an incoming phone call or Siri mutes the mic and restores it; a non-resumable interruption ends the call as
PolyError.voice(.interrupted). - Retryable errors: a post-connect drop surfaces as
PolyError.voice(.disconnected). Both it and.interruptedareisRetryable, so you can offer a one-tap retry.
Troubleshooting
Architecture
PolyVoice provides a real CallMediaEngine (an RTCPeerConnection audio engine) and an AVAudioSession controller, injected into the existing PolyMessaging CallCoordinator via PolyCall.wired(config:webrtcToken:signalingHost:mediaEngine:) (SPI — @_spi(PolyVoice), not public API). The signaling pipeline (auth → session → link → signaling → offer/answer/ICE) lives in PolyMessaging and is exercised end-to-end by its test suite.
Example apps
A one-screen tap-to-call demo ships in both toolkits — drop your connector token and web calling token into thePolyVoice.call(...) block and run:
The 02-CallKit examples encode all of the CallKit rules above.
Related pages
iOS SDK
Chat with PolyMessaging: installation, authentication, sessions, and UI
Multichannel agents
Build agents that work across voice, webchat, and mobile

