Skip to main content
The iOS SDK and the Android SDK put your agent inside your own app. The SDK handles the conversation: it streams the agent’s reply, keeps the history, signs users in and runs the actions your app provides. Your app draws the chat screen, with SwiftUI, UIKit or Jetpack Compose. Both SDKs, and an example app for each, are public on GitHub:

Before you start

1

Open the SDK channel

Open Channels in your agent’s sidebar. On the iOS SDK or Android SDK card, choose Setup (Manage once the channel is enabled). The channel opens on its Setup tab, next to Overview and Actions.
2

Copy the agent ID

Under Agent ID, choose Copy. You pass this ID when you create the client. The Quick start snippet on the same tab already contains it, together with the base URL of your Centillion host, which ends in /api/sdk.
3

Turn on Visibility

Open Overview, switch on Visibility and save. Until you do, the agent answers no messages from the app, and requests fail with AGENT_NOT_FOUND. The card on Channels shows Enabled once the platform is visible.
iOS and Android are separate channels with their own Visibility switch and their own action settings. Turn on each one your app ships on.

iOS

The iOS SDK needs iOS 17 or later (macOS 14 or later), Swift 6 and Xcode 16 or later.

Install the iOS package

In Xcode, choose File → Add Package Dependencies and paste the package URL:
Select version 0.1.0 or later and add CentillionSDK to your app target. For a Swift package, add the dependency to Package.swift instead:
Package.swift

Send a message from Swift

The client and its callbacks run on the main actor, so call it from an async function or a Task. onTextDelta receives the reply as it streams; onToolCall and onToolResult report the actions the agent runs. To continue a conversation, pass its ID: send("Tell me more", conversationId: response.conversationId). A send without a conversationId starts a new conversation. Cancel the calling Task to cancel the request.

On iOS

  • The signed-in user’s token is kept in the Keychain, readable only on this device. After the app restarts, the user is still signed in, but currentUserId stays empty until you call identify again.
  • The SDK accepts plain HTTP only for localhost, 127.0.0.1 and ::1. In the simulator, use http://localhost:3000/api/sdk against a local server. Remote servers need HTTPS.

Android

The Android SDK needs Android 7.0 or later (API 24), JDK 17 or later, and an Android Studio version that supports Android Gradle plugin 8.13.2. The SDK builds with Kotlin 2.2.20 and Gradle 9.2.1, and adds the INTERNET permission to your app.

Install the Android module

The SDK is installed from source. Clone it beside your project:
Then include its centillion-sdk module and depend on it. Use the Google and Maven Central repositories.

Send a message from Kotlin

Call the client’s suspend functions from a coroutine, such as viewModelScope. Its callbacks (onStart, onTextDelta, onToolCall, onToolResult, onFinish and onError) run on Dispatchers.Main. The Android client continues its current conversation by default. Pass another conversationId to switch, or call client.newConversation() to start a new one. Cancel the coroutine to cancel the request.

Raw event streams

sendMessage runs your app’s actions for you. If you need every event yourself, sendMessageStream and retryStream return a Flow<StreamEvent> with Start, TextDelta, ToolRequested, ToolCompleted and Finish. These flows do not run actions: for a ToolRequested event, call submitToolResult(conversationId, requestId, callId, output) yourself, unless the call’s execution is "server", which means Centillion already ran it.

On Android

  • The signed-in user’s token is kept in memory only, never in preferences. Call identify with a fresh token from your backend each time the app starts.
  • The SDK accepts plain HTTP only for localhost, 127.0.0.1, 10.0.2.2 and ::1. In the emulator, use http://10.0.2.2:3000/api/sdk against a local server. Remote servers need HTTPS.

Replies and retries

Every reply ends with a response that holds message, conversationId, userMessageId, finishReason and usage, which counts the credits, inputTokens and outputTokens the reply used. sendNonStreaming waits for the whole reply and returns the same response, running your app’s actions along the way. retry takes a conversation ID and the ID of one of the agent’s answers, and asks the agent to answer again. The saved conversation changes only when the new answer succeeds: the answer and every message after it are replaced. If the retry fails, the conversation stays as it was. When the stream ends before the reply finishes, the request fails with INCOMPLETE_STREAM. A lost connection is a URLSession error on iOS and a NetworkException on Android. The SDK never sends the message again by itself; show the error and let the customer try again.

Conversation state

ConversationState keeps one conversation ready for your chat screen. On iOS its properties are observable; on Android it publishes a StateFlow snapshot. It holds messages, conversationId, isSending, isLoadingHistory, hasMoreHistory, toolCalls, toolResults and error. Use sendMessage, retry, loadHistory, loadMoreHistory, clearError and clear. ConversationListState does the same for the list of conversations, with conversations, isLoading, hasMore and error, and load, loadMore, clearError and clear. When the signed-in user changes, cancel any work still running and clear both.

History

listConversations and listMessages return a page with data, total, hasMore and cursor. A page holds 20 items unless you ask for between 1 and 100. Conversations come most recently active first, each with a title taken from the customer’s first message. The first page of messages holds the newest ones, in the order they were written; each later page holds older messages to put in front. History only holds this agent’s iOS and Android conversations. Website conversations are never included.
  • Before sign-in, history belongs to the app installation. The SDK creates an installation ID the first time it runs and keeps it for that agent and base URL.
  • A signed-in user has the same history on every device and on both platforms.
A conversation’s status is ongoing, taken_over or ended. An ended conversation accepts no new messages or retries. While a teammate has the conversation (taken_over), new messages go to them rather than to the agent, and retries are refused. See Helpdesk for taking a conversation over. To rate an answer, call feedback with the conversation ID, the answer’s ID and "up" or "down". Pass nil on iOS or null on Android to clear the rating.

Signed-in users

When someone is signed in to your app, your backend can vouch for them, so their conversations follow them across devices.
1

Get the token secret

Open Settings → Identity and copy the Token secret. Keep it on your backend. It must never ship inside the app.
2

Sign a token on your backend

After your backend has authenticated the user, sign a JWT with HS256 and the token secret. It must name the user in sub or user_id. If it has an exp, it can be at most 24 hours ahead, plus 60 seconds for clock differences. The token works exactly like the website widget’s; see Identity verification for the contact fields it can carry.
3

Pass it to the SDK

Fetch the token from your backend and call identify. Call logout when the user signs out.
Signing in moves the conversations this installation had before sign-in to the user and starts a new conversation. Signing out forgets the user and the current conversation but keeps the installation ID, and deletes no history on the server. A token that is invalid or expired never falls back to anonymous chat: every request fails with INVALID_IDENTITY until you call identify with a new one. Signed-in users need the Standard plan or above, and identity verification set up for the agent. Without them, every token is refused. See plans.

App actions

The agent can ask your app to do something, such as reading the app version or opening a screen, and use the result in its answer.
1

Create the action

Create a custom action and choose Run client-side code in Action type. Keep Wait for response on when the agent needs the result. See Custom tools.
2

Enable it for the app

Turn the action on for iOS SDK, Android SDK or both. The SDK channel’s Actions tab lists the actions for that platform.
3

Register a handler

Register a handler under the action’s exact name:
When the agent calls the action, the SDK runs your handler, sends its result back and lets the agent finish its answer.
  • The result must be JSON of at most 20,000 bytes.
  • By default the SDK runs at most 10 app actions per reply. On iOS, change this with maxToolLoopSteps when you create the client; on Android, set client.maxToolLoopSteps.
  • With Wait for response on, your handler has 25 seconds. After that, the agent tells the customer the action could not be completed.
  • With no handler under that name, or when the handler throws, the agent gets an error instead of a result.
Actions that call an API run on Centillion and need no handler in the app. onToolCall and onToolResult report them too, with execution set to "server". Each platform uses its own action settings, even in a conversation that started on the other one. An action written as JavaScript for the website does not run in a native app. Implement it in Swift or Kotlin instead.

Errors

On iOS, server errors are SDKError, and lost connections are URLSession errors. On Android, server errors are ApiException, and lost connections are NetworkException. Both server error types carry status, code and message. A cancelled request stays a cancellation.

Example apps

Each example app is a working chat app built on its SDK, with streaming replies, history, retry and a get_app_version action. To try that action, create a client action with that name, turn on Wait for response, enable it for the platform and ask the agent for the app version.
  • iOS. Clone ios-sdk-example-app, open CentillionSample.xcodeproj, choose an iPhone simulator and run. Run swift test in a clone of the SDK to test the library.
  • Android. Clone android-sdk-example-app with git clone --recurse-submodules, open it in Android Studio and run the app target.
Enter your agent ID and SDK base URL in the app. Only the Debug builds accept plain HTTP to a local server; use HTTPS on physical devices and in production.
Test with a separate agent. Every message creates real conversations and uses credits.
Last modified on October 17, 2026