Skip to main content
The Android SDK is a native Kotlin library that embeds PolyAI’s messaging agent directly inside your Android app. It’s headless by design — you own the UI, PolyAI provides the AI layer underneath. Your app connects to the same agent logic used across voice, webchat, and other channels.
The Android SDK uses the Messaging API under the hood. All WebSocket events, streaming, and handoff behavior documented in the API reference apply.

How it works

The SDK handles authentication, session management, WebSocket connections, and reconnection logic. Your app sends and receives messages through the SDK and renders them however you choose — in Jetpack Compose or Android Views.
1

Install the SDK

Add the SDK to your project via Maven Central.
2

Configure authentication

Add your API key (from Agent Studio) and ensure your app’s package name (applicationId) matches the host registered in Agent Studio for your API key.
3

Initialize and start a session

Initialize the SDK once in Application.onCreate(), then call PolyMessaging.chat() to get a ChatSession. The SDK handles access token exchange and WebSocket connection automatically.
4

Build your UI

Observe ChatSession state via Kotlin StateFlow — collect messages, connection status, typing indicators, and more. Render the conversation in your own UI components.

Installation

The SDK is published to Maven Central as ai.poly:messaging. Ensure mavenCentral() is in your repositories (it’s there by default in new Android projects).

Requirements

RequirementMinimum
AndroidAPI 24 (Android 7.0)
compileSdk36
Kotlin2.2+
JDK (to build)17
Java consumersSupported
No permissions to declare — the SDK’s manifest merges INTERNET and ACCESS_NETWORK_STATE into your app automatically. R8/minify works out of the box.

Authentication setup

The Android SDK authenticates using a connector token and your app’s package name.
1

Generate a connector token

In Agent Studio, go to Messaging > API Configuration and generate a new Messaging API key.
2

Register your package name

Your app’s applicationId is sent as the X-Host header. It must match the host registered in Agent Studio for your API key.
Never embed the connector token directly in your app binary. Use a backend service to mint short-lived access tokens and pass them to the SDK. See security best practices.

Quick start

Initialize the SDK once in Application.onCreate(), then create a ChatSession and render messages.

Initialize once

// HelloApplication.kt
import ai.poly.messaging.Configuration
import ai.poly.messaging.PolyMessaging
import android.app.Application

class HelloApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        PolyMessaging.initialize(
            this,
            Configuration(apiKey = "YOUR_API_KEY"),
        )
    }
}
Register it in your manifest with android:name=".HelloApplication". No network happens at init — the work starts when you call chat().

Build the chat UI

// MainActivity.kt
import ai.poly.messaging.ChatMessage
import ai.poly.messaging.ChatSession
import ai.poly.messaging.PolyMessaging
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.coroutines.launch

@Composable
fun ChatScreen() {
    val session: ChatSession = remember { PolyMessaging.chat() }
    val messages by session.messages.collectAsStateWithLifecycle()
    val scope = rememberCoroutineScope()
    var input by remember { mutableStateOf("") }

    Column(Modifier.fillMaxSize().imePadding()) {
        LazyColumn(Modifier.weight(1f)) {
            items(messages, key = { it.id }) { message ->
                val mine = message is ChatMessage.User
                Box(Modifier.fillMaxWidth().padding(vertical = 4.dp, horizontal = 8.dp)) {
                    Text(
                        message.text ?: "",
                        Modifier.align(if (mine) Alignment.CenterEnd else Alignment.CenterStart),
                    )
                }
            }
        }
        Row(Modifier.padding(8.dp)) {
            TextField(value = input, onValueChange = { input = it }, modifier = Modifier.weight(1f))
            Button(onClick = {
                val body = input.trim()
                if (body.isNotEmpty()) {
                    input = ""
                    scope.launch { runCatching { session.send(body) } }
                }
            }) { Text("Send") }
        }
    }
}

Key features

Session persistence

Sessions persist across app launches. If the user leaves and returns, the SDK reconnects to the existing session and replays the conversation history automatically. Use PolyMessaging.chat() to resume or PolyMessaging.start() to always start fresh.
val session = if (PolyMessaging.hasResumableSession()) {
    PolyMessaging.chat()    // resume the previous conversation
} else {
    PolyMessaging.start()   // start a new one
}

Streaming responses

Streaming is on by default — agent replies grow token-by-token. The SDK reassembles chunks and updates session.messages automatically. To switch to complete-message bubbles, set streamingEnabled = false on the Configuration.
PolyMessaging.initialize(
    this,
    Configuration(apiKey = "YOUR_API_KEY", streamingEnabled = false),
)

Handoff to live agents

The full handoff flow is supported. When the PolyAI agent triggers a handoff, the SDK delivers the same handoff events via ChatMessage.System messages with typed SystemEvent cases (HandoffStarted, QueueStatus, LiveAgentJoined, etc.). Live agent messages arrive as ChatMessage.Agent with agentKind == AgentKind.LIVE.

Response suggestions

Agent messages can include suggestions — pre-written reply options. Render these as tappable chips in your UI. When the user taps one, call clearSuggestions(messageId) then send(suggestion.messageText).

Attachments

Agent messages may include rich content via the attachments field — images (AttachmentContentType.IMAGE), link cards (AttachmentContentType.URL), and call-to-action phone buttons (callActions).

Delivery tracking

User messages appear immediately as Delivery.PENDING, then settle to SENT or FAILED. The SDK retries automatically (every 3s, up to 3 times). On FAILED, call removeMessage(draftId) then re-send.

Connection & reconnect

The SDK reconnects automatically with exponential backoff and jitter. Observe session.connection to show a reconnect banner:
// StateFlow<ConnectionStatus> — Connecting / Open / Reconnecting(attempt) / Failed(reason)
session.connection.collect { status ->
    showBanner = status is ConnectionStatus.Reconnecting
}
When the reconnect budget is exhausted (ConnectionStatus.Failed), recover with session.client.startNewSession().

Configuration reference

FieldDefaultDescription
apiKey— (required)API key from Agent Studio
environmentEnvironment.USUS / UK / EUW / cluster("name") / custom(restUrl, wsUrl)
hostIdentifierpackage nameX-Host for connector validation
streamingEnabledtrueToken-by-token (true) or complete bubbles (false)
logLevelLogLevel.ERRORNONE / ERROR / WARN / INFO / DEBUG
maxReconnectAttempts10Reconnect budget before Failed

Environments

EnvironmentEndpoint
Environment.US (default)messaging.us-1.poly.ai
Environment.UKmessaging.uk-1.poly.ai
Environment.EUWmessaging.euw-1.poly.ai
Environment.cluster("name")messaging.<name>.poly.ai

ChatSession reference

State (read-only StateFlow properties)

PropertyTypeDescription
messagesStateFlow<List<ChatMessage>>Full transcript — ChatMessage.User / .Agent / .System
isReadyStateFlow<Boolean>Connected and ready to send
connectionStateFlow<ConnectionStatus>Socket state
isAgentTypingStateFlow<Boolean>Show typing indicator
agentAvatarUrlStateFlow<URI?>Latest agent avatar
hasEndedStateFlow<Boolean>Conversation is over
failureReasonStateFlow<PolyError?>Terminal failure (invalid key, reconnect exhausted, session expired)

Methods

MethodDescription
suspend send(text)Send a user message (optimistic)
suspend sendTyping()Broadcast typing (throttled internally)
suspend end()End the conversation
removeMessage(draftId)Drop a failed draft before re-sending
clearSuggestions(messageId)Clear quick-reply pills for a message
clearChat()Wipe the transcript
close()Tear down this session’s observers

Platform values

When the SDK creates a session, it sets platform to android automatically. This is visible in Agent Studio analytics and can be used in your agent logic to tailor behavior for mobile users.
Platform valueSource
androidAndroid SDK (native)
iosiOS SDK (native)
ios-webWebchat widget on iOS Safari
webWebchat widget on desktop

Limitations

These limitations apply to the initial release. Check the release notes for updates.
LimitationDetails
Text onlyVoice input is not supported in V1. WebRTC voice support is planned for a future release.
No push notificationsWhile the app is active, the SDK delivers messages in real time. When the app is killed by the system, no messages arrive. Local notifications work as a workaround while the process is alive. Remote push (FCM) is not yet supported.
No React Native or FlutterThe SDK is native Kotlin (Java-compatible). Cross-platform frameworks would require a separate SDK.

Example apps

The SDK ships with a 7-rung example ladder, mirrored across Jetpack Compose and Android Views:
LevelWhat it adds
01 HelloInitialize, render, send
02 StandardTyping, suggestions, delivery, reconnect, end + start-new
03 Rich ContentAttachments, link cards, tel: actions, Markdown
04 ResilienceOffline banner, loading skeleton, terminal error + retry
05 HandoffFull live-agent ladder
06 Full ReferenceProduction resume + start-new flows
07 PlaygroundDiagnostics, runtime config, streaming toggle
Browse the examples on GitHub.

Multichannel

The Android SDK connects to the same agent project as your voice and webchat channels. Agent behavior, knowledge, and flows are shared — only channel-specific settings (greetings, formatting) differ. See multichannel agents for how to tailor behavior per channel. In your agent’s start function, detect the mobile channel using conv.channel_type:
def start(conv):
    if conv.channel_type == "android":
        conv.state.greet_message = "Hi! How can I help you today?"
    elif conv.channel_type.startswith("webchat"):
        conv.state.greet_message = "Hi there! 👋 How can I help?"

Messaging API reference

Full WebSocket protocol, events, streaming, and handoff

Sessions and authentication

Access tokens, session creation, and platform values

Multichannel agents

Build agents that work across voice, webchat, and mobile

Best practices

Connection management, UX, and security recommendations
Last modified on June 26, 2026