# ChatResponse Source: https://chatbase.co/docs/android-sdk/chat-response Reference for ChatResponse, ResponseMetadata, Part, and related types returned by sendMessage and retry. ## ChatResponse `data class ChatResponse` — **Package:** `com.chatbase.sdk.model` The aggregated result returned by `sendMessage` and `retry`. ```kotlin theme={null} data class ChatResponse( val id: String, val role: String, val parts: List, val metadata: ResponseMetadata ) ``` Server-assigned message ID. Always `"assistant"`. The response content — text, tool calls, and tool results. Same as `ChatResponse.id`. Server-assigned ID for the user's message. The conversation ID. Pass this to continue the conversation. Why the stream ended: `STOP`, `ERROR`, `TOOL_CALLS`, or `UNKNOWN`. Credits consumed by this request. ## Part `sealed interface Part` — **Package:** `com.chatbase.sdk.model` ### Part.Text Text content generated by the AI agent or sent by the user. ```kotlin theme={null} data class Text(val text: String) : Part ``` The text content. ### Part.ToolCall A tool invocation requested by the AI agent. ```kotlin theme={null} data class ToolCall( val toolCallId: String, val toolName: String, val input: JsonElement? ) : Part ``` Unique tool call identifier. Name of the tool. The tool's input parameters. ### Part.ToolResult The result of a tool execution. ```kotlin theme={null} data class ToolResult( val toolCallId: String, val toolName: String, val output: JsonElement? ) : Part ``` Matches the originating `ToolCall.toolCallId`. Name of the tool. The tool's output. ## Enums ### Role ```kotlin theme={null} enum class Role { USER, // serialized as "user" ASSISTANT // serialized as "assistant" } ``` ### FinishReason ```kotlin theme={null} enum class FinishReason { STOP, // serialized as "stop" — normal completion ERROR, // serialized as "error" — an error occurred TOOL_CALLS, // serialized as "tool-calls" — waiting for tool results UNKNOWN // serialized as "unknown" — unrecognized finish reason } ``` # Client-Side Tools Source: https://chatbase.co/docs/android-sdk/client-side-tools Register local tool handlers that the AI agent can invoke during a conversation. ## What Are Client-Side Tools? Client-side tools let your AI agent invoke functions that run locally on the Android device. Register a handler, and the SDK takes care of the rest — when the AI agent calls the tool, your handler runs and the result is fed back into the conversation automatically. Client-side tools correspond to **Custom Actions** configured on your AI agent in the [Chatbase Dashboard](https://www.chatbase.co/dashboard). The `toolName` in the SDK matches the name of the configured action. ## tool `interface ChatbaseClient` — **Package:** `com.chatbase.sdk` ```kotlin theme={null} fun tool(name: String, handler: suspend (input: Map) -> Any) ``` Register a client-side tool handler. The tool name. Must match a Custom Action configured on your AI agent. A suspend function that receives the parsed input and returns a result. ```kotlin theme={null} client.tool("get_weather") { input -> val city = input["city"] as String // Call a weather API, read a sensor, etc. mapOf( "city" to city, "temperature" to "22°C", "condition" to "Sunny" ) } ``` The AI agent can now call `get_weather` during a conversation. The SDK executes your handler and feeds the result back automatically. This loop can repeat up to 10 times per `sendMessage` call — if the AI agent requests more, `sendMessage` throws a `ChatbaseException` ("Tool loop exceeded maximum iterations"). Tool results are limited to **20 KB** when serialized to JSON. Keep tool outputs concise — return only the data the AI agent needs. ## removeTool ```kotlin theme={null} fun removeTool(name: String) ``` Unregister a previously registered tool handler. The tool name to remove. ## Tracking Execution Use the `onToolCall` and `onToolResult` callbacks to observe tool execution: ```kotlin theme={null} client.sendMessage("What's the weather in Tokyo?") { onToolCall { tool -> println("Agent is calling: ${tool.toolName}") println("Input: ${tool.inputAsMap()}") } onToolResult { result -> println("Tool result: ${result.outputAsString()}") } onTextDelta { delta -> print(delta) // Agent's response after the tool result } } ``` ### ToolCallInfo `data class ToolCallInfo` — **Package:** `com.chatbase.sdk` Passed to the `onToolCall` callback before handler execution. ```kotlin theme={null} data class ToolCallInfo( val toolCallId: String, val toolName: String, val input: JsonElement ) ``` ```kotlin theme={null} fun inputAsMap(): Map ``` Parse the JSON input into a `Map` for easy access. ### ToolResultInfo `data class ToolResultInfo` — **Package:** `com.chatbase.sdk` Passed to the `onToolResult` callback after handler execution. ```kotlin theme={null} data class ToolResultInfo( val toolCallId: String, val toolName: String, val output: Any ) ``` ```kotlin theme={null} fun outputAsString(): String ``` Serialize the output to a JSON string. ## Interactive Tools Since handlers are `suspend` functions, they can block on user interaction. For example, showing a color picker and waiting for the user's choice: ```kotlin theme={null} client.tool("pick_color") { _ -> val deferred = CompletableDeferred() // Show a color picker dialog (UI-framework specific) colorPickerRequest.value = deferred // Observed by the Composable // Suspend until the user picks a color val color = deferred.await() mapOf("color" to color) } ``` ## Related Streaming callbacks and Kotlin Flow Handle errors during tool execution # Conversation & Message Source: https://chatbase.co/docs/android-sdk/conversation-models Reference for Conversation, Message, Page, and related types used in conversation history. ## Conversation `data class Conversation` — **Package:** `com.chatbase.sdk.model` ```kotlin theme={null} data class Conversation( val id: String, val title: String?, val createdAt: Long, val updatedAt: Long, val userId: String?, val status: ConversationStatus ) ``` Unique conversation ID. Auto-generated or server-assigned title. Creation timestamp (Unix epoch seconds). Timestamp of the last message (Unix epoch seconds). The identified user who owns this conversation, or `null` for anonymous (device-scoped) conversations. `ONGOING`, `ENDED`, or `TAKEN_OVER`. ### ConversationStatus ```kotlin theme={null} enum class ConversationStatus { ONGOING, // serialized as "ongoing" — active conversation ENDED, // serialized as "ended" — conversation has ended TAKEN_OVER // serialized as "taken_over" — conversation taken over by a human agent } ``` ## Message `data class Message` — **Package:** `com.chatbase.sdk.model` ```kotlin theme={null} data class Message( val id: String, val role: Role, val parts: List, val createdAt: Long?, val feedback: Feedback?, val metadata: MessageMetadata? ) ``` Unique message ID. `USER` or `ASSISTANT`. Message content parts — `Text`, `ToolCall`, or `ToolResult`. See [ChatResponse](/docs/android-sdk/chat-response) for details. Creation timestamp (Unix epoch seconds). Absent on some older messages. User feedback on this message. Confidence/relevance score. ### Feedback ```kotlin theme={null} enum class Feedback { POSITIVE, // serialized as "positive" NEGATIVE // serialized as "negative" } ``` ## Page\ `data class Page` — **Package:** `com.chatbase.sdk.model` Returned by `listConversations()` and `listMessages()`. ```kotlin theme={null} data class Page( val data: List, val cursor: String?, val hasMore: Boolean, val total: Int ) ``` Items on this page. Cursor for the next page. `null` if no more pages. Whether more pages exist. Total item count across all pages. ```kotlin theme={null} val canLoadMore: Boolean ``` `true` if `hasMore` is true and `cursor` is non-null. ```kotlin theme={null} suspend fun loadMore(): Page? ``` Load the next page. Returns a new `Page` with the older items prepended to the existing `data`, so you always have the full accumulated list. Returns `null` if there are no more pages. # Conversations & History Source: https://chatbase.co/docs/android-sdk/conversations Manage conversations, load message history, and navigate paginated results with the Chatbase Android SDK. ## Starting and Continuing Conversations Send a message to start a new conversation. The SDK creates one automatically if no `conversationId` is provided. ```kotlin theme={null} val response = client.sendMessage("Hello!") ``` The conversation ID is returned in the response metadata: ```kotlin theme={null} val conversationId = response.metadata.conversationId ``` Pass the `conversationId` to subsequent calls: ```kotlin theme={null} val followUp = client.sendMessage( message = "Tell me more", conversationId = conversationId ) ``` The SDK automatically tracks the current conversation ID. After your first `sendMessage`, subsequent calls without an explicit `conversationId` reuse the same conversation: ```kotlin theme={null} client.sendMessage("First message") // starts a new conversation client.sendMessage("Follow-up") // continues the same conversation println(client.currentConversationId) // "conv_abc123" ``` ## newConversation `interface ChatbaseClient` — **Package:** `com.chatbase.sdk` ```kotlin theme={null} fun newConversation() ``` Clear the current conversation ID so the next `sendMessage` starts a new conversation. ```kotlin theme={null} client.newConversation() client.sendMessage("Brand new conversation!") ``` ## listConversations ```kotlin theme={null} suspend fun listConversations( cursor: String? = null, limit: Int? = null ): Page ``` Retrieve a paginated list of conversations. Opaque cursor from a previous response. Omit to start from the beginning. Number of items per page, between 1 and 100. Defaults to 20. ```kotlin theme={null} val page = client.listConversations(limit = 20) page.data.forEach { conversation -> println("${conversation.id} — ${conversation.title}") println(" Status: ${conversation.status}") } println("Total: ${page.total}") println("Has more: ${page.hasMore}") ``` ### Conversation `data class Conversation` — **Package:** `com.chatbase.sdk.model` ```kotlin theme={null} data class Conversation( val id: String, val title: String?, val createdAt: Long, val updatedAt: Long, val userId: String?, val status: ConversationStatus ) ``` Unique conversation ID. Auto-generated or server-assigned title. Creation timestamp (Unix epoch seconds). Timestamp of the last message (Unix epoch seconds). The identified user who owns this conversation, or `null` for anonymous (device-scoped) conversations. `ONGOING`, `ENDED`, or `TAKEN_OVER`. ## listMessages ```kotlin theme={null} suspend fun listMessages( conversationId: String, cursor: String? = null, limit: Int? = null ): Page ``` Retrieve messages in a conversation. The conversation to fetch messages from. Opaque cursor from a previous response. Omit to start from the newest messages. Number of items per page, between 1 and 100. Defaults to 20. Messages are returned in **reverse chronological order** — the first page contains the most recent messages. Within each page, messages are ordered oldest to newest. ```kotlin theme={null} val page = client.listMessages(conversationId, limit = 50) page.data.forEach { message -> val role = if (message.role == Role.USER) "You" else "Agent" val text = message.parts .filterIsInstance() .joinToString("") { it.text } println("$role: $text") } ``` ### Message `data class Message` — **Package:** `com.chatbase.sdk.model` ```kotlin theme={null} data class Message( val id: String, val role: Role, val parts: List, val createdAt: Long?, val feedback: Feedback?, val metadata: MessageMetadata? ) ``` Unique message ID. `USER` or `ASSISTANT`. Message content parts (text, tool calls, tool results). Creation timestamp (Unix epoch seconds). Absent on some older messages. `POSITIVE`, `NEGATIVE`, or `null`. Confidence/relevance score. ## Pagination `data class Page` — **Package:** `com.chatbase.sdk.model` All list methods return a `Page` with built-in pagination support. ```kotlin theme={null} data class Page( val data: List, val cursor: String?, val hasMore: Boolean, val total: Int ) ``` Items on this page. Cursor for the next page. `null` if no more pages. Whether more pages exist. Total item count across all pages. ```kotlin theme={null} val canLoadMore: Boolean ``` `true` if `hasMore` is true and `cursor` is non-null. ```kotlin theme={null} suspend fun loadMore(): Page? ``` Load the next page. Returns a new `Page` with the older items prepended to the existing `data`, so you always have the full accumulated list. Returns `null` if there are no more pages. ### Paginating Through All Results ```kotlin theme={null} var page = client.listConversations(limit = 20) while (true) { page.data.forEach { conversation -> println(conversation.title) } if (!page.canLoadMore) break page = page.loadMore() ?: break } ``` ## Related Send messages and stream responses Scope conversations to users # Error Handling Source: https://chatbase.co/docs/android-sdk/error-handling Exception hierarchy and error handling patterns for the Chatbase Android SDK. ## Exception Hierarchy All SDK errors extend `ChatbaseException`: ``` ChatbaseException ├── ApiException — API errors from the Chatbase server └── NetworkException — Connection failures, DNS errors, timeouts ``` ## ApiException `class ApiException : ChatbaseException` — **Package:** `com.chatbase.sdk.exception` Thrown when the Chatbase API returns an error response. ```kotlin theme={null} class ApiException( val httpStatus: Int, val errorCode: String, val errorMessage: String, val details: Map? = null ) : ChatbaseException ``` The HTTP status code of the error response (e.g. `401`, `403`, `429`). Machine-readable error code. Use this for programmatic handling. Human-readable error description. Optional field-level validation errors. ### Convenience Properties ```kotlin theme={null} val isRateLimited: Boolean // httpStatus == 429 val isNotFound: Boolean // httpStatus == 404 val isCreditsExhausted: Boolean // httpStatus == 402 ``` For statuses without a helper — such as `401` (authentication) or `403` (access denied) — check `httpStatus` or `errorCode` directly. ## Error Codes These are the error codes you may encounter through `ApiException.errorCode`:
Status Code Description
400VALIDATION\_INVALID\_BODYThe request body failed validation. details maps field names to messages. Also returned when a tool result exceeds the 20 KB limit.
400CHAT\_RETRY\_NO\_USER\_MESSAGEThe message passed to retry() has no preceding user message to retry from.
401AUTH\_INVALID\_JWTThe JWT token passed to identify() is invalid, expired, or could not be verified — including when identity verification is not configured for the AI agent. See User Identity.
402CHAT\_CREDITS\_EXHAUSTEDThe workspace's message credit balance is zero. Upgrade the plan or wait for credits to reset. Caught by isCreditsExhausted.
402CHAT\_AGENT\_CREDITS\_EXHAUSTEDThe specific AI agent's credit allocation has been used up. Caught by isCreditsExhausted.
403AUTH\_OWNERSHIP\_MISMATCHThe conversation belongs to a different user or device. Also returned by retry() and listMessages() when the conversation does not exist.
403CHAT\_CONVERSATION\_MISMATCHThe conversation could not be resolved for this AI agent.
403CHAT\_MODEL\_NOT\_ALLOWEDThe AI agent uses a model not available on the current plan.
403CHAT\_CONVERSATION\_NOT\_ONGOINGThe conversation has ended or was taken over and cannot receive new messages. Start a new conversation.
404AGENT\_NOT\_FOUNDNo AI agent matches the provided ID, or the Android SDK channel is not enabled for the AI agent (see Quick Start). Caught by isNotFound.
404RESOURCE\_NOT\_FOUNDThe conversation or message does not exist. Caught by isNotFound.
404CHAT\_RETRY\_MESSAGE\_NOT\_FOUNDThe message ID provided for retry() was not found. Caught by isNotFound.
404RESOURCE\_TOOL\_CALL\_NOT\_FOUNDThe tool call was not found or has expired. Can surface through the automatic tool loop. Caught by isNotFound.
404RESOURCE\_TOOL\_CALL\_MISMATCHThe tool call does not belong to this conversation. Caught by isNotFound.
404RESOURCE\_TOOL\_RESULT\_NOT\_PENDINGNo pending tool result exists for this tool call — usually a duplicate submission. Caught by isNotFound.
429RATE\_LIMIT\_TOO\_MANY\_REQUESTSRate limit exceeded (1,000 requests per 10 seconds per device). Back off and retry. Caught by isRateLimited.
500CHAT\_STREAMING\_ERRORThe response stream failed server-side. Safe to retry.
500INTERNAL\_SERVER\_ERRORAn unexpected server error occurred. Retry, or contact support if it persists.
## NetworkException `class NetworkException : ChatbaseException` — **Package:** `com.chatbase.sdk.exception` ```kotlin theme={null} class NetworkException( message: String, cause: Throwable? = null ) : ChatbaseException ``` Thrown for connection-level failures — DNS errors, socket timeouts, no internet connectivity, and similar issues. ## Handling Errors Use a try-catch block with the SDK's exception hierarchy: ```kotlin theme={null} try { val response = client.sendMessage("Hello") } catch (e: ApiException) { when { e.httpStatus == 401 -> { println("Authentication failed. Check your JWT token.") } e.httpStatus == 403 -> { println("Access denied. Check conversation ownership or plan.") } e.isRateLimited -> { println("Rate limited. Back off and retry.") } e.isCreditsExhausted -> { println("No credits remaining. Upgrade plan.") } e.isNotFound -> { println("Not found. Check your agent ID.") } else -> { println("API error: ${e.errorCode} — ${e.errorMessage}") } } } catch (e: NetworkException) { println("Network error: ${e.message}") } catch (e: ChatbaseException) { println("Unknown SDK error: ${e.message}") } ``` Handle errors via the `onError` callback: ```kotlin theme={null} client.sendMessage("Hello") { onTextDelta { delta -> print(delta) } onError { error -> when (error) { is ApiException -> println("API error: ${error.errorCode}") is NetworkException -> println("Network error: ${error.message}") else -> println("Error: ${error.message}") } } } ``` Check for `ChatStreamEvent.Error` events: ```kotlin theme={null} client.sendMessageStream("Hello").collect { event -> when (event) { is ChatStreamEvent.TextDelta -> print(event.delta) is ChatStreamEvent.Error -> { when (val ex = event.exception) { is ApiException -> println("${ex.errorCode}: ${ex.errorMessage}") is NetworkException -> println("Network: ${ex.message}") else -> println("Error: ${ex.message}") } } else -> {} } } ``` ## Related Streaming callbacks and error events SDK setup and configuration # Android SDK Overview Source: https://chatbase.co/docs/android-sdk/overview Introduction to the Chatbase Android SDK — a Kotlin-first library for building conversational AI experiences on Android. **Alpha Release.** The Chatbase Android SDK is currently in alpha (v0.0.1-alpha03). APIs may change in future releases. Conversation methods apply exclusively to conversations created through the mobile SDKs (Android and iOS). Conversations generated through the widget, the API, or external integrations cannot be accessed using the SDK. A user identified on both platforms sees their Android and iOS SDK conversations together. ## What is the Chatbase Android SDK? The Chatbase Android SDK is a Kotlin-first library that lets you integrate Chatbase AI agents into your Android app. It provides: * **Real-time streaming** with two levels of abstraction * **Client-side tools** that let the AI agent invoke local functions on the device * **User identity** with JWT-based authentication and automatic device ID tracking * **Conversation management** with cursor-based pagination * **Structured error handling** with typed exceptions **Requirements:** | Requirement | Minimum | | ----------------- | -------------------------------------------------- | | Android API | 24 (Android 7.0) | | Java | 11+ | | Kotlin Coroutines | Required | | Jetpack Compose | Not required — the SDK works with any UI framework | ## Installation ```kotlin theme={null} // build.gradle.kts (app module) dependencies { implementation("com.chatbase:chatbase-sdk:0.0.1-alpha03") } ``` ```groovy theme={null} // build.gradle (app module) dependencies { implementation 'com.chatbase:chatbase-sdk:0.0.1-alpha03' } ``` The SDK declares the `INTERNET` permission in its own manifest. It is merged automatically — you do not need to add it to your app's manifest. ## Quick Start 1. Go to the [Chatbase Dashboard](https://www.chatbase.co/dashboard) 2. Select your AI agent 3. Go to **Settings** → **General** 4. Copy the **Agent ID** In the dashboard, go to **Deploy** → **Android SDK** and enable the channel for your AI agent. If the Android SDK channel is not enabled, every SDK request fails with a `404` `AGENT_NOT_FOUND` error — even when the agent ID is correct. ```kotlin theme={null} import com.chatbase.sdk.Chatbase val client = Chatbase.create(context, "YOUR_AGENT_ID") ``` `context` can be any Android `Context` (Activity, Application, etc.). It is only used during creation to generate the device ID. ```kotlin theme={null} import com.chatbase.sdk.model.Part lifecycleScope.launch { val response = client.sendMessage("Hello! How can you help me?") { onTextDelta { delta -> // Called for each text chunk — safe to update UI print(delta) } } // Access the full response val text = response.parts .filterIsInstance() .joinToString("") { it.text } println(text) println("Conversation: ${response.metadata.conversationId}") } ``` ```kotlin theme={null} // In your ViewModel override fun onCleared() { client.close() } ``` ## Chatbase `object Chatbase` — **Package:** `com.chatbase.sdk` The singleton factory for creating SDK clients. ### create ```kotlin theme={null} fun create(context: Context, agentId: String): ChatbaseClient ``` Create a client with default settings. Any Android `Context`. Used only during creation to generate the device ID. The Chatbase agent ID to connect to. ### create (with configuration) ```kotlin theme={null} fun create(context: Context, block: ChatbaseConfig.Builder.() -> Unit): ChatbaseClient ``` Create a client with custom configuration via a DSL builder. ```kotlin theme={null} val client = Chatbase.create(context) { agentId = "YOUR_AGENT_ID" connectTimeoutMs = 15_000 // 15 seconds readTimeoutMs = 60_000 // 60 seconds } ``` ## ChatbaseConfig `data class ChatbaseConfig` — **Package:** `com.chatbase.sdk` | Property | Type | Default | Description | | ------------------ | -------- | ---------- | ------------------------------------ | | `agentId` | `String` | (required) | The Chatbase agent ID to connect to. | | `connectTimeoutMs` | `Long` | `10_000` | Connection timeout in milliseconds. | | `readTimeoutMs` | `Long` | `30_000` | Read timeout in milliseconds. | For streaming responses, the SDK uses a separate 5-minute read timeout regardless of the `readTimeoutMs` setting. This ensures long-running streams are not interrupted prematurely. ## Rate Limits The Chatbase API enforces a rate limit of **1,000 requests per 10 seconds** per device, applied server-side. When the limit is exceeded, the SDK throws an `ApiException` with `isRateLimited == true`. See [Error Handling](/docs/android-sdk/error-handling) for how to handle this. ## Next Steps Real-time streaming with callbacks and Kotlin Flow Register local tool handlers the AI agent can invoke Manage conversations, history, and pagination Exception hierarchy and error handling patterns # Streaming Source: https://chatbase.co/docs/android-sdk/streaming How to stream real-time responses from the Chatbase Android SDK using callbacks and Kotlin Flow. ## Two-Tier Streaming API The SDK provides two levels of abstraction for streaming: * **`sendMessage()`** — High-level API with a callback DSL. Handles tool calls automatically (up to 10 iterations). Recommended for most use cases. * **`sendMessageStream()`** — Low-level API returning a `Flow`. Tool calls are not handled automatically, giving you full control over event processing. ## sendMessage `interface ChatbaseClient` — **Package:** `com.chatbase.sdk` ```kotlin theme={null} suspend fun sendMessage( message: String, conversationId: String? = null, callbacks: StreamCallbacks.() -> Unit = {} ): ChatResponse ``` Sends a message, streams the response in real time, and automatically handles tool calls (up to 10 iterations). Returns the aggregated `ChatResponse` when the stream completes. The user message to send to the AI agent. Continue an existing conversation. Omit to use `currentConversationId` or start a new one. Streaming callback DSL. ```kotlin theme={null} val response = client.sendMessage("Tell me a story") { onStart { println("Stream started...") } onTextDelta { delta -> // Called for each text chunk — append to your UI print(delta) } onToolCall { toolCall -> println("Agent is calling: ${toolCall.toolName}") } onToolResult { result -> println("Tool result: ${result.outputAsString()}") } onFinish { response -> println("\nDone! Message ID: ${response.id}") } onError { error -> println("Error: ${error.message}") } } ``` ### StreamCallbacks `class StreamCallbacks` — **Package:** `com.chatbase.sdk` All callbacks are invoked on `Dispatchers.Main` — it is safe to update UI directly from any callback without explicit dispatching. ```kotlin theme={null} fun onStart(handler: () -> Unit) ``` Called when the connection opens and streaming begins. ```kotlin theme={null} fun onTextDelta(handler: (text: String) -> Unit) ``` Called for each incremental text chunk received. ```kotlin theme={null} fun onToolCall(handler: (toolCall: ToolCallInfo) -> Unit) ``` Called when a tool call's full input is available (before execution). ```kotlin theme={null} fun onToolResult(handler: (result: ToolResultInfo) -> Unit) ``` Called after a tool handler executes and returns a result. ```kotlin theme={null} fun onFinish(handler: (response: ChatResponse) -> Unit) ``` Called when the stream completes successfully. ```kotlin theme={null} fun onError(handler: (error: ChatbaseException) -> Unit) ``` Called when an error occurs during streaming. ### ChatResponse `data class ChatResponse` — **Package:** `com.chatbase.sdk.model` The aggregated result after streaming completes. Server-assigned message ID. Always `"assistant"`. The response content — text, tool calls, and tool results. See [ChatResponse](/docs/android-sdk/chat-response). Same as `ChatResponse.id`. Server-assigned ID for the user's message. The conversation ID. Pass this to continue the conversation. Why the stream ended: `STOP`, `ERROR`, `TOOL_CALLS`, or `UNKNOWN`. Credits consumed by this request. ## sendMessageStream ```kotlin theme={null} fun sendMessageStream( message: String, conversationId: String? = null ): Flow ``` Returns a cold `Flow` of raw streaming events. Tool calls are **not** handled in Flow mode — registered tool handlers are not invoked, and the SDK exposes no API for submitting tool results manually. The user message to send to the AI agent. Continue an existing conversation. Omit to use `currentConversationId` or start a new one. Tool calls are **not** executed in Flow mode — tool events are informational only, and the stream finishes with `finishReason == "tool-calls"` without a final answer. If your AI agent uses client-side tools, use `sendMessage` with callbacks instead. See [Client-Side Tools](/docs/android-sdk/client-side-tools) for details. ```kotlin theme={null} client.sendMessageStream("Tell me about Kotlin").collect { event -> when (event) { is ChatStreamEvent.TextDelta -> print(event.delta) is ChatStreamEvent.Finish -> println("\nDone: ${event.finishReason}") is ChatStreamEvent.Error -> println("Error: ${event.exception.message}") else -> { /* handle other events as needed */ } } } ``` You can also filter for specific event types: ```kotlin theme={null} client.sendMessageStream("Hello") .filterIsInstance() .collect { event -> print(event.delta) } ``` ## Stream Events The `Flow` returned by `sendMessageStream` emits `ChatStreamEvent` objects — text deltas, tool input/output, step lifecycle, and errors. See [Streaming Events](/docs/android-sdk/streaming-events) for the full type reference. ## Continuing a Conversation The SDK automatically tracks the current conversation. After sending a message, subsequent calls reuse the same conversation: ```kotlin theme={null} // First message — starts a new conversation client.sendMessage("My name is Alice.") println(client.currentConversationId) // "conv_abc123" // Subsequent messages continue the same conversation client.sendMessage("What is my name?") // Agent remembers: "Alice" ``` To start a fresh conversation: ```kotlin theme={null} client.newConversation() client.sendMessage("Fresh start!") // Creates a new conversation ``` See [Conversations & History](/docs/android-sdk/conversations) for listing conversations and loading message history. ## retry ```kotlin theme={null} suspend fun retry( conversationId: String, messageId: String, callbacks: StreamCallbacks.() -> Unit = {} ): ChatResponse ``` Retry a failed assistant message. Same streaming and tool-loop behavior as `sendMessage`. The conversation containing the failed message. The ID of the assistant message to retry. Streaming callback DSL. A convenience extension extracts the IDs from a `ChatResponse`: ```kotlin theme={null} suspend fun ChatbaseClient.retry( response: ChatResponse, callbacks: StreamCallbacks.() -> Unit = {} ): ChatResponse ``` ```kotlin theme={null} val response = client.sendMessage("Hello") // ... later: val retried = client.retry(response) { onTextDelta { delta -> print(delta) } } ``` ## retryStream ```kotlin theme={null} fun retryStream( conversationId: String, messageId: String ): Flow ``` Raw streaming variant of `retry`. Tool calls are **not** handled automatically. ```kotlin theme={null} client.retryStream(conversationId, messageId).collect { event -> when (event) { is ChatStreamEvent.TextDelta -> print(event.delta) is ChatStreamEvent.Finish -> println("\nDone") is ChatStreamEvent.Error -> println("Error: ${event.exception.message}") else -> {} } } ``` ## Related Register tool handlers the AI agent can invoke Exception hierarchy and error handling patterns # Streaming Events Source: https://chatbase.co/docs/android-sdk/streaming-events Reference for ChatStreamEvent, StreamMessageMetadata, ToolCallInfo, and related streaming types. ## ChatStreamEvent `sealed interface ChatStreamEvent` — **Package:** `com.chatbase.sdk.streaming` Events emitted by `sendMessageStream()` and `retryStream()`. ### TextStart A new text block is starting. ```kotlin theme={null} data class TextStart(val id: String) : ChatStreamEvent ``` ### TextDelta An incremental text chunk. Append to the current text. ```kotlin theme={null} data class TextDelta(val id: String, val delta: String) : ChatStreamEvent ``` ### TextEnd The current text block is complete. ```kotlin theme={null} data class TextEnd(val id: String) : ChatStreamEvent ``` ### ToolInputStart A tool call is starting — input will stream incrementally. ```kotlin theme={null} data class ToolInputStart( val toolCallId: String, val toolName: String ) : ChatStreamEvent ``` ### ToolInputDelta Incremental tool input text. ```kotlin theme={null} data class ToolInputDelta( val toolCallId: String, val inputTextDelta: String ) : ChatStreamEvent ``` ### ToolInputAvailable The tool call's full input is ready. ```kotlin theme={null} data class ToolInputAvailable( val toolCallId: String, val toolName: String, val input: JsonElement ) : ChatStreamEvent ``` Read the full `input` object directly from this event — no need to concatenate preceding deltas. ### ToolOutputAvailable A tool's execution result is available. ```kotlin theme={null} data class ToolOutputAvailable( val toolCallId: String, val output: JsonElement ) : ChatStreamEvent ``` ### StepStart / StepFinish ```kotlin theme={null} object StepStart : ChatStreamEvent object StepFinish : ChatStreamEvent ``` ### Start The message stream is starting. ```kotlin theme={null} data class Start( val messageId: String?, val messageMetadata: StreamMessageMetadata? ) : ChatStreamEvent ``` ### Finish The stream is complete. ```kotlin theme={null} data class Finish( val finishReason: String, val messageMetadata: StreamMessageMetadata? ) : ChatStreamEvent ``` `finishReason` is usually `"stop"`, `"error"`, or `"tool-calls"`. Other values (`"length"`, `"content-filter"`, `"other"`, `"unknown"`) are possible and map to `FinishReason.UNKNOWN` in the aggregated `ChatResponse`. ### MessageMetadataEvent Updated metadata arrived mid-stream. ```kotlin theme={null} data class MessageMetadataEvent( val messageMetadata: StreamMessageMetadata ) : ChatStreamEvent ``` ### Error An error occurred during streaming. ```kotlin theme={null} data class Error(val exception: ChatbaseException) : ChatStreamEvent ``` The `exception` may be an `ApiException` or `NetworkException`. See [Error Handling](/docs/android-sdk/error-handling). ## StreamMessageMetadata `data class StreamMessageMetadata` — **Package:** `com.chatbase.sdk.streaming` Accompanies `Start`, `Finish`, and `MessageMetadataEvent` events. ```kotlin theme={null} data class StreamMessageMetadata( val messageId: String?, val userMessageId: String?, val conversationId: String?, val usage: StreamUsage? ) ``` ### StreamUsage ```kotlin theme={null} data class StreamUsage(val credits: Double = 0.0) ``` ## ToolCallInfo `data class ToolCallInfo` — **Package:** `com.chatbase.sdk` Passed to the `onToolCall` callback before handler execution. ```kotlin theme={null} data class ToolCallInfo( val toolCallId: String, val toolName: String, val input: JsonElement ) ``` ```kotlin theme={null} fun inputAsMap(): Map ``` Parse the JSON input into a `Map` for easy access. ## ToolResultInfo `data class ToolResultInfo` — **Package:** `com.chatbase.sdk` Passed to the `onToolResult` callback after handler execution. ```kotlin theme={null} data class ToolResultInfo( val toolCallId: String, val toolName: String, val output: Any ) ``` ```kotlin theme={null} fun outputAsString(): String ``` Serialize the output to a JSON string. # User Identity Source: https://chatbase.co/docs/android-sdk/user-identity How to identify users with JWT tokens and manage device-level identity in the Chatbase Android SDK. ## Overview The SDK supports two layers of identity: | Layer | How It Works | Scope | | ----------------- | ----------------------------------------- | ---------------------------------- | | **Device ID** | Automatic — generated on first use | Conversations scoped to the device | | **User Identity** | Opt-in — set a JWT token via `identify()` | Conversations scoped to the user | The SDK works anonymously out of the box. Call `identify()` to associate conversations with a specific user. ## Device ID Every SDK instance has a stable device ID, generated automatically on creation: ```kotlin theme={null} val deviceId: String ``` ```kotlin theme={null} val client = Chatbase.create(context, "YOUR_AGENT_ID") println(client.deviceId) // "a1b2c3d4-e5f6-..." ``` The device ID uses Android's `Settings.Secure.ANDROID_ID` when available. On emulators or when restricted, it falls back to a UUID persisted in SharedPreferences (`chatbase_sdk_prefs`). The ID remains stable across app launches. ## identify `interface ChatbaseClient` — **Package:** `com.chatbase.sdk` ```kotlin theme={null} suspend fun identify(token: String) ``` Verify a JWT token with the Chatbase server and identify the current user. Subsequent requests are associated with this user. `identify()` is equivalent to [`verify()`](#verify) — it verifies the token server-side and triggers the anonymous-conversation merge. A JWT generated by your backend and signed with your AI agent's identity verification secret. The payload must include a `user_id` (or `sub`) claim. Create a signed JWT token on your server containing the user ID in its payload. ```kotlin theme={null} client.identify(jwtToken) ``` Conversations are now scoped to this user. ```kotlin theme={null} println(client.isIdentified) // true println(client.currentUserId) // "user_123" (confirmed by the server during verification) ``` ## Identity Properties ```kotlin theme={null} val deviceId: String ``` Auto-generated device ID. Always available. ```kotlin theme={null} val isIdentified: Boolean ``` `true` if a JWT token has been set via `identify()`. ```kotlin theme={null} val currentUserId: String? ``` User ID confirmed by the server when the token was verified. `null` if not identified. ## verify ```kotlin theme={null} suspend fun verify(token: String) ``` Verify a JWT token with the Chatbase server. On success, the server also merges any conversations created anonymously (with the device ID) into the verified user's account. `identify()` is an alias for this method. The JWT token to verify. ```kotlin theme={null} try { client.verify(jwtToken) println("Token is valid") } catch (e: ApiException) { println("Token verification failed: ${e.errorMessage}") } ``` When `verify()` succeeds, the server automatically merges previously anonymous (device-scoped) conversations into the verified user's account. Conversations started before identification are preserved and accessible under the user's identity. The merge runs asynchronously on the server — a `listConversations()` call issued immediately after `verify()` returns may not reflect it yet. ## How Identity Affects Conversations When identified, conversations are scoped to the user — `listConversations()` returns only that user's conversations. Without identity, conversations are scoped to the device. ## logout ```kotlin theme={null} fun logout() ``` Clear the JWT token and return to anonymous (device-scoped) mode. Also clears the current conversation ID so the next message starts a fresh anonymous conversation. ```kotlin theme={null} client.logout() println(client.isIdentified) // false println(client.currentUserId) // null println(client.currentConversationId) // null println(client.deviceId) // still available — unchanged ``` `identify()` and `verify()` are equivalent — both verify the token with the server and trigger the merge of prior anonymous (device-scoped) conversations into the user's account. You never need to call both. ## Related List conversations and load message history SDK setup and configuration # Delete chatbot icon Source: https://chatbase.co/docs/api-reference/assets/delete-chatbot-icon /openapi.yaml delete /delete-chatbot-icon Deletes the chatbot's icon image # Delete chatbot profile picture Source: https://chatbase.co/docs/api-reference/assets/delete-chatbot-profile-picture /openapi.yaml delete /delete-chatbot-profile-picture Deletes the chatbot's profile picture # Upload chatbot icon Source: https://chatbase.co/docs/api-reference/assets/upload-chatbot-icon /openapi.yaml post /upload-chatbot-icon Uploads an icon image for the chatbot # Upload chatbot profile picture Source: https://chatbase.co/docs/api-reference/assets/upload-chatbot-profile-picture /openapi.yaml post /upload-chatbot-profile-picture Uploads a profile picture for the chatbot # Chat with a chatbot Source: https://chatbase.co/docs/api-reference/chat/chat-with-a-chatbot /openapi.yaml post /chat Send a message to a chatbot and receive a response. Supports streaming responses. Can continue existing conversations by providing a conversationId. **Looking for API v2?** The new Chatbase API v2 features structured error codes, cursor-based pagination, and SSE streaming. Note that API v2 is available starting from the Standard Plan. [Check out the API v2 Reference →](/docs/api-v2/overview) # Create a new chatbot Source: https://chatbase.co/docs/api-reference/chatbots/create-a-new-chatbot /openapi.yaml post /create-chatbot Creates a new chatbot with training data from text # Delete a chatbot Source: https://chatbase.co/docs/api-reference/chatbots/delete-a-chatbot /openapi.yaml delete /delete-chatbot Permanently deletes a chatbot and all associated data # Get all chatbots Source: https://chatbase.co/docs/api-reference/chatbots/get-all-chatbots /openapi.yaml get /get-chatbots Retrieves all chatbots for the authenticated account # Update a chatbot Source: https://chatbase.co/docs/api-reference/chatbots/update-a-chatbot /openapi.yaml post /update-chatbot-data Updates and retrains a chatbot with new content # Update chatbot settings Source: https://chatbase.co/docs/api-reference/chatbots/update-chatbot-settings /openapi.yaml post /update-chatbot-settings Updates various chatbot configuration settings # Create contacts for a chatbot Source: https://chatbase.co/docs/api-reference/contacts/create-contacts-for-a-chatbot /openapi.yaml post /chatbots/{chatbotId}/contacts Creates one or more contacts for a specific chatbot (max 1000 per request) # Create custom attribute Source: https://chatbase.co/docs/api-reference/contacts/create-custom-attribute /openapi.yaml post /chatbots/{chatbotId}/custom-attributes Creates a new custom attribute for contacts # Delete a contact Source: https://chatbase.co/docs/api-reference/contacts/delete-a-contact /openapi.yaml delete /chatbots/{chatbotId}/contacts/{contactId} Permanently deletes a contact # Get a specific contact Source: https://chatbase.co/docs/api-reference/contacts/get-a-specific-contact /openapi.yaml get /chatbots/{chatbotId}/contacts/{contactId} Retrieves a single contact by ID # Get contacts for a chatbot Source: https://chatbase.co/docs/api-reference/contacts/get-contacts-for-a-chatbot /openapi.yaml get /chatbots/{chatbotId}/contacts Retrieves paginated list of contacts for a specific chatbot # Get custom attributes schema Source: https://chatbase.co/docs/api-reference/contacts/get-custom-attributes-schema /openapi.yaml get /chatbots/{chatbotId}/custom-attributes Retrieves the custom attributes schema for contacts # Update a contact Source: https://chatbase.co/docs/api-reference/contacts/update-a-contact /openapi.yaml patch /chatbots/{chatbotId}/contacts/{contactId} Updates an existing contact's information # Update custom attribute Source: https://chatbase.co/docs/api-reference/contacts/update-custom-attribute /openapi.yaml put /chatbots/{chatbotId}/custom-attributes/{name} Updates an existing custom attribute # Get conversations for a chatbot Source: https://chatbase.co/docs/api-reference/conversations/get-conversations-for-a-chatbot /openapi.yaml get /get-conversations Retrieves conversation history for a specific chatbot **Looking for API v2?** The new Chatbase API v2 features structured error codes, cursor-based pagination, and SSE streaming. [Check out the API v2 Reference →](/docs/api-v2/overview) # Get leads for a chatbot Source: https://chatbase.co/docs/api-reference/leads/get-leads-for-a-chatbot /openapi.yaml get /get-leads Retrieves collected leads/customers for a specific chatbot # Agents Source: https://chatbase.co/docs/api-v2/agents Programmatically create, configure, and manage your Chatbase AI agents. The Agents API gives you full programmatic control over your AI agents — create them, configure their behavior and widget styles, and clone them for reuse. Training is not a separate step: sources train as soon as you write them (see the [Sources API](/docs/api-v2/sources)). All endpoints are scoped to the account that owns the API key; a request for another account's AI agent returns 404, not 403, to avoid leaking existence. ## AI agent status The `status` field on an AI agent is derived from its sources: | Status | Meaning | | ----------- | --------------------------------------------- | | `untrained` | No live sources yet | | `training` | At least one source is still being processed | | `failed` | A source failed to train; the others are live | | `trained` | Every source is live | To follow one source, poll `GET /agents/{agentId}/sources/{sourceId}`. To follow the whole AI agent, poll `GET /agents/{agentId}`. ## Partial updates `PUT /agents/{agentId}` uses **partial update semantics** — only the fields you include are changed. | What you send | Result | | ---------------------------------- | ---------------------------------------------- | | Omit a field | No change — field keeps its current value | | Send a value | Field is updated to that value | | Send `null` (nullable fields only) | Feature is disabled or field resets to default | Example: `{ "voiceSettings": null }` disables voice mode. An empty body `{}` makes no changes. The `ipRateLimits` object also supports partial updates within itself — send only the sub-fields you want to change without affecting the others. ## `pendingSteps` Create and clone both return a 201 even when secondary steps fail. The AI agent always exists — `id` is always in the response. `pendingSteps` tells you what to retry: | Step | Meaning | Recovery | | ------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------ | | `ADD_SOURCE` | The `url` could not be added as a source | Add sources manually via the [Sources API](/docs/api-v2/sources/create-source) | | `TRAIN_AGENT` | Kept for compatibility. Sources train on write, so no action is needed. | None | When `pendingSteps` is absent, all steps succeeded. ## Training happens on write Each source trains on its own as soon as it is created, updated, or deleted. `POST /agents/{agentId}/train` is deprecated: it returns `200` with `deprecated: true` and a `Deprecation` header, and does nothing. Remove it from your integration and poll source `status` instead. ## Endpoints Paginated list of all AI agents for the account Create a new AI agent, optionally seeded with a URL Retrieve full AI agent details by ID Partial update of AI agent configuration Configure chat widget and center stage appearance No-op. Sources train on write. Deep-copy an AI agent including all its sources Re-sync websites, Notion pages, and tickets on a weekly schedule Permanently delete an AI agent and all its data ## Error codes Agent-specific error codes beyond the standard [authentication and rate-limiting errors](/docs/api-v2/error-handling):
Code HTTP Description
AGENT\_NOT\_FOUND404AI agent doesn't exist or doesn't belong to the authenticated account.
AGENT\_NOT\_TRAINED409Auto-resync needs at least one live source. Add a source and wait for it to reach trained.
AGENT\_LIMIT\_REACHED403The account has reached its plan's maximum number of AI agents. Delete an existing AI agent or upgrade your plan.
PLAN\_FEATURE\_NOT\_AVAILABLE403The requested feature is not available on the current plan. Upgrade to unlock it.
# Chat with an agent Source: https://chatbase.co/docs/api-v2/agents/chat-with-an-agent /api-v2-openapi.json post /agents/{agentId}/chat Send a message to an agent and receive a response. Supports streaming responses when `stream: true` is set in the request body. # Clone agent Source: https://chatbase.co/docs/api-v2/agents/clone-agent /api-v2-openapi.json post /agents/{agentId}/clone Creates a full deep-clone of an agent, including all its sources (excluding Notion). Returns the new agent ID. Same response shape as Create Agent — `pendingSteps` indicates if training could not start automatically. The clone is a new, independent agent; changes to the original do not affect it. Subject to plan agent limits — returns `AGENT_LIMIT_REACHED` (403) when the account has reached its maximum number of agents. # Create agent Source: https://chatbase.co/docs/api-v2/agents/create-agent /api-v2-openapi.json post /agents Creates a new agent. If `url` is provided, a link source is created from that URL and training is queued automatically. The agent is always created even if source setup or training fails — `id` is always returned. Check `pendingSteps` in the response to see which steps need to be retried: - `ADD_SOURCE` — the URL could not be added as a source. Add sources manually via the Sources API. - `TRAIN_AGENT` — training could not be started. Trigger it manually via `POST /agents/{agentId}/train`. When `pendingSteps` is absent, all steps succeeded. Subject to plan agent limits — returns `AGENT_LIMIT_REACHED` (403) when the account has reached its maximum number of agents. # Delete agent Source: https://chatbase.co/docs/api-v2/agents/delete-agent /api-v2-openapi.json delete /agents/{agentId} Permanently deletes an agent and all its sources. Also disconnects any active integrations (Slack, WhatsApp, etc.). This action is irreversible. # Get agent Source: https://chatbase.co/docs/api-v2/agents/get-agent /api-v2-openapi.json get /agents/{agentId} Returns a single agent by ID. # List agents Source: https://chatbase.co/docs/api-v2/agents/list-agents /api-v2-openapi.json get /agents Returns a paginated list of all agents for the authenticated account. # Retry a message Source: https://chatbase.co/docs/api-v2/agents/retry-a-message /api-v2-openapi.json post /agents/{agentId}/conversations/{conversationId}/retry Retry generating an assistant response for a given message. Truncates the conversation at the target message, then re-sends the preceding user message through the chat service. # Start a voice session Source: https://chatbase.co/docs/api-v2/agents/start-a-voice-session /api-v2-openapi.json post /agents/{agentId}/voice/sessions Create a real-time voice session for an agent. Pass the response `data` to the Chatbase Voice SDK (`@chatbase-co/voice-sdk`) in your client: the SDK connects, publishes the microphone, and the agent joins automatically. Requires a plan with voice mode enabled; voice minutes consume message credits. Send `{}` when no options are needed. # Submit a tool result Source: https://chatbase.co/docs/api-v2/agents/submit-a-tool-result /api-v2-openapi.json post /agents/{agentId}/conversations/{conversationId}/tool-result Submit the result of a client-side tool call. Use the toolCallId from the tool-call part in the chat response to identify the tool call. # Toggle auto-retrain Source: https://chatbase.co/docs/api-v2/agents/toggle-auto-retrain /api-v2-openapi.json put /agents/{agentId}/auto-retrain Enables or disables automatic retraining. When enabled, the agent retrains every 7 days to reflect any source changes. Requirements: - The agent must have been trained at least once — returns `AGENT_NOT_TRAINED` (409) otherwise. - Requires the Standard plan or higher — returns `PLAN_FEATURE_NOT_AVAILABLE` (403) on unsupported plans. # Train agent (deprecated) Source: https://chatbase.co/docs/api-v2/agents/train-agent-deprecated /api-v2-openapi.json post /agents/{agentId}/train **Deprecated.** Sources train as soon as they are created, updated or deleted, so changes take effect immediately and there is nothing to trigger. The call does nothing and always succeeds. Poll `GET /agents/{agentId}/sources/{sourceId}` and read `status` to follow a source, or `GET /agents/{agentId}` for the agent-level `status`. # Update agent Source: https://chatbase.co/docs/api-v2/agents/update-agent /api-v2-openapi.json put /agents/{agentId} Partially updates an agent. Only provided fields are changed. # Update agent styles Source: https://chatbase.co/docs/api-v2/agents/update-agent-styles /api-v2-openapi.json put /agents/{agentId}/styles Updates the visual styles for an agent. # Authentication Source: https://chatbase.co/docs/api-v2/authentication How to authenticate with the Chatbase API v2 using Bearer tokens, and understand rate limiting. ## Bearer Token Authentication All API v2 endpoints (except the health check) require a Bearer token in the `Authorization` header: ``` Authorization: Bearer ``` ### Getting an API Key 1. Go to the [Chatbase Dashboard](https://www.chatbase.co/dashboard) 2. Go to **Workspace settings** → **API keys** 3. Click **Create API Key** 4. Copy and securely store the generated key API keys grant full access to your workspace. Never expose them in client-side code, public repositories, or browser network requests. API v2 requires a Chatbase Standard Plan or above. Requests from accounts on unsupported plans will be rejected. ### Example Request ```bash theme={null} curl -X POST 'https://www.chatbase.co/api/v2/agents/YOUR_AGENT_ID/chat' \ -H 'Authorization: Bearer YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{"message": "Hello!"}' ``` ## Rate Limiting The API enforces a rate limit of **100 requests per 10-second sliding window**, scoped per API key and IP address. ### Rate Limit Headers Every response includes rate limit headers so you can track your usage: | Header | Description | | ----------------------- | ----------------------------------------------------------------- | | `X-RateLimit-Limit` | Maximum number of requests allowed in the window (100). | | `X-RateLimit-Remaining` | Number of requests remaining in the current window. | | `X-RateLimit-Reset` | Unix timestamp in milliseconds when the current window resets. | | `Retry-After` | Seconds to wait before retrying. Only present on `429` responses. | ### Handling Rate Limits When you exceed the rate limit, the API returns a `429` status code: ```json theme={null} { "error": { "code": "RATE_LIMIT_TOO_MANY_REQUESTS", "message": "Too many requests, please try again later" } } ``` Use the `Retry-After` header to determine how long to wait before retrying: ```javascript theme={null} async function fetchWithRetry(url, options) { const response = await fetch(url, options); if (response.status === 429) { const retryAfter = parseInt(response.headers.get("Retry-After"), 10); await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000)); return fetchWithRetry(url, options); } return response; } ``` ## Request ID Every response includes an `x-request-id` header containing a unique identifier for the request. When contacting support about an error, always include this value to help with debugging. ``` x-request-id: req_a1b2c3d4e5f6 ``` # Client Actions Source: https://chatbase.co/docs/api-v2/client-actions Handle client-side actions invoked by your AI agent through the Chatbase API v2. ## What Are Client Actions? Client actions allow your AI agent to request that your application perform an action on the client side. When an AI agent determines it needs external information or wants to trigger an operation, it responds with a `finishReason` of `"tool-calls"` and includes `tool-call` parts describing what it needs. Your application executes the action, submits the result back to the API, and then continues the conversation. Client actions correspond to the **Custom Actions** configured on your AI agent in the Chatbase dashboard. The `toolName` in the API response is the name of the configured action. ## Flow ```mermaid theme={null} sequenceDiagram participant App as Your App participant API as Chatbase API participant Agent as AI Agent App->>API: POST /chat (message) API->>Agent: Process message Agent-->>API: Response with tool-call parts API-->>App: finishReason: "tool-calls" App->>App: Execute action client-side App->>API: POST /tool-result (toolCallId, output) API-->>App: { success: true } App->>API: POST /chat (continue conversation) API->>Agent: Process with tool result Agent-->>API: Final response API-->>App: finishReason: "stop" ``` Send a message to the chat endpoint as usual. The response has `finishReason: "tool-calls"` and `tool-call` parts containing `toolCallId`, `toolName`, and `input`. Use `toolName` and `input` to determine what to do and execute the action in your application. Send the result to `POST /agents/{agentId}/conversations/{conversationId}/tool-result` with the `toolCallId` and `output`. Call the chat endpoint again with the `conversationId`. You can omit `message` to let the AI agent continue based on the tool result alone, or include a new message. ## Message Parts Responses can include three types of parts in the `parts` array: Text content generated by the AI agent. Fields: `type`, `text` A client action the AI agent wants your app to execute. Fields: `type`, `toolCallId`, `toolName`, `input` The result of a previously executed client action (visible in conversation history). Fields: `type`, `toolCallId`, `toolName`, `output` ## Detecting a Client Action Check the `finishReason` in the response metadata. When it is `"tool-calls"`, the `parts` array will contain one or more `tool-call` entries: ```json theme={null} { "data": { "id": "msg_abc123", "role": "assistant", "parts": [ { "type": "text", "text": "Let me look up that order for you." }, { "type": "tool-call", "toolCallId": "call_abc123", "toolName": "lookupOrder", "input": { "orderId": "ORD-123" } } ], "metadata": { "userMessageId": "msg_xyz789", "conversationId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "userId": "user_abc123", "finishReason": "tool-calls", "usage": { "credits": 2 } } } } ``` ## Submitting the Result After executing the action, submit the result using the tool-result endpoint: ``` POST /api/v2/agents/{agentId}/conversations/{conversationId}/tool-result ``` ### Request Body The `toolCallId` from the `tool-call` part in the chat response. The result of executing the action. ### Response ```json theme={null} { "data": { "success": true } } ``` ## Continuing the Conversation After submitting the tool result, continue the conversation by calling the chat endpoint again. You can either: * **Omit `message`** to let the AI agent continue based on the tool result alone. * **Include a `message`** to provide additional context or a follow-up question. You must include the `conversationId` to continue the same conversation. ```bash theme={null} curl -X POST 'https://www.chatbase.co/api/v2/agents/YOUR_AGENT_ID/chat' \ -H 'Authorization: Bearer YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "conversationId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" }' ``` ## Streaming Client Actions When streaming is enabled, client action input arrives incrementally through these events: Signals the start of a client action. Includes `toolCallId` and `toolName`. Incremental chunks of the action input stream in. The complete input is ready. You can read the full `input` object directly from this event without concatenating the preceding deltas. The stream's `finish` event will have `finishReason: "tool-calls"`. See [Streaming](/docs/api-v2/streaming) for full event type reference. ## Code Examples ```javascript Node.js theme={null} // Step 1: Send a message const chatResponse = await fetch( "https://www.chatbase.co/api/v2/agents/YOUR_AGENT_ID/chat", { method: "POST", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ message: "What's the status of order ORD-123?", stream: false, }), } ); const { data } = await chatResponse.json(); const { conversationId, finishReason } = data.metadata; // Step 2: Check if a client action was invoked if (finishReason === "tool-calls") { for (const part of data.parts) { if (part.type === "tool-call") { // Step 3: Execute the action const result = await executeAction(part.toolName, part.input); // Step 4: Submit the result await fetch( `https://www.chatbase.co/api/v2/agents/YOUR_AGENT_ID/conversations/${conversationId}/tool-result`, { method: "POST", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ toolCallId: part.toolCallId, output: result, }), } ); } } // Step 5: Continue the conversation const continueResponse = await fetch( "https://www.chatbase.co/api/v2/agents/YOUR_AGENT_ID/chat", { method: "POST", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ conversationId, stream: false, }), } ); const continued = await continueResponse.json(); console.log(continued.data.parts); } // Your action handler async function executeAction(toolName, input) { switch (toolName) { case "lookupOrder": // Call your order service return { status: "shipped", eta: "2026-04-03" }; default: return { error: "Unknown action" }; } } ``` ```python Python theme={null} import requests API_KEY = "YOUR_API_KEY" AGENT_ID = "YOUR_AGENT_ID" BASE_URL = "https://www.chatbase.co/api/v2" HEADERS = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json", } # Step 1: Send a message chat_response = requests.post( f"{BASE_URL}/agents/{AGENT_ID}/chat", headers=HEADERS, json={ "message": "What's the status of order ORD-123?", "stream": False, }, ).json() data = chat_response["data"] conversation_id = data["metadata"]["conversationId"] finish_reason = data["metadata"]["finishReason"] # Step 2: Check if a client action was invoked if finish_reason == "tool-calls": for part in data["parts"]: if part["type"] == "tool-call": # Step 3: Execute the action result = execute_action(part["toolName"], part["input"]) # Step 4: Submit the result requests.post( f"{BASE_URL}/agents/{AGENT_ID}/conversations/{conversation_id}/tool-result", headers=HEADERS, json={ "toolCallId": part["toolCallId"], "output": result, }, ) # Step 5: Continue the conversation continued = requests.post( f"{BASE_URL}/agents/{AGENT_ID}/chat", headers=HEADERS, json={ "conversationId": conversation_id, "stream": False, }, ).json() for part in continued["data"]["parts"]: if part["type"] == "text": print(part["text"]) def execute_action(tool_name, tool_input): if tool_name == "lookupOrder": return {"status": "shipped", "eta": "2026-04-03"} return {"error": "Unknown action"} ``` ```bash curl theme={null} # Step 1: Send a message curl -X POST 'https://www.chatbase.co/api/v2/agents/YOUR_AGENT_ID/chat' \ -H 'Authorization: Bearer YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "message": "What is the status of order ORD-123?", "stream": false }' # Step 2: Submit the tool result (use toolCallId from the response) curl -X POST 'https://www.chatbase.co/api/v2/agents/YOUR_AGENT_ID/conversations/CONVERSATION_ID/tool-result' \ -H 'Authorization: Bearer YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "toolCallId": "call_abc123", "output": { "status": "shipped", "eta": "2026-04-03" } }' # Step 3: Continue the conversation curl -X POST 'https://www.chatbase.co/api/v2/agents/YOUR_AGENT_ID/chat' \ -H 'Authorization: Bearer YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "conversationId": "CONVERSATION_ID" }' ``` ## Error Handling | Code | Status | Description | | ------------------------------ | ------ | --------------------------------------------------------------------------------------------------------- | | `RESOURCE_TOOL_CALL_NOT_FOUND` | 404 | No pending client action matches the provided `toolCallId`. It may have expired or already been resolved. | | `VALIDATION_INVALID_BODY` | 400 | The request body failed schema validation. Check the `details` field for specifics. | # Export conversations Source: https://chatbase.co/docs/api-v2/conversations/export-conversations /api-v2-openapi.json get /agents/{agentId}/conversations/export Export all conversations with full message history for an agent. Includes conversations from all sources. Tool results are sanitized to remove internal data. Supports cursor-based pagination. Pass `conversationId` to fetch a single conversation from any source (widget, API, WhatsApp, etc.). Pass `include=summary` to omit message bodies for a cheaper triage pass, `source` to restrict to one or more conversation sources, and `startDate` / `endDate` to restrict to a createdAt window. # Get a conversation Source: https://chatbase.co/docs/api-v2/conversations/get-a-conversation /api-v2-openapi.json get /agents/{agentId}/conversations/{conversationId} Get conversation metadata and its most recent messages. The pagination cursor can be used with the list messages endpoint to fetch older messages. Only returns conversations created through the API. To fetch a conversation from any source (widget, WhatsApp, etc.), use GET /agents/{agentId}/conversations/export?conversationId={conversationId} instead. # List conversation messages Source: https://chatbase.co/docs/api-v2/conversations/list-conversation-messages /api-v2-openapi.json get /agents/{agentId}/conversations/{conversationId}/messages List all messages in a conversation with cursor-based pagination. Messages are returned in chronological order within each page, paginating backward from newest. The cursor from the get-conversation endpoint works here. # List conversations Source: https://chatbase.co/docs/api-v2/conversations/list-conversations /api-v2-openapi.json get /agents/{agentId}/conversations List conversations for an agent, ordered by createdAt date. Supports cursor-based pagination. Pass `startDate` and/or `endDate` to restrict the results to a createdAt window. # List conversations for a user Source: https://chatbase.co/docs/api-v2/conversations/list-conversations-for-a-user /api-v2-openapi.json get /agents/{agentId}/users/{userId}/conversations List conversations for a specific user under an agent, ordered by last activity. Supports cursor-based pagination. # Pause or resume a conversation Source: https://chatbase.co/docs/api-v2/conversations/pause-or-resume-a-conversation /api-v2-openapi.json patch /agents/{agentId}/conversations/{conversationId} Pause or resume an ongoing conversation. A paused conversation stops receiving AI replies but still records incoming messages. # Update message feedback Source: https://chatbase.co/docs/api-v2/conversations/update-message-feedback /api-v2-openapi.json patch /agents/{agentId}/conversations/{conversationId}/messages/{messageId}/feedback Set or clear feedback on an assistant message. Use "positive" or "negative" to set feedback, or null to remove existing feedback. # Error Handling Source: https://chatbase.co/docs/api-v2/error-handling Structured error codes and troubleshooting guide for the Chatbase API v2. ## Error Response Format All errors follow a consistent envelope format: ```json theme={null} { "error": { "code": "ERROR_CODE", "message": "Human-readable description", "details": {} } } ``` | Field | Type | Description | | --------- | -------- | ------------------------------------------------------------------------------- | | `code` | `string` | Machine-readable error code. Use this for programmatic handling. | | `message` | `string` | Human-readable description of the error. | | `details` | `object` | Optional. Field-level validation errors (present on `VALIDATION_INVALID_BODY`). | ## Error Codes
Code Description
VALIDATION\_INVALID\_BODYThe request body failed schema validation. Check the details field for specific field errors.
VALIDATION\_INVALID\_JSONThe request body is not valid JSON.
CHAT\_RETRY\_NO\_USER\_MESSAGEThe retry target message has no preceding user message to re-send.
AUTH\_MISSING\_API\_KEYNo Authorization header was provided.
AUTH\_INVALID\_API\_KEYThe API key is not valid.
AUTH\_EXPIRED\_API\_KEYThe API key has expired. Generate a new one from the dashboard.
CHAT\_CREDITS\_EXHAUSTEDThe workspace's message credit balance is zero. Upgrade the plan or wait for credits to reset.
CHAT\_AGENT\_CREDITS\_EXHAUSTEDThe specific AI agent's credit allocation has been used up.
SUBSCRIPTION\_API\_RESTRICTED\_PLANYour current plan does not include API access. A Standard Plan or above is required.
AUTH\_INSUFFICIENT\_PERMISSIONSThe API key does not have the required permissions for this operation.
CHAT\_MODEL\_NOT\_ALLOWEDThe AI agent is configured to use a model that is not available on the current plan.
CHAT\_CONVERSATION\_MISMATCHThe conversation does not belong to the specified AI agent.
CHAT\_CONVERSATION\_NOT\_ONGOINGThe conversation has ended or been taken over and cannot receive new messages.
RESOURCE\_NOT\_FOUNDThe requested resource does not exist.
RESOURCE\_TOOL\_CALL\_NOT\_FOUNDNo pending client action matches the provided toolCallId. It may have expired or already been resolved.
RESOURCE\_MESSAGE\_NOT\_FOUNDThe specified message was not found in the conversation.
RESOURCE\_MESSAGE\_NOT\_ASSISTANTOnly assistant messages support feedback and metadata updates.
CHAT\_RETRY\_MESSAGE\_NOT\_FOUNDThe message ID provided for retry was not found in the conversation.
RATE\_LIMIT\_TOO\_MANY\_REQUESTSRate limit exceeded. Check the Retry-After header for how long to wait. See Authentication for details.
INTERNAL\_SERVER\_ERRORAn unexpected error occurred. If this persists, contact support with the x-request-id header value.
CHAT\_STREAMING\_ERRORAn error occurred during stream generation. The stream may have been partially delivered.
SOURCE\_NOT\_FOUNDSource doesn't exist, belongs to a different AI agent, or has been permanently deleted.
SOURCE\_TYPE\_NOT\_SUPPORTEDAttempting to update a notionPage via PUT. Manage Notion sources through the dashboard integration.
SOURCE\_IS\_TRAININGThe source is still training. Wait for trained, then retry the edit.
SOURCE\_PENDING\_DELETIONThe source is being deleted and cannot be edited.
SOURCE\_ALREADY\_PENDING\_DELETIONDELETE was called on a source that is already being deleted.
SOURCE\_LINK\_LIMIT\_EXCEEDEDThe 15 crawl/sitemap-parent limit per AI agent has been reached.
SOURCE\_SIZE\_LIMIT\_EXCEEDEDCreating or updating this source would exceed the plan's storage limit.
SOURCE\_DUPLICATEA link source with this URL and linkType already exists for this AI agent.
SOURCE\_URL\_IMMUTABLEA link's URL cannot be changed via PUT. Delete and recreate the source to use a different URL.
AGENT\_NOT\_FOUNDAI agent doesn't exist or doesn't belong to the authenticated account.
AGENT\_NOT\_TRAINEDAuto-resync needs at least one live source.
AGENT\_LIMIT\_REACHEDThe account has reached its plan's maximum number of AI agents. Delete an existing AI agent or upgrade your plan.
PLAN\_FEATURE\_NOT\_AVAILABLEThe requested feature is not available on the current plan. Upgrade to unlock it.
**Example with field-level details (`VALIDATION_INVALID_BODY`):** ```json theme={null} { "error": { "code": "VALIDATION_INVALID_BODY", "message": "Invalid request", "details": { "message": "Required" } } } ``` ## Handling Errors ```javascript theme={null} const response = await fetch( "https://www.chatbase.co/api/v2/agents/YOUR_AGENT_ID/chat", { method: "POST", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ message: "Hello" }), } ); if (!response.ok) { const { error } = await response.json(); const requestId = response.headers.get("x-request-id"); switch (error.code) { case "RATE_LIMIT_TOO_MANY_REQUESTS": const retryAfter = response.headers.get("Retry-After"); // Wait and retry break; case "CHAT_CREDITS_EXHAUSTED": // Notify user about credit limit break; case "VALIDATION_INVALID_BODY": // Fix request based on error.details console.log("Validation errors:", error.details); break; default: console.error(`[${error.code}] ${error.message} (request: ${requestId})`); } } ``` # Health check Source: https://chatbase.co/docs/api-v2/health/health-check /api-v2-openapi.json get /health Returns the API health status. No authentication required. # Helpdesk Source: https://chatbase.co/docs/api-v2/helpdesk Programmatically create, triage, and reply to support tickets for your Chatbase AI agents. The Helpdesk API is the same ticketing that powers the Helpdesk tab in the dashboard. You can create tickets on behalf of customers, list and search them, and change a ticket's status, assignee, or team. Each ticket carries a message thread you can read and reply to. Tickets are numbered per AI agent. The `ticketNumber` path parameter is that per-agent number, not a global id. A ticket's `channel` records where it originated, such as email, the chat widget, or WhatsApp. Tickets created through this API always have `channel: "api"`. ## Statuses Each ticket has a status, and each status belongs to one of six fixed categories: `new`, `on_you`, `on_customer`, `on_hold`, `closed`, `cancelled`. The statuses themselves are configured per agent in the dashboard; every category has exactly one default status. Write endpoints accept a status in one of two forms, at most one per request: | Field | Meaning | | ---------------- | -------------------------------------------------------------------------------------------------------- | | `statusId` | A specific configured status. Must belong to the AI agent and be active; archived statuses are rejected. | | `statusCategory` | Resolves to that category's default status. | [List ticket statuses](/docs/api-v2/helpdesk/list-ticket-statuses) returns the AI agent's active statuses with their ids, categories, and labels. Each status has two labels: `externalLabel` is what the customer sees, `internalLabel` is what the dashboard shows. ## Assignment and routing When you create a ticket, the assignee fields decide whether auto-assignment runs. `assigneeId` and `assigneeEmail` are a mutually exclusive pair; sending both is a 400. | Assignee | `teamId` | Result | | ------------------ | -------- | ------------------------------------------------------------------------------------------ | | Provided | Any | Written as given. No auto-assignment. | | `assigneeId: null` | Any | Ticket is created unassigned. The team is still written if given. | | Omitted | Provided | An agent is picked within that team by its assignment strategy. Routing rules are skipped. | | Omitted | Omitted | Routing rules pick both the team and the assignee. | [Update a ticket](/docs/api-v2/helpdesk/update-a-ticket) never auto-assigns. Omitted fields keep their current value, `assigneeId: null` unassigns the ticket, and `teamId: null` clears the team. [List teams](/docs/api-v2/helpdesk/list-teams) returns the AI agent's teams; exactly one is the default. ## Messages A ticket's thread contains three message types: | Type | Meaning | | ------- | ------------------------------------------------------------------------------------------------------------------------------ | | `reply` | Customer-visible. Delivered over the ticket's origin channel. | | `note` | Internal. Never delivered to the customer. | | `event` | System record of a change, such as a status transition or assignment. Excluded from list results unless requested via `types`. | [Add a message to a ticket](/docs/api-v2/helpdesk/add-a-message-to-a-ticket) currently accepts only `type: "reply"`, attributed to a team member via `authorId` or `authorEmail`. The body is GitHub-flavored Markdown; raw HTML is stripped. Delivery to the customer is asynchronous, so a 201 means the reply was recorded, not that it reached the customer. ## Endpoints Filterable, sortable, paginated list Free-text search over ticket messages Open a ticket on behalf of a customer Retrieve a single ticket by number Change status, assignee, or team Read a ticket's thread Post an agent reply The AI agent's teams and the default Configured statuses with ids and labels ## Error codes Helpdesk-specific error codes beyond the standard [authentication and rate-limiting errors](/docs/api-v2/error-handling):
Code HTTP Description
TICKET\_NOT\_FOUND404No ticket matches this number for the AI agent.
CONVERSATION\_NOT\_TAKEN\_OVER409The ticket is linked to a live conversation that has not been taken over from the AI agent, so a human reply cannot be posted. Take over the conversation from the dashboard first.
MESSAGE\_CONTENT\_NOT\_RENDERABLE422The message body rendered to empty HTML. This happens when it consists only of raw HTML, which is stripped. Send Markdown or plain text.
TICKET\_INVALID\_STATUS422statusId does not belong to a status for this AI agent.
TICKET\_ARCHIVED\_STATUS422statusId refers to an archived status, which cannot be applied.
TICKET\_TEAM\_MEMBER\_NOT\_FOUND422Neither assigneeId nor assigneeEmail resolved to a team member on this account.
TICKET\_TEAM\_NOT\_FOUND422teamId does not belong to a team for this AI agent.
TEAM\_MEMBER\_NOT\_FOUND422On message creation, neither authorId nor authorEmail resolved to a team member on this account.
# Add a message to a ticket Source: https://chatbase.co/docs/api-v2/helpdesk/add-a-message-to-a-ticket /api-v2-openapi.json post /agents/{agentId}/helpdesk/tickets/{ticketNumber}/messages Posts an agent reply to a ticket on behalf of a team member. Delivery to the customer is asynchronous; a 201 confirms the reply was recorded, not delivered. Posting a reply may transition the ticket status, matching dashboard behavior. # Create a ticket Source: https://chatbase.co/docs/api-v2/helpdesk/create-a-ticket /api-v2-openapi.json post /agents/{agentId}/helpdesk/tickets Creates a ticket on behalf of a customer. Unless an assignee is provided, the ticket is auto-assigned via the agent's routing rules. # Get a ticket Source: https://chatbase.co/docs/api-v2/helpdesk/get-a-ticket /api-v2-openapi.json get /agents/{agentId}/helpdesk/tickets/{ticketNumber} Returns a single ticket by its per-agent ticket number. # List teams Source: https://chatbase.co/docs/api-v2/helpdesk/list-teams /api-v2-openapi.json get /agents/{agentId}/helpdesk/teams Returns the teams configured for an agent, ordered by creation date. Exactly one team is marked as the default for the agent. # List ticket messages Source: https://chatbase.co/docs/api-v2/helpdesk/list-ticket-messages /api-v2-openapi.json get /agents/{agentId}/helpdesk/tickets/{ticketNumber}/messages Returns a ticket's message thread in chronological order. Supports cursor-based pagination. # List ticket statuses Source: https://chatbase.co/docs/api-v2/helpdesk/list-ticket-statuses /api-v2-openapi.json get /agents/{agentId}/helpdesk/ticket-statuses Returns the active (non-archived) ticket statuses configured for an agent, ordered by category then position. Each category has exactly one default status. # List tickets Source: https://chatbase.co/docs/api-v2/helpdesk/list-tickets /api-v2-openapi.json get /agents/{agentId}/helpdesk/tickets Returns tickets for an agent, sorted by `updatedAt` descending by default. Supports filtering and cursor-based pagination. Filters combine with AND across parameters. # Search tickets Source: https://chatbase.co/docs/api-v2/helpdesk/search-tickets /api-v2-openapi.json post /agents/{agentId}/helpdesk/tickets/search Searches ticket messages with a free-text query and returns matching tickets ranked by relevance. Results are capped and not paginated. # Update a ticket Source: https://chatbase.co/docs/api-v2/helpdesk/update-a-ticket /api-v2-openapi.json patch /agents/{agentId}/helpdesk/tickets/{ticketNumber} Partially updates a ticket's status, assignee, team, or priority. Only provided fields are changed. Fields are validated together but written independently, so a 500 can leave a partial update. # API v2 Overview Source: https://chatbase.co/docs/api-v2/overview Introduction to the Chatbase API v2 — a structured, modern REST API for chatting with AI agents and managing conversations. **Standard Plan required.** The Chatbase API v2 is available starting from the Standard Plan. [View pricing →](https://www.chatbase.co/pricing) Prefer the terminal? Use the [Chatbase CLI](/docs/cli/overview) for the same API v2 surface from your shell or CI. Most conversation endpoints apply exclusively to conversations created programmatically via the Chatbase API. Conversations generated through the bubble or external integrations cannot be accessed using these endpoints. The exception is the [Export conversations](/docs/api-v2/conversations/export-conversations) endpoint, which returns conversations from **all sources**, including a single conversation when you pass `conversationId`. ## What is the Chatbase API v2? The Chatbase API v2 is a redesigned REST API that provides a clean, consistent interface for interacting with your AI agents. It features structured error codes, cursor-based pagination, streaming support via Server-Sent Events, and a predictable response format. **Base URL:** ``` https://www.chatbase.co/api/v2 ``` ## Quick Start 1. Go to the [Chatbase Dashboard](https://www.chatbase.co/dashboard) 2. Go to **Workspace settings** → **API keys** 3. Click **Create API Key** and copy the generated key Store your API key securely. Never expose it in client-side code. 1. Select your AI Agent in the dashboard 2. Go to the AI agent’s **Settings** → **General** 3. Copy the **Agent ID** from the **Agent details** card ```bash theme={null} curl -X POST 'https://www.chatbase.co/api/v2/agents/YOUR_AGENT_ID/chat' \ -H 'Authorization: Bearer YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "message": "Hello! How can you help me?" }' ``` **Response:** ```json theme={null} { "data": { "id": "msg_abc123", "role": "assistant", "parts": [ { "type": "text", "text": "Hello! I'm here to help. What can I assist you with today?" } ], "metadata": { "userMessageId": "msg_xyz789", "conversationId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "userId": null, "finishReason": "stop", "usage": { "credits": 2 } } } } ``` ## Endpoints **Base URL:** `https://www.chatbase.co/api/v2` | Method | Endpoint | Notes | | -------- | --------------------------------------------------------------------------------------- | ---------------------- | | `GET` | `/api/v2/health` | | | `GET` | `/api/v2/agents` | | | `POST` | `/api/v2/agents` | | | `GET` | `/api/v2/agents/{agentId}` | | | `PUT` | `/api/v2/agents/{agentId}` | | | `DELETE` | `/api/v2/agents/{agentId}` | | | `PUT` | `/api/v2/agents/{agentId}/styles` | | | `PUT` | `/api/v2/agents/{agentId}/auto-retrain` | (auto-resync schedule) | | `POST` | `/api/v2/agents/{agentId}/clone` | | | `POST` | `/api/v2/agents/{agentId}/train` | (deprecated, no-op) | | `POST` | `/api/v2/agents/{agentId}/chat` | | | `POST` | `/api/v2/agents/{agentId}/conversations/{conversationId}/retry` | | | `GET` | `/api/v2/agents/{agentId}/conversations` | | | `GET` | `/api/v2/agents/{agentId}/conversations/export` | | | `GET` | `/api/v2/agents/{agentId}/conversations/{conversationId}` | | | `GET` | `/api/v2/agents/{agentId}/conversations/{conversationId}/messages` | | | `GET` | `/api/v2/agents/{agentId}/users/{userId}/conversations` | | | `POST` | `/api/v2/agents/{agentId}/conversations/{conversationId}/tool-result` | | | `PATCH` | `/api/v2/agents/{agentId}/conversations/{conversationId}/messages/{messageId}/feedback` | | | `GET` | `/api/v2/agents/{agentId}/sources/summary` | | | `GET` | `/api/v2/agents/{agentId}/sources` | | | `POST` | `/api/v2/agents/{agentId}/sources` | | | `GET` | `/api/v2/agents/{agentId}/sources/{sourceId}` | | | `PUT` | `/api/v2/agents/{agentId}/sources/{sourceId}` | | | `DELETE` | `/api/v2/agents/{agentId}/sources/{sourceId}` | | | `POST` | `/api/v2/agents/{agentId}/sources/{sourceId}/restore` | (deprecated, no-op) | **File upload base URL:** `https://files.chatbase.co/api/v2` | Method | Endpoint | Notes | | ------ | --------------------------------------------- | ----- | | `POST` | `/api/v2/agents/{agentId}/sources` | | | `PUT` | `/api/v2/agents/{agentId}/sources/{sourceId}` | | ## Response Headers Every response includes these headers: | Header | Description | | ----------------------- | ---------------------------------------------------------------- | | `x-request-id` | Unique request identifier. Include this when contacting support. | | `X-RateLimit-Limit` | Maximum requests allowed in the current window. | | `X-RateLimit-Remaining` | Requests remaining in the current window. | | `X-RateLimit-Reset` | Unix timestamp (ms) when the rate limit window resets. | ## Next Steps API keys, Bearer tokens, and rate limiting Real-time SSE streaming responses Structured error codes and troubleshooting Cursor-based pagination for list endpoints # Pagination Source: https://chatbase.co/docs/api-v2/pagination How cursor-based pagination works in the Chatbase API v2. ## How It Works The API v2 uses **cursor-based pagination** for all list endpoints. Cursors are opaque, base64-encoded strings — treat them as opaque tokens and do not attempt to decode or construct them. ### Query Parameters | Parameter | Type | Default | Description | | --------- | --------- | ------- | ------------------------------------------------------------------------- | | `cursor` | `string` | — | Opaque cursor from a previous response. Omit to start from the beginning. | | `limit` | `integer` | `20` | Number of items per page. Range: 1–100. | ### Response Shape All paginated responses follow this structure: ```json theme={null} { "data": [...], "pagination": { "cursor": "eyJ0IjoiMjAyNC0wMS0xNVQxMDozMDowMC4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", "hasMore": true, "total": 142 } } ``` | Field | Type | Description | | -------------------- | ---------------- | ------------------------------------------------------------------------ | | `data` | `array` | The page of results. | | `pagination.cursor` | `string \| null` | Cursor to pass for the next page. `null` when there are no more results. | | `pagination.hasMore` | `boolean` | `true` if more results are available beyond this page. | | `pagination.total` | `integer` | Total number of items matching the query. | ## Paginating Through All Results Pass the `cursor` from each response into the next request to iterate through all pages: ```javascript Node.js theme={null} async function fetchAllConversations(agentId, apiKey) { const conversations = []; let cursor = undefined; do { const params = new URLSearchParams({ limit: "100" }); if (cursor) params.set("cursor", cursor); const response = await fetch( `https://www.chatbase.co/api/v2/agents/${agentId}/conversations?${params}`, { headers: { Authorization: `Bearer ${apiKey}` }, } ); const { data, pagination } = await response.json(); conversations.push(...data); cursor = pagination.cursor; } while (cursor); return conversations; } ``` ```python Python theme={null} import requests def fetch_all_conversations(agent_id: str, api_key: str): conversations = [] cursor = None while True: params = {"limit": 100} if cursor: params["cursor"] = cursor response = requests.get( f"https://www.chatbase.co/api/v2/agents/{agent_id}/conversations", headers={"Authorization": f"Bearer {api_key}"}, params=params, ) body = response.json() conversations.extend(body["data"]) cursor = body["pagination"]["cursor"] if not cursor: break return conversations ``` ## Export Pagination The export endpoint (`GET /api/v2/agents/{agentId}/conversations/export`) paginates through **all conversations** (from every source) with full message history included. Each page returns up to `limit` conversations with their complete messages already embedded — no separate call to a messages endpoint is needed. Because each exported conversation includes all of its messages, pages can be significantly larger than other paginated responses. Use a smaller `limit` if you want to keep response sizes manageable. ## Message Pagination The messages endpoint (`GET /api/v2/agents/{agentId}/conversations/{conversationId}/messages`) paginates **backward from the newest messages**. Within each page, messages are returned in chronological order. This means: * The first page contains the most recent messages * Passing the `cursor` fetches the next older page * Each page's messages are ordered oldest → newest The cursor returned by the [Get a conversation](/docs/api-v2/conversations/get-a-conversation) endpoint is compatible with the messages endpoint, so you can use it to fetch older messages beyond what the conversation response includes. # Sources Source: https://chatbase.co/docs/api-v2/sources Programmatically manage the knowledge sources that power your Chatbase AI agent — web pages, documents, Q&A pairs, and text. The Sources API lets you manage the content your AI agent is trained on. You can list, inspect, create, update, and delete sources without touching the dashboard. Sources train on write. Creating, updating, or deleting a source takes effect on its own within seconds. There is no separate train call. ## Hostname routing **File upload operations use a different base URL from all other endpoints.** | Operation | Base URL | | ---------------------------------------- | ---------------------------------- | | All read operations and JSON-body writes | `https://www.chatbase.co/api/v2` | | Create or update **file** sources | `https://files.chatbase.co/api/v2` | Using the wrong host for file uploads will return a 404. ## Source types | Type | List | Get | Create | Update | Delete | | | ------------ | ---- | --- | ------ | ------ | ------ | - | | `text` | ✓ | ✓ | ✓ | ✓ | ✓ | | | `qna` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `link` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `file` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | `notionPage` | ✓ | ✓ | ✗ | ✗ | ✓ | ✓ | Notion pages appear in list and get results, but cannot be created or updated via the API. Manage Notion sources through the Notion integration in the dashboard. ## Source status Every source has a `status` field. It is the completion signal: poll `GET /agents/{agentId}/sources/{sourceId}` until it reads `trained` or `failed`. | Status | Meaning | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `untrained` | Just created. Training has started and usually finishes within seconds. | | `trained` | Live. The AI agent answers from this content. | | `updated` | Content changed. The new version is training; the old version stays live until it lands. | | `toBeDeleted` | Delete in progress. The row disappears once the purge finishes, normally before the DELETE response returns. | | `failed` | The last training run did not land. An earlier trained version, if any, stays live. Fix the source (or re-save it) to retry; the dashboard shows the reason. | | `deleted` | Removed. Returned only by the DELETE response; never appears in list or get results. | `shouldRetrain: true` from [Get sources summary](/docs/api-v2/sources/get-sources-summary) means at least one source is still `untrained`, `updated`, or `toBeDeleted`. Nothing to do; wait for them to settle. ## Editing a source that is training A source can be edited by one writer at a time. `PUT` while it is still training returns `409 SOURCE_IS_TRAINING`. Wait for `trained`, then retry. Delete is always accepted. ## Deletes are permanent `DELETE` removes the source and its knowledge immediately. There is no pending state and no undo. The restore endpoint is deprecated and returns a no-op success. ## File upload rate limit File uploads are limited to 10 per minute per account. Space uploads at least 6 seconds apart. A `429` counts toward the window, so retrying into it keeps it closed; wait, do not retry in a tight loop. ## Endpoints Paginated list with optional type and name filters Aggregate counts and sizes per source type Retrieve a single source by ID Create text, Q\&A, and link sources Upload PDF, DOCX, or TXT files Update text, Q\&A, and link sources Replace file content or rename a file source Permanently remove a source and its knowledge No-op. Deletes are final. ## Error codes Sources-specific error codes beyond the standard [authentication and rate-limiting errors](/docs/api-v2/error-handling):
Code HTTP Description
SOURCE\_NOT\_FOUND404Source doesn't exist, belongs to a different AI agent, or has been permanently deleted.
SOURCE\_TYPE\_NOT\_SUPPORTED400Attempting to update a notionPage via PUT. Manage Notion sources through the dashboard.
SOURCE\_IS\_TRAINING409The source is still training. Wait for trained, then retry the edit.
SOURCE\_PENDING\_DELETION409The source is being deleted. It cannot be edited.
SOURCE\_ALREADY\_PENDING\_DELETION409DELETE was called on a source that is already being deleted.
SOURCE\_LINK\_LIMIT\_EXCEEDED422The 15 crawl/sitemap-parent limit per AI agent has been reached. Delete an existing crawl or sitemap source before adding another.
SOURCE\_SIZE\_LIMIT\_EXCEEDED422Creating or updating this source would exceed the plan's storage limit. Remove existing sources or upgrade your plan.
SOURCE\_DUPLICATE409A link source with this URL and linkType already exists for this AI agent.
SOURCE\_URL\_IMMUTABLE400A link's URL cannot be changed via PUT. Delete and recreate the source to use a different URL.
# Create file source Source: https://chatbase.co/docs/api-v2/sources/create-file-source /api-v2-openapi.json post /api/v2/agents/{agentId}/sources Upload a file as a knowledge source for an agent. Accepts PDF, DOC, DOCX, and TXT files up to 20 MB. **Base URL:** `https://files.chatbase.co/api/v2` — this endpoint uses a different host from all other Sources endpoints. # Create source Source: https://chatbase.co/docs/api-v2/sources/create-source /api-v2-openapi.json post /agents/{agentId}/sources Creates a new source. Accepts text, qna, and link source types. File sources require a dedicated endpoint. Ticket and Notion sources are not accepted. The source starts training immediately — no separate training call is needed. It is returned as `untrained` and flips to `trained` when it is live, or `failed` if training did not land; poll `GET /agents/{agentId}/sources/{sourceId}` to follow it. **Q&A request body limit:** The total request body must not exceed 4.5 MB for Q&A sources. # Delete source Source: https://chatbase.co/docs/api-v2/sources/delete-source /api-v2-openapi.json delete /agents/{agentId}/sources/{sourceId} Deletes a source. Its knowledge is removed from the agent immediately; the response carries the final state (`deleted`, or `toBeDeleted` while the purge finishes). # Get source Source: https://chatbase.co/docs/api-v2/sources/get-source /api-v2-openapi.json get /agents/{agentId}/sources/{sourceId} Returns a single source by ID. # Get sources summary Source: https://chatbase.co/docs/api-v2/sources/get-sources-summary /api-v2-openapi.json get /agents/{agentId}/sources/summary Returns aggregated counts and sizes for each source type, plus a flag if the chatbot knowledge base requires a retrain to reflect any changes # List sources Source: https://chatbase.co/docs/api-v2/sources/list-sources /api-v2-openapi.json get /agents/{agentId}/sources Returns a paginated list of sources for an agent. Ticket sources are excluded. For link sources only individual or sitemap/crawl parent links are returned with aggregated children metadata. # Restore source (deprecated) Source: https://chatbase.co/docs/api-v2/sources/restore-source-deprecated /api-v2-openapi.json post /agents/{agentId}/sources/{sourceId}/restore **Deprecated.** A delete takes effect immediately — the knowledge is purged right away, so there is no pending state to restore from. The call does nothing and always succeeds. Re-create the source instead. # Update file source Source: https://chatbase.co/docs/api-v2/sources/update-file-source /api-v2-openapi.json put /api/v2/agents/{agentId}/sources/{sourceId} Replace a file source's content, rename it, or both. At least one of `name` or `file` must be provided. **Base URL:** `https://files.chatbase.co/api/v2` — this endpoint uses a different host from all other Sources endpoints. # Update source Source: https://chatbase.co/docs/api-v2/sources/update-source /api-v2-openapi.json put /agents/{agentId}/sources/{sourceId} Updates an existing source. Accepts text, qna, and link sources. File sources require a dedicated endpoint. A content change retrains the source immediately (status `updated` until it is live again). Link URL is immutable — to change it, delete and recreate the source. Ticket and Notion sources are not accepted. **Q&A request body limit:** The total request body must not exceed 4.5 MB for Q&A sources. # Streaming Source: https://chatbase.co/docs/api-v2/streaming How to use Server-Sent Events (SSE) streaming with the Chatbase API v2 for real-time responses. ## Enabling Streaming To receive a streaming response, set `stream: true` in the request body of the chat or retry endpoints: ```bash theme={null} curl -X POST 'https://www.chatbase.co/api/v2/agents/YOUR_AGENT_ID/chat' \ -H 'Authorization: Bearer YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "message": "Explain quantum computing", "stream": true, "conversationId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "userId": "user_abc123" }' ``` ## Request Body The user message to send to the AI agent. Omit to continue the conversation after submitting a client action result. Stream the response as SSE. Defaults to `true`. Continue an existing conversation. Omit to create a new one. Associate a user with a new conversation. Max 128 chars, `[a-zA-Z0-9._-]` only. Ignored when `conversationId` is provided. Once set, a conversation's `userId` is immutable — it cannot be changed or removed. See [User Conversations](/docs/api-v2/user-conversations) for details on managing per-user conversation history. The response uses `Content-Type: text/event-stream` and follows the **AI SDK UIMessage Stream** protocol. Events arrive as Server-Sent Events — each event is a `data:` line whose payload is a JSON object with a `type` field, and the stream terminates with `data: [DONE]`: ``` data: {"type":"start","messageId":"msg_abc123"} data: {"type":"text-delta","id":"text_001","delta":"Hello"} data: [DONE] ``` ## Event Types ### `start` Emitted once at the beginning of a new message. Contains the message ID. ```json theme={null} { "type": "start", "messageId": "msg_abc123" } ``` ### `text-start` Emitted at the beginning of a text block. ```json theme={null} { "type": "text-start", "id": "text_001" } ``` ### `text-delta` Emitted for each chunk of generated text. Concatenate all deltas to build the full response. ```json theme={null} { "type": "text-delta", "id": "text_001", "delta": "Quantum computing is" } ``` ### `text-end` Emitted when a text block is complete. ```json theme={null} { "type": "text-end", "id": "text_001" } ``` These events are emitted when the AI agent invokes a [client action](/docs/api-v2/client-actions). The `toolName` corresponds to the name of the configured action. ### `tool-input-start` Emitted at the start of a client action input. ```json theme={null} { "type": "tool-input-start", "toolCallId": "call_abc123", "toolName": "lookupOrder" } ``` ### `tool-input-delta` Emitted for each chunk of the action input as it streams. ```json theme={null} { "type": "tool-input-delta", "toolCallId": "call_abc123", "inputTextDelta": "{\"order" } ``` ### `tool-input-available` Emitted when the complete action input is ready. You can read the full `input` object directly from this event without concatenating the preceding deltas. ```json theme={null} { "type": "tool-input-available", "toolCallId": "call_abc123", "toolName": "lookupOrder", "input": { "orderId": "ORD-123" } } ``` ### `tool-output-available` Emitted when a tool execution result is available. The full `output` can be read directly from this event. ```json theme={null} { "type": "tool-output-available", "toolCallId": "call_abc123", "output": { "status": "shipped", "eta": "2026-04-03" } } ``` For the full client action flow — including how to submit results and continue the conversation — see [Client Actions](/docs/api-v2/client-actions). ### `start-step` Emitted at the start of a processing step. ```json theme={null} { "type": "start-step" } ``` ### `finish-step` Emitted at the end of a processing step. ```json theme={null} { "type": "finish-step" } ``` ### `finish` Emitted once when generation is complete. Carries the finish reason and Chatbase-specific metadata — see [Metadata](#metadata). ```json theme={null} { "type": "finish", "finishReason": "stop", "messageMetadata": { "messageId": "msg_abc123", "userMessageId": "msg_xyz789", "conversationId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "userId": "user_abc123", "usage": { "credits": 2 } } } ``` ### `error` Emitted if an error occurs during generation. The stream may have been partially delivered. ```json theme={null} { "type": "error", "errorText": "An error occurred during generation" } ``` ## Metadata The `finish` event carries the finish reason and, nested under `messageMetadata`, the Chatbase-specific metadata: ```json theme={null} { "type": "finish", "finishReason": "stop", "messageMetadata": { "messageId": "msg_abc123", "userMessageId": "msg_xyz789", "conversationId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "userId": "user_abc123", "usage": { "credits": 2 } } } ``` Why the model stopped generating: * `"stop"` — normal completion * `"error"` — an error occurred * `"tool-calls"` — the AI agent invoked a client action — submit the result and continue Other values (`"length"`, `"content-filter"`, `"other"`, `"unknown"`) are possible but rare. Unique ID of the assistant message. The ID of the user message that triggered this response. For continuation responses, this is the last user message in the conversation. The conversation ID. Use this for follow-up messages. The user ID associated with this conversation, or `null` if none. Credits consumed by this request. The protocol also allows standalone `message-metadata` events mid-stream, with the same object nested under a `messageMetadata` key. Handle them if present, but expect the metadata on the `finish` event. The stream terminates with `data: [DONE]`. ## Code Examples ```javascript Node.js theme={null} const response = await fetch( "https://www.chatbase.co/api/v2/agents/YOUR_AGENT_ID/chat", { method: "POST", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ message: "Explain quantum computing", stream: true, // conversationId: "a1b2c3d4-...", // omit to start a new conversation // userId: "user_abc123", // associate a user with the conversation }), } ); const reader = response.body.getReader(); const decoder = new TextDecoder(); let conversationId; let userId; while (true) { const { done, value } = await reader.read(); if (done) break; const lines = decoder.decode(value, { stream: true }).split("\n"); for (const line of lines) { if (!line.startsWith("data: ")) continue; const data = line.slice("data: ".length); if (data === "[DONE]") continue; const event = JSON.parse(data); switch (event.type) { case "start": console.log("Message ID:", event.messageId); break; case "text-delta": process.stdout.write(event.delta); break; case "tool-input-available": console.log("\nClient action requested:", event.toolName, event.input); // Handle client action — see Client Actions guide break; case "finish": conversationId = event.messageMetadata.conversationId; userId = event.messageMetadata.userId; console.log("\nFinish reason:", event.finishReason); console.log("Credits used:", event.messageMetadata.usage.credits); break; case "error": console.error("Stream error:", event.errorText); break; } } } ``` ```python Python theme={null} import requests import json response = requests.post( "https://www.chatbase.co/api/v2/agents/YOUR_AGENT_ID/chat", headers={ "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, json={ "message": "Explain quantum computing", "stream": True, # "conversationId": "a1b2c3d4-...", # omit to start a new conversation # "userId": "user_abc123", # associate a user with the conversation }, stream=True, ) conversation_id = None user_id = None for line in response.iter_lines(): if not line: continue line = line.decode() if not line.startswith("data: "): continue data = line[len("data: "):] if data == "[DONE]": continue event = json.loads(data) if event["type"] == "start": print(f"Message ID: {event['messageId']}") elif event["type"] == "text-delta": print(event["delta"], end="", flush=True) elif event["type"] == "tool-input-available": print(f"\nClient action requested: {event['toolName']}", event["input"]) # Handle client action — see Client Actions guide elif event["type"] == "finish": metadata = event["messageMetadata"] conversation_id = metadata["conversationId"] user_id = metadata["userId"] print(f"\nFinish reason: {event['finishReason']}") print(f"Credits used: {metadata['usage']['credits']}") elif event["type"] == "error": print(f"Stream error: {event['errorText']}") ``` ```bash curl theme={null} curl -N -X POST 'https://www.chatbase.co/api/v2/agents/YOUR_AGENT_ID/chat' \ -H 'Authorization: Bearer YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "message": "Explain quantum computing", "stream": true, "conversationId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "userId": "user_abc123" }' ``` ## Non-Streaming Mode When `stream` is set to `false`, the API returns a standard JSON response with the complete message: ```json theme={null} { "data": { "id": "msg_abc123", "role": "assistant", "parts": [ { "type": "text", "text": "Quantum computing is a type of computation..." } ], "metadata": { "userMessageId": "msg_xyz789", "conversationId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "userId": "user_abc123", "finishReason": "stop", "usage": { "credits": 2 } } } } ``` When a client action is invoked, the response includes `tool-call` parts and `finishReason: "tool-calls"`: ```json theme={null} { "data": { "id": "msg_abc123", "role": "assistant", "parts": [ { "type": "text", "text": "Let me look up that order for you." }, { "type": "tool-call", "toolCallId": "call_abc123", "toolName": "lookupOrder", "input": { "orderId": "ORD-123" } } ], "metadata": { "userMessageId": "msg_xyz789", "conversationId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "userId": "user_abc123", "finishReason": "tool-calls", "usage": { "credits": 2 } } } } ``` See [Client Actions](/docs/api-v2/client-actions) for how to submit the result and continue the conversation. # User Conversations Source: https://chatbase.co/docs/api-v2/user-conversations Associate users with conversations, manage per-user history, and export full conversation data across all sources. ## Overview Tag conversations with a `userId` to track per-user chat history. Once a conversation is associated with a user, you can list all of that user's conversations and continue any of them by passing the `conversationId`. ## Setting a User ID Pass `userId` when creating a new conversation. The ID must follow these constraints: | Constraint | Value | | ------------------ | -------------------------------------------------------------- | | Max length | 128 characters | | Allowed characters | `a-z`, `A-Z`, `0-9`, `.`, `_`, `-` | | When applied | Only on conversation creation (no `conversationId` in request) | | Mutability | Immutable — cannot be changed or removed after creation | A conversation's `userId` is set once at creation and cannot be changed. If you send `userId` with a `conversationId`, the `userId` field is ignored. ```javascript Node.js theme={null} const response = await fetch( "https://www.chatbase.co/api/v2/agents/YOUR_AGENT_ID/chat", { method: "POST", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ message: "Hello!", stream: true, userId: "user_abc123", }), } ); ``` ```python Python theme={null} import requests response = requests.post( "https://www.chatbase.co/api/v2/agents/YOUR_AGENT_ID/chat", headers={ "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, json={ "message": "Hello!", "stream": True, "userId": "user_abc123", }, stream=True, ) ``` The response metadata includes the `userId`: ```json theme={null} { "type": "finish", "finishReason": "stop", "messageMetadata": { "messageId": "msg_abc123", "userMessageId": "msg_xyz789", "conversationId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "userId": "user_abc123", "usage": { "credits": 2 } } } ``` ## Continuing a Conversation To send follow-up messages in the same conversation, pass the `conversationId` from a previous response: ```javascript Node.js theme={null} const response = await fetch( "https://www.chatbase.co/api/v2/agents/YOUR_AGENT_ID/chat", { method: "POST", headers: { Authorization: "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ message: "Tell me more about that.", stream: true, conversationId: "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", }), } ); ``` ```python Python theme={null} response = requests.post( "https://www.chatbase.co/api/v2/agents/YOUR_AGENT_ID/chat", headers={ "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json", }, json={ "message": "Tell me more about that.", "stream": True, "conversationId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", }, stream=True, ) ``` The `conversationId` is returned in the streaming `finish` event's `metadata` or in the non-streaming response's `metadata` object. See [Streaming](/docs/api-v2/streaming) for details. If the conversation has ended (e.g. after a human takeover), the API returns a `CHAT_CONVERSATION_NOT_ONGOING` error. Start a new conversation instead. ## Listing a User's Conversations Retrieve all conversations for a specific user with `GET /api/v2/agents/{agentId}/users/{userId}/conversations`. ### Path Parameters | Parameter | Type | Description | | --------- | -------- | -------------------------------------- | | `agentId` | `string` | The agent ID. | | `userId` | `string` | The user ID to list conversations for. | ### Query Parameters | Parameter | Type | Default | Description | | --------- | --------- | ------- | ------------------------------------------------------------------------- | | `cursor` | `string` | — | Opaque cursor from a previous response. Omit to start from the beginning. | | `limit` | `integer` | `20` | Number of items per page. Range: 1–100. | ### Response ```json theme={null} { "data": [ { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "title": "Quantum computing basics", "createdAt": 1770681600, "updatedAt": 1770681900, "userId": "user_abc123", "status": "ongoing" }, { "id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e", "title": "Pricing questions", "createdAt": 1770595200, "updatedAt": 1770595500, "userId": "user_abc123", "status": "ended" } ], "pagination": { "cursor": "eyJ0IjoiMjAyNC0wMS0xNVQxMDozMDowMC4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", "hasMore": true, "total": 42 } } ``` ### Response Fields | Field | Type | Description | | ----------- | ---------------- | --------------------------------------------------------- | | `id` | `string` | Conversation ID. | | `title` | `string \| null` | Conversation title. | | `createdAt` | `number` | Unix epoch timestamp (seconds). | | `updatedAt` | `number` | Unix epoch timestamp (seconds) of last activity. | | `userId` | `string \| null` | The user ID associated with this conversation. | | `status` | `string` | Conversation status: `ongoing`, `ended`, or `taken_over`. | ### Pagination Examples ```javascript Node.js theme={null} async function fetchUserConversations(agentId, userId, apiKey) { const conversations = []; let cursor = undefined; do { const params = new URLSearchParams({ limit: "100" }); if (cursor) params.set("cursor", cursor); const response = await fetch( `https://www.chatbase.co/api/v2/agents/${agentId}/users/${userId}/conversations?${params}`, { headers: { Authorization: `Bearer ${apiKey}` }, } ); const { data, pagination } = await response.json(); conversations.push(...data); cursor = pagination.cursor; } while (cursor); return conversations; } ``` ```python Python theme={null} import requests def fetch_user_conversations(agent_id: str, user_id: str, api_key: str): conversations = [] cursor = None while True: params = {"limit": 100} if cursor: params["cursor"] = cursor response = requests.get( f"https://www.chatbase.co/api/v2/agents/{agent_id}/users/{user_id}/conversations", headers={"Authorization": f"Bearer {api_key}"}, params=params, ) body = response.json() conversations.extend(body["data"]) cursor = body["pagination"]["cursor"] if not cursor: break return conversations ``` ## Full Example Send the first message with a `userId` to associate the conversation: ```bash theme={null} curl -N -X POST 'https://www.chatbase.co/api/v2/agents/YOUR_AGENT_ID/chat' \ -H 'Authorization: Bearer YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "message": "What is quantum computing?", "stream": true, "userId": "user_abc123" }' ``` Save the `conversationId` from the `finish` event. Continue the conversation by passing the `conversationId`: ```bash theme={null} curl -N -X POST 'https://www.chatbase.co/api/v2/agents/YOUR_AGENT_ID/chat' \ -H 'Authorization: Bearer YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "message": "How does it differ from classical computing?", "stream": true, "conversationId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" }' ``` Retrieve all conversations for the user: ```bash theme={null} curl 'https://www.chatbase.co/api/v2/agents/YOUR_AGENT_ID/users/user_abc123/conversations?limit=20' \ -H 'Authorization: Bearer YOUR_API_KEY' ``` ## Exporting All Conversations The [Export conversations](/docs/api-v2/conversations/export-conversations) endpoint returns **all conversations with full message history**, regardless of source. This is different from the list endpoints above, which only return API-created conversations. [Get a conversation](/docs/api-v2/conversations/get-a-conversation) also only returns conversations created through the API. If you request a conversation that exists but came from another source, its 404 response names the source and points you to the export endpoint below instead. ### Key differences from list endpoints | | List conversations | Export conversations | | ------------------- | ---------------------------- | -------------------------------------------- | | **Sources** | API-only | All (Widget, WhatsApp, Messenger, API, etc.) | | **Messages** | Not included (metadata only) | Full message history included | | **Tool results** | Not included (metadata only) | Sanitized — internal data stripped | | **Tool call input** | Not included (metadata only) | Omitted (not useful for export consumers) | ### Fetching a single conversation Pass `conversationId` as a query parameter to fetch one conversation instead of paging through the full export. This works for a conversation from any source, including ones created through the widget, WhatsApp, or other integrations, not just the API. ```bash theme={null} curl 'https://www.chatbase.co/api/v2/agents/YOUR_AGENT_ID/conversations/export?conversationId=a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d' \ -H 'Authorization: Bearer YOUR_API_KEY' ``` The response keeps the normal paginated shape, with a single item in `data`: ```json theme={null} { "data": [ { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "...": "..." } ], "pagination": { "cursor": null, "hasMore": false, "total": 1 } } ``` If no conversation matches the ID, the response is a normal empty page (`data: []`, `total: 0`), not a 404. ### Conversation sources Exported conversations include a `source` field indicating where the conversation originated: `API`, `WhatsApp`, `Messenger`, `Instagram`, `Slack`, `Salesforce`, `Zendesk`, `Zendesk Messaging`, `Widget or Iframe`, `Iframe`, `Email`, `Agent page`, `Phone`, `Android SDK`, `iOS SDK`, `Chatbase site`, `Playground`, and others. ### Message format Each conversation includes a `messages` array. Messages contain `parts`, which can be: | Part type | Fields | Description | | ------------- | ------------------------------------------ | ----------------------------------------------------------------- | | `text` | `type`, `text` | Text content from the user or assistant | | `tool-call` | `type`, `toolCallId`, `toolName` | A tool invocation by the AI agent (`input` is omitted in exports) | | `tool-result` | `type`, `toolCallId`, `toolName`, `output` | The sanitized result of a tool invocation | ### Tool result output shape All tool results in the export follow a unified shape, so you can handle them with a single `switch` on `status`: | Status | Shape | Description | | --------- | ------------------------------------- | ---------------------------------------------------------------------------------- | | `success` | `{ status: "success", data?: }` | Tool completed successfully. `data` is present when there is a meaningful payload. | | `error` | `{ status: "error", error?: string }` | Tool encountered an error. | | `pending` | `{ status: "pending" }` | Tool execution is still in progress. | | `ignored` | `{ status: "ignored" }` | Tool was skipped by the user. | ### Example response ```json theme={null} { "data": [ { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "title": "Order status inquiry", "createdAt": 1770681600, "updatedAt": 1770681900, "userId": "user_abc123", "source": "WhatsApp", "status": "ended", "messages": [ { "id": "msg_001", "role": "user", "parts": [ { "type": "text", "text": "What's the status of my order?" } ], "createdAt": 1770681600 }, { "id": "msg_002", "role": "assistant", "parts": [ { "type": "text", "text": "Let me look that up for you." }, { "type": "tool-call", "toolCallId": "call_abc123", "toolName": "lookupOrder" }, { "type": "tool-result", "toolCallId": "call_abc123", "toolName": "lookupOrder", "output": { "status": "success", "data": { "orderId": "ORD-123", "shipped": true } } }, { "type": "text", "text": "Your order ORD-123 has been shipped!" } ], "createdAt": 1770681605, "feedback": "positive", "metadata": { "score": 0.95 } } ] } ], "pagination": { "cursor": "eyJ0IjoiMjAyNC0wMS0xNVQxMDozMDowMC4wMDBaIiwiaWQiOiJhYmMxMjMifQ==", "hasMore": true, "total": 1250 } } ``` ### Paginating through all exports ```javascript Node.js theme={null} async function exportAllConversations(agentId, apiKey) { const conversations = []; let cursor = undefined; do { const params = new URLSearchParams({ limit: "100" }); if (cursor) params.set("cursor", cursor); const response = await fetch( `https://www.chatbase.co/api/v2/agents/${agentId}/conversations/export?${params}`, { headers: { Authorization: `Bearer ${apiKey}` }, } ); const { data, pagination } = await response.json(); conversations.push(...data); cursor = pagination.cursor; } while (cursor); return conversations; } ``` ```python Python theme={null} import requests def export_all_conversations(agent_id: str, api_key: str): conversations = [] cursor = None while True: params = {"limit": 100} if cursor: params["cursor"] = cursor response = requests.get( f"https://www.chatbase.co/api/v2/agents/{agent_id}/conversations/export", headers={"Authorization": f"Bearer {api_key}"}, params=params, ) body = response.json() conversations.extend(body["data"]) cursor = body["pagination"]["cursor"] if not cursor: break return conversations ``` The export endpoint can return large amounts of data. Use a smaller `limit` (e.g., 20) if you're processing messages as they arrive rather than collecting everything in memory. ## Related Learn about streaming responses and event types. How cursor-based pagination works across all list endpoints. Full API reference for the export endpoint. How tool calls and tool results work in conversations. # Voice Sessions Source: https://chatbase.co/docs/api-v2/voice Embed real-time voice conversations with your AI agent in your own app using the Voice Sessions API and the Chatbase Voice SDK. The Voice Sessions API lets you run your AI agent's voice mode inside your own web interface instead of the Chatbase widget. Your backend creates a session, your client joins it with the Chatbase Voice SDK, and the full voice pipeline runs on Chatbase: speech to text, the AI agent response with your training data and actions, text to speech, and natural interruption handling. Your client only publishes microphone audio and renders state. Transport is WebRTC rather than a plain WebSocket. This is what makes low latency and barge-in possible: the user can talk over the AI agent and it stops speaking immediately, with no extra code on your side. ## Try it first Paste your Agent ID and API key, start a session, and talk to your AI agent. No setup, nothing to install. The demo takes an API key in its UI so you can try it in a few seconds; the key is sent only to the demo's own backend, which proxies Chatbase. Your own app should keep it in server-side env vars instead, as shown below. Demo sessions are real: they use message credits and appear in your chat logs. ## Create a session Create sessions from your backend. Your API key must never reach a browser or mobile app, and the endpoint does not send CORS headers, so cross-origin browser calls are rejected by design. ```bash theme={null} curl -X POST 'https://www.chatbase.co/api/v2/agents/YOUR_AGENT_ID/voice/sessions' \ -H 'Authorization: Bearer YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "userId": "user_abc123", "timezone": "Europe/Paris" }' ``` ## Request Body The body is required. Send `{}` when you have no options to set. Optional conversation UUID. Reuse a value to group multiple voice sessions into one conversation in chat logs; the AI agent continues with the earlier transcript as history. Omit to create a new conversation. A conversation belongs to the end-user who started it: when reusing, send that same `userId`, or omit `userId` to inherit it — a different `userId` is rejected with `CONVERSATION_USER_MISMATCH`. Your end-user ID. Max 128 chars, `[a-zA-Z0-9._-]` only. Send a stable ID so per-user voice limits apply; if omitted a random one is generated per session (or inherited from the conversation when reusing a `conversationId`). A `userId` alone never resumes an earlier conversation — each session without a `conversationId` is a new conversation owned by that user. IANA timezone of the end user, for example `Europe/Paris`. The AI agent uses it for time-aware answers. ## Response ```json theme={null} { "data": { "participantToken": "eyJhbGciOiJIUzI1NiJ9...", "sessionId": "81f59ffe-b937-4b00-816a-b50cf416cc7e", "roomName": "chatbot-AGENT_ID-CONVERSATION_ID-SESSION_ID", "maxDurationSeconds": 600, "conversationId": "b363c804-efb5-47f4-9e28-5417e978eb0b", "userId": "user_abc123" } } ``` Hand the `data` object to your client. The `participantToken` is scoped to this single session and expires with it, so it is safe to ship to the browser. Never cache this response. Each `participantToken` belongs to one session and expires with it, so a cached response makes every later visitor connect with a dead token. In Next.js App Router that means `cache: "no-store"` on the fetch and `export const dynamic = "force-dynamic"` in the route. ## Errors Failures use the standard API v2 error shape: ```json theme={null} { "error": { "code": "VOICE_NOT_AVAILABLE", "message": "Voice mode is not available on your current plan." } } ``` | Status | Code | Meaning | | ------ | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | | 401 | `AUTH_MISSING_API_KEY` | No `Authorization` header was sent. | | 401 | `AUTH_INVALID_API_KEY` | The API key is not valid for this workspace. | | 403 | `API_RESTRICTED_PLAN` | The plan does not include API access. | | 403 | `VOICE_NOT_AVAILABLE` | The plan does not include voice mode. | | 403 | `INSUFFICIENT_CREDITS` | Not enough message credits to start a session. | | 403 | `AGENT_CREDITS_LIMIT_REACHED` | The AI agent hit its own credit limit; raise it in the AI agent settings. | | 403 | `CONVERSATION_USER_MISMATCH` | The reused `conversationId` was created with a different `userId`. Send that same `userId`, or omit it to inherit the conversation's end-user. | | 404 | `AGENT_NOT_FOUND` | No such AI agent, or it belongs to another workspace. | | 404 | `CONVERSATION_NOT_FOUND` | The reused `conversationId` belongs to another AI agent or was deleted. | | 429 | `VOICE_LIMIT_EXCEEDED` | A voice session limit was hit. `details.reason` names which one: concurrency, per user, hourly, or daily. | | 429 | `RATE_LIMIT_TOO_MANY_REQUESTS` | Too many API requests; retry after a short delay. | | 500 | `SESSION_CREATION_FAILED` | The session could not be created. Retry the request. | | 503 | `SERVICE_UNDER_MAINTENANCE` | Chatbase is in maintenance; retry shortly. | A 429 from `VOICE_LIMIT_EXCEEDED` is worth surfacing to your user, since it clears on its own: ```json theme={null} { "error": { "code": "VOICE_LIMIT_EXCEEDED", "message": "Voice session limit exceeded. Please try again later or adjust the voice limits in settings.", "details": { "reason": "concurrent_exceeded" } } } ``` ### Example backend route A minimal Next.js App Router handler that your client calls instead of talking to Chatbase directly: ```ts app/api/voice-session/route.ts theme={null} export const dynamic = 'force-dynamic' import { NextResponse } from 'next/server' const CHATBASE_API_URL = 'https://www.chatbase.co/api/v2/agents' export async function POST(request: Request) { const { agentId, userId, timezone } = await request.json() const response = await fetch(`${CHATBASE_API_URL}/${agentId}/voice/sessions`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.CHATBASE_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ userId, timezone }), cache: 'no-store' }) const data = await response.json() // Pass Chatbase's status through so the client can tell a plan or limit // problem from a network failure. return NextResponse.json(data, { status: response.status }) } ``` ## Join from your client Install the Chatbase Voice SDK: ```bash theme={null} npm install @chatbase-co/voice-sdk ``` The AI agent joins the session automatically and speaks the configured greeting; its audio plays without any setup. ```js theme={null} import { ChatbaseVoice } from '@chatbase-co/voice-sdk' // Your backend proxies the create call and returns the session `data` const { data } = await fetch('/your-backend/voice-session').then((r) => r.json()) const voice = new ChatbaseVoice() // Agent state: initializing, listening, thinking, speaking voice.on('agentState', (state) => console.log('agent is', state)) // Live transcripts for both sides. Segments grow while spoken, so key your // UI on segmentId and replace that segment's text on every event. voice.on('transcript', ({ segmentId, speaker, text }) => { // your own UI update, e.g. setMessages((prev) => ({ ...prev, [segmentId]: { speaker, text } })) console.log(`[${speaker}] ${text}`) }) voice.on('sessionEnd', (reason) => console.log('session ended:', reason)) await voice.connect(data) // microphone goes live; the agent greets and listens // Optional: send text into the live session. The agent replies with speech. await voice.sendText('What are your opening hours?') // Microphone control; the session stays alive while muted await voice.mute() await voice.unmute() // End the session await voice.disconnect() ``` If the user declines microphone permission, the session continues in text mode: the SDK emits an `error` event, and `sendText` still gets spoken replies. ## SDK reference ### Methods and properties | Member | Description | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `new ChatbaseVoice()` | Creates an instance. One instance handles one session at a time. | | `connect(session)` | Joins the session. Pass the `data` object from the endpoint, or just the `participantToken` string. Resolves once connected; the microphone goes live and the AI agent greets. | | `disconnect()` | Ends the session. The AI agent hangs up and `sessionEnd` fires. | | `sendText(message)` | Sends text into the live session. The AI agent answers with speech, so this works as a typed alternative to speaking. | | `mute()` | Stops sending microphone audio. The session stays connected and the AI agent keeps talking. | | `unmute()` | Resumes sending microphone audio. | | `isMuted` | `boolean` — whether the microphone is currently muted. | | `localAudioStream` | The local microphone `MediaStream`, or `null` before connecting. Use it to draw a level meter or waveform. | | `on(event, cb)` | Subscribes to an event. Returns an unsubscribe function. | | `off(event, cb)` | Removes a previously registered listener. | Every method returns a promise except `on` and `off`; `isMuted` and `localAudioStream` are plain properties. ### Events | Event | Payload | Carries | | ----------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | `connectionState` | `'connecting' \| 'connected' \| 'reconnecting' \| 'disconnected' \| 'error'` | Connection lifecycle of the session. | | `agentState` | `'initializing' \| 'listening' \| 'thinking' \| 'speaking'` | What the AI agent is doing right now. Drive your UI from this. | | `transcript` | `{ text, speaker, segmentId }` | Live transcripts of both sides. Segments grow while being spoken, so replace by `segmentId` rather than appending. | | `sessionEnd` | `reason?: string` | The session ended, with the reason when one is known. | | `error` | `Error` | Non-fatal problems such as an unavailable microphone, and connect failures. | ## Interruption Barge-in needs no client code. The AI agent detects the caller speaking over it and stops mid-sentence, when the "Allow interruptions" setting is enabled in the AI agent's voice settings. ## Lifecycle and billing A session ends when the client disconnects, when `maxDurationSeconds` elapses, after the configured silence timeout, or when credits run out. Enforcement happens server-side, so clients cannot extend a session past its limits. Voice minutes consume message credits exactly like widget voice sessions. Conversations, transcripts, and recordings appear in the dashboard chat logs with source API. Voice sessions require a plan with voice mode enabled. Session concurrency, per-user, hourly, and daily limits come from the AI agent's voice settings; exceeding one returns `VOICE_LIMIT_EXCEEDED` with the specific reason in `details.reason`. # WhatsApp Source: https://chatbase.co/docs/api-v2/whatsapp List the approved WhatsApp templates available to your AI agent and send them to any phone number. The WhatsApp API [sends approved message templates](/docs/api-v2/whatsapp/send-a-whatsapp-template-message) from the numbers connected to your AI agent. A template is the only way to open a conversation with someone who has not messaged you in the last 24 hours. Recipients are identified by phone number, with no Chatbase user id needed: a user is resolved from the `to` number or created, and replies arrive in that user's conversation through your AI agent's normal WhatsApp pipeline. Both endpoints return `WHATSAPP_NOT_CONNECTED` when the AI agent has no connected number. ## Listing templates [List WhatsApp templates](/docs/api-v2/whatsapp/list-whatsapp-templates) returns every approved template across all of the AI agent's WhatsApp Business Accounts, together with the numbers you can send them from. | Field | Meaning | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `templates[].wabaId` | The Business Account the template belongs to. | | `templates[].variables` | The values the template needs, grouped by component. | | `senders[]` | The AI agent's connected numbers. Each entry carries the `from` value to send with, its `wabaId`, and the display name Meta verified. | | `complete` | `false` when at least one Business Account could not be read. | | `unavailableWabaIds` | The Business Accounts missing from an incomplete listing. Retry to pick them up. | One Business Account failing does not fail the call, so check `complete` before treating the list as the full set. ## Selecting a template Templates are addressed by name. `template.language` is optional when the name has one approved variant and required when it has several, which otherwise returns `TEMPLATE_LANGUAGE_REQUIRED`. Unapproved variants never make the choice ambiguous; when none is approved the send returns `TEMPLATE_NOT_APPROVED` with the review status in `details.status`. A template whose buttons take a parameter cannot be sent from this API and returns `TEMPLATE_BUTTONS_UNSUPPORTED`: | Button | Sendable | | ------------------------------------ | -------- | | Quick reply | Yes | | URL with a fixed address | Yes | | URL containing a `{{1}}` placeholder | No | | Copy code | No | | One-time password | No | Spot these before sending: in the listing, an unsendable URL button has a `{{...}}` placeholder in its `url`. Campaigns cannot send them either, so edit the template in WhatsApp Manager to use a fixed address, or send a variant without the button. Templates with an image, video, or document header send the media approved with the template, so there is nothing to supply in the request. `MEDIA_UPLOAD_FAILED` means that media could not be uploaded to WhatsApp. ## Choosing the sender | `from` | Connected numbers | Result | | -------- | ----------------- | -------------------------------------------------------------------------------------------------------------------- | | Omitted | Exactly one | That number sends. | | Omitted | More than one | `PHONE_NUMBER_REQUIRED`. Pick a number from `senders`. | | Provided | Any | Matched on digits, so `+1 415-555-2671` and `14155552671` are equivalent. No match returns `PHONE_NUMBER_NOT_FOUND`. | A template is looked up on the Business Account of the number you send from, so pair the template's `wabaId` with a `senders` entry carrying the same `wabaId`. Sending from a number on a different account returns `TEMPLATE_NOT_FOUND` even though the template exists. ## Template variables Each component numbers its placeholders from `{{1}}` independently, so values are grouped by component. A template whose header reads `Order {{1}}` and body reads `Hi {{1}}, arriving {{2}}` is listed as `{"header": ["1"], "body": ["1", "2"]}` and takes three values, the header's `1` being separate from the body's. Send back exactly the keys the listing reported. For a named template, use the parameter names in place of the numbers. A value you leave out returns `MISSING_TEMPLATE_VARIABLES`, and one WhatsApp will not accept, meaning empty or containing a line break, a tab, or five or more consecutive spaces, returns `INVALID_TEMPLATE_VARIABLES`. Both name each slot as `component[key]`, such as `body[2]`. ## Delivery and conversations Meta accepts sends from a blocked Business Account and drops them afterwards, reporting the reason only on a status webhook. Chatbase checks first, turning that silent loss into `SEND_BLOCKED` with the reason in `details.reason`. It is most often a billing problem: resolve it in WhatsApp Manager and sends resume within about a minute. A `201` means WhatsApp accepted the message, not that it reached the recipient. It can still be dropped afterwards, most often because the recipient is not on WhatsApp, has blocked your business, or has already had its limit of marketing templates for the period. Match later webhooks against the returned `to`, which is the canonical WhatsApp id and can differ from what you sent. `conversationId` is the conversation the recipient's replies continue in, and the template is appended to it unless a human has taken the conversation over or it has ended. ## Error codes WhatsApp-specific error codes beyond the standard [authentication and rate-limiting errors](/docs/api-v2/error-handling):
Code HTTP Description
WHATSAPP\_NOT\_CONNECTED403The AI agent has no connected WhatsApp number. Connect one from the deploy page.
PHONE\_NUMBER\_REQUIRED400The AI agent has more than one connected number, so from must say which one sends.
PHONE\_NUMBER\_NOT\_FOUND404No number connected to this AI agent matches from.
TEMPLATE\_NOT\_FOUND404No template with that name, and language when given, exists on the Business Account of the sending number. Check that the template's wabaId matches the sender's.
TEMPLATE\_LANGUAGE\_REQUIRED400The template name has more than one approved language variant. Pass template.language.
TEMPLATE\_NOT\_APPROVED409The template exists but has no approved variant to send. details.status carries the Meta review status.
SEND\_BLOCKED409Meta has blocked business-initiated conversations for this Business Account. details.reason carries Meta's explanation.
MISSING\_TEMPLATE\_VARIABLES422The template declares variables that were not provided. details.missing lists them as component\[key].
INVALID\_TEMPLATE\_VARIABLES422A value is empty, or contains a line break, a tab, or five or more consecutive spaces. details.invalid names each offending slot and why.
TEMPLATE\_BUTTONS\_UNSUPPORTED422The template has a button that takes a parameter, which this API cannot send. details.buttons lists the button types.
TEMPLATE\_PARAMS\_REJECTED422WhatsApp rejected the parameters as not matching the approved template.
RECIPIENT\_INVALID422to is not a valid phone number for its country. Unassignable numbers, such as a 555 US area code, are rejected.
RECIPIENT\_NOT\_REACHABLE422WhatsApp reported the recipient as undeliverable. The number may not be on WhatsApp or may have blocked business messages.
MEDIA\_UPLOAD\_FAILED502The template has a media header and uploading its media to WhatsApp failed.
WHATSAPP\_SEND\_FAILED502WhatsApp returned an error that does not map to a more specific code.
# List WhatsApp templates Source: https://chatbase.co/docs/api-v2/whatsapp/list-whatsapp-templates /api-v2-openapi.json get /agents/{agentId}/whatsapp/templates Lists the approved WhatsApp templates available to the agent, across all of its connected WhatsApp Business Accounts. Each template's `variables` object is the shape a send request must provide. A template can only be sent from a number on its own Business Account, so pair its `wabaId` with a matching entry in `senders` to pick the `from` value. Check `complete` before treating the list as exhaustive: it is `false` when one of the agent’s Business Accounts could not be read. # Send a WhatsApp template message Source: https://chatbase.co/docs/api-v2/whatsapp/send-a-whatsapp-template-message /api-v2-openapi.json post /agents/{agentId}/whatsapp/messages/template Sends an approved WhatsApp template to a recipient from one of the agent's connected phone numbers. Recipients are identified by phone number only — no user ID is required. A Chatbase user is resolved or created from the `to` number automatically, and their reply flows through the agent's regular WhatsApp pipeline. The message is also appended to that conversation, unless a human has taken it over or it has ended — see `conversationId` in the response. # CLI Agents Source: https://chatbase.co/docs/cli/agents Create, inspect, update, clone, and delete Chatbase AI agents from the terminal. Use AI agent commands to manage the AI agents in your workspace. For REST details, see [Agents API](/docs/api-v2/agents). Authenticate first — see [CLI Auth](/docs/cli/auth). ## List and inspect List every AI agent in the workspace: ```bash theme={null} chatbase agents list ``` Show one AI agent by ID: ```bash theme={null} chatbase agents get agt_123 ``` Use `--json` or `--plain` for scripting. Set a default agent with [CLI Config](/docs/cli/config) so you can omit the ID on `get` and other commands. ## Create and configure Create a new AI agent: ```bash theme={null} chatbase agents create --name "Support Bot" --instructions "Be helpful" ``` Update name, instructions, model, and other fields: ```bash theme={null} chatbase agents update agt_123 --name "Support Bot v2" ``` Update chat widget appearance (theme, colors, etc.): ```bash theme={null} chatbase agents styles agt_123 --data '{"chat":{"theme":"dark"}}' ``` Pass a JSON file with `@styles.json` instead of inline JSON when you have many style properties. ## Auto-resync and duplicate Sources train on their own when you add or change them, so there is no train step. `chatbase agents train` is deprecated and does nothing. Turn on the weekly auto-resync for websites, Notion pages, and tickets: ```bash theme={null} chatbase agents auto-retrain agt_123 --enabled ``` Clone an AI agent and its sources (Notion sources are excluded): ```bash theme={null} chatbase agents clone agt_123 ``` ## Delete an AI agent Permanently remove an AI agent: ```bash theme={null} chatbase agents delete agt_123 ``` Deletion cannot be undone. In scripts and CI, pass `--confirm agt_123` to skip the interactive prompt. ## Commands | Command | Purpose | | ------------------------------ | ------------------------------------------------------------------------------- | | `chatbase agents auto-retrain` | Enable or disable the weekly auto-resync of websites, Notion pages, and tickets | | `chatbase agents clone` | Clone an AI agent, including all its sources (excluding Notion) | | `chatbase agents create` | Create a new AI agent | | `chatbase agents delete` | Permanently delete an AI agent (cannot be undone) | | `chatbase agents get` | Show one AI agent | | `chatbase agents list` | List all AI agents in the workspace | | `chatbase agents styles` | Update visual styles for an AI agent | | `chatbase agents train` | Deprecated. Sources train on write; this command does nothing | | `chatbase agents update` | Update an existing AI agent | For every flag, run `chatbase agents --help`. # CLI API Source: https://chatbase.co/docs/cli/api Call any Chatbase API v2 endpoint from the terminal when no dedicated CLI command exists. Use `chatbase api` as an escape hatch for [API v2](/docs/api-v2/overview) endpoints that do not have a dedicated CLI command. Pass an HTTP method and a path relative to `/api/v2` — for example, `/agents` maps to `https://www.chatbase.co/api/v2/agents`. Authenticate first — see [CLI Auth](/docs/cli/auth). ## List resources Fetch a collection: ```bash theme={null} chatbase api GET /agents ``` Limit results with `--field`: ```bash theme={null} chatbase api GET /agents --field limit=5 ``` Add URL query parameters with `--query` (repeatable): ```bash theme={null} chatbase api GET /agents --query limit=5 --query status=active ``` ## Create a resource Send a JSON body inline: ```bash theme={null} chatbase api POST /agents --body '{"name":"Support Bot"}' ``` Set individual body fields instead of raw JSON: ```bash theme={null} chatbase api POST /agents --field name="Support Bot" ``` ## Update a resource Read the body from a file with `@`: ```bash theme={null} chatbase api PATCH /agents/agt_123 --body @patch.json ``` Pipe JSON from stdin with `@-`: ```bash theme={null} echo '{"name":"Renamed Bot"}' | chatbase api PATCH /agents/agt_123 --body @- ``` ## Request options | Flag | Purpose | | ---------------- | ----------------------------------------------------------- | | `--body` | JSON request body — inline JSON, `@file`, or `@-` for stdin | | `--field` / `-f` | Set a body field as `key=value` (repeatable) | | `--query` | Add a URL query parameter as `key=value` (repeatable) | Supported methods: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`. Use `--json` for raw API JSON in scripts. See [Chatbase CLI](/docs/cli/overview) for other global output flags. ## Commands | Command | Purpose | | -------------------------- | --------------------------------- | | `chatbase api METHOD PATH` | Call any API v2 endpoint directly | For every flag, run `chatbase api --help`. # CLI Auth Source: https://chatbase.co/docs/cli/auth Authenticate the Chatbase CLI with an API key or browser login, and inspect or remove stored credentials. Use auth commands to store credentials for interactive use. For CI, prefer the `CHATBASE_API_KEY` environment variable instead of a stored key — see [Chatbase CLI](/docs/cli/overview). ## Log in with the browser For interactive use, log in through your browser: ```bash theme={null} chatbase auth login --browser ``` The CLI opens a browser window and prints a short device code. Open [chatbase.co/activate](https://www.chatbase.co/activate), sign in if needed, and approve the code. When approval succeeds, the CLI stores the credential locally so later commands are authenticated. ## Log in with an API key Paste a key interactively: ```bash theme={null} chatbase auth login ``` Or pipe a key from a file or secret store: ```bash theme={null} cat key.txt | chatbase auth login --with-token ``` Create keys in **Workspace settings → API keys** (Standard plan or higher). ## Check status ```bash theme={null} chatbase auth status ``` Shows the active credential and where it comes from (stored key, environment, etc.). ## Log out ```bash theme={null} chatbase auth logout ``` Removes the stored API key. CLI-paired keys are revoked server-side when applicable. ## Commands | Command | Purpose | | ---------------------- | -------------------------------------------------------- | | `chatbase auth login` | Authenticate (paste key, `--with-token`, or `--browser`) | | `chatbase auth status` | Show active credential and source | | `chatbase auth logout` | Remove stored API key | For every flag, run `chatbase auth --help`. # CLI Chat Source: https://chatbase.co/docs/cli/chat Send messages to an AI agent, continue conversations, and retry assistant responses from the terminal. Use chat commands to send messages to an AI agent and read responses in your terminal. For REST details, see [User Conversations](/docs/api-v2/user-conversations) and [Streaming](/docs/api-v2/streaming). Authenticate first — see [CLI Auth](/docs/cli/auth). Pass an agent ID with `-a agt_123`, look one up with `--agent-name`, set `CHATBASE_AGENT_ID`, or configure a [default agent](/docs/cli/config). ## Send a message Send a one-shot message: ```bash theme={null} chatbase chat -a agt_123 -m "How do I reset my password?" ``` Pipe text from stdin instead of `-m`: ```bash theme={null} echo "summarize our refund policy" | chatbase chat -a agt_123 ``` If you omit both `-m` and stdin, the CLI starts an interactive REPL. By default, responses stream token-by-token to your terminal. See [Streaming](/docs/api-v2/streaming) for event types and how to parse them in scripts. ## Continue a conversation Pass a `conversationId` from a previous response to send a follow-up message: ```bash theme={null} chatbase chat -a agt_123 -m "and then?" --conversation conv_123 ``` Add `--resume` to replay the last few messages when continuing, so the AI agent has recent context without you re-sending it. Save the `conversationId` from a `--json` response or from the streaming `finish` event metadata. See [User Conversations](/docs/api-v2/user-conversations) for per-user history and listing conversations by user. ## Scripting output Wait for the complete response instead of streaming: ```bash theme={null} chatbase chat -a agt_123 -m "hi" --no-stream ``` Output raw API JSON for parsing in scripts: ```bash theme={null} chatbase chat -a agt_123 -m "hi" --json ``` Use `--plain` for tab-separated output. Combine with `--quiet` in CI pipelines. ## Retry a response Regenerate an assistant message and discard it plus everything after it in the conversation: ```bash theme={null} chatbase chat retry --conversation c_123 -a agt_123 --message-id msg_456 ``` Wait for the full retry response instead of streaming: ```bash theme={null} chatbase chat retry --conversation c_123 -a agt_123 --message-id msg_456 --no-stream ``` ## Commands | Command | Purpose | | --------------------- | ---------------------------------------------------------------------------------------------------------- | | `chatbase chat` | Send a message to an AI agent and print its response | | `chatbase chat retry` | Retry generating an assistant response (discards that message and everything after it in the conversation) | For every flag, run `chatbase chat --help` or `chatbase chat retry --help`. # CLI Config Source: https://chatbase.co/docs/cli/config Set and inspect Chatbase CLI defaults such as the active AI agent and request timeout. CLI config stores defaults so you do not need to pass `-a` on every command. ## Set a default agent ```bash theme={null} chatbase config set agent agt_123 ``` Omit the value to pick an AI agent interactively: ```bash theme={null} chatbase config set agent ``` ## Set timeout Timeout is in milliseconds: ```bash theme={null} chatbase config set timeout 60000 ``` ## Inspect config ```bash theme={null} chatbase config get agent chatbase config list ``` `get` and `list` show the resolved value and where it comes from (config file, environment, flag). ## Commands | Command | Purpose | | --------------------------------- | --------------------------------------- | | `chatbase config set KEY [VALUE]` | Set `agent` or `timeout` | | `chatbase config get KEY` | Print one resolved value and its source | | `chatbase config list` | List every resolved value and source | Supported keys: `agent`, `timeout`. You can also set `CHATBASE_AGENT_ID` in the environment. See [Chatbase CLI](/docs/cli/overview) for CI patterns. For every flag, run `chatbase config --help`. # CLI Conversations Source: https://chatbase.co/docs/cli/conversations List, inspect, export, and manage conversation history and message feedback from the terminal. Use conversation and message commands to inspect chat history, export transcripts, submit client-side tool results, and set feedback on assistant replies. For REST details, see [User Conversations](/docs/api-v2/user-conversations). Authenticate first — see [CLI Auth](/docs/cli/auth). Most commands require an agent ID (`-a agt_123`) or a [default agent](/docs/cli/config). ## List API-created conversations List conversations created programmatically through the API, newest first: ```bash theme={null} chatbase conversations list -a agt_123 ``` List conversations for a specific user: ```bash theme={null} chatbase conversations list -a agt_123 --user usr_456 ``` Fetch every page as JSON: ```bash theme={null} chatbase conversations list -a agt_123 --all --json ``` `conversations list` returns only API-created conversations. Conversations from the chat bubble and external integrations (Slack, WhatsApp, Instagram, Messenger, and the like) are not included and are not counted in `total`. Use `conversations export` to read conversations from every source. ## Get one conversation Show metadata for a single API-created conversation: ```bash theme={null} chatbase conversations get conv_123 -a agt_123 ``` You can pass the ID as an argument or with `--conversation`: ```bash theme={null} chatbase conversations get --conversation conv_123 -a agt_123 ``` ## Export all conversations Export conversations from every source with full message history embedded: ```bash theme={null} chatbase conversations export -a agt_123 ``` Write the full export to a file: ```bash theme={null} chatbase conversations export -a agt_123 --all -o export.json ``` Each exported item includes its own `messages` array, so you do not need follow-up `conversations get` or `messages list` calls. Export is the only way to read bubble and integration conversations from the CLI — `get` and `messages list` work only for API-created conversations. Use `--limit` (1–20, default 20) when processing large exports page by page. See [Exporting All Conversations](/docs/api-v2/user-conversations#exporting-all-conversations) for source types and message format. ## Submit client-side tool results When a chat response pauses on a client-side tool call, submit the result so the turn can continue: ```bash theme={null} chatbase conversations tool-result conv_123 --tool-call-id tc_1 --output '{"temperature": 72}' -a agt_123 ``` Pass JSON from a file with `@result.json`, or omit `--output` to send an empty result. See [Client Actions](/docs/api-v2/client-actions) for how tool calls and results work. ```bash theme={null} chatbase conversations tool-result conv_123 --tool-call-id tc_1 --output @result.json -a agt_123 ``` Send an empty result when the tool produced no output: ```bash theme={null} chatbase conversations tool-result conv_123 --tool-call-id tc_1 -a agt_123 ``` ## List messages List messages in an API-created conversation: ```bash theme={null} chatbase messages list --conversation conv_123 -a agt_123 ``` Fetch every page as JSON: ```bash theme={null} chatbase messages list --conversation conv_123 -a agt_123 --all --json ``` ## Set message feedback Rate an assistant message: ```bash theme={null} chatbase messages feedback msg_1 --conversation conv_123 --rating positive -a agt_123 ``` Clear existing feedback: ```bash theme={null} chatbase messages feedback --conversation conv_123 --message msg_1 --rating clear -a agt_123 ``` Ratings are `positive`, `negative`, or `clear`. ## Commands | Command | Purpose | | ------------------------------------ | ----------------------------------------------------------------- | | `chatbase conversations export` | Export conversations from every source, with full message history | | `chatbase conversations get` | Show one API-created conversation | | `chatbase conversations list` | List an AI agent's API-created conversations | | `chatbase conversations tool-result` | Submit a client-side tool result to a chat turn | | `chatbase messages feedback` | Set or clear user feedback on an assistant message | | `chatbase messages list` | List messages in a conversation | For every flag, run `chatbase conversations --help` or `chatbase messages --help`. # CLI Health Source: https://chatbase.co/docs/cli/health Verify that the Chatbase API is reachable from your terminal or CI pipeline. Use `chatbase health` to confirm the Chatbase API is reachable before running other commands — for example in CI preflight checks, after network or proxy changes, or when debugging authentication issues. No API key is required. ## Check reachability ```bash theme={null} chatbase health ``` Exits successfully when the API responds. ## Scripting output Output raw JSON for automated checks: ```bash theme={null} chatbase health --json ``` Combine with `--quiet` in CI pipelines. See [Chatbase CLI](/docs/cli/overview) for other global output flags. ## Commands | Command | Purpose | | ----------------- | ---------------------------------------- | | `chatbase health` | Check that the Chatbase API is reachable | For every flag, run `chatbase health --help`. # Chatbase CLI Source: https://chatbase.co/docs/cli/overview Install and use the official Chatbase CLI to manage AI agents, sources, conversations, helpdesk, and WhatsApp from your terminal or CI. The Chatbase CLI is the official command-line client for the [Chatbase API v2](/docs/api-v2/overview). Use it to manage AI agents, knowledge sources, conversations, helpdesk tickets, and WhatsApp templates from your terminal or CI pipelines. Requires **Node.js 20+**. Interactive login uses your Chatbase account in the browser (`chatbase auth login --browser`). API keys (Standard plan or higher) work for paste-login and CI — create them in **Workspace settings → API keys**. ## Install ```bash theme={null} npm install -g chatbase ``` Or run without installing: ```bash theme={null} npx chatbase ``` Check the version: ```bash theme={null} chatbase --version ``` ## Quick start ```bash theme={null} chatbase auth login --browser ``` This opens a browser window and shows a short code. Approve the code at [chatbase.co/activate](https://www.chatbase.co/activate). The CLI stores the credential for later commands. Prefer pasting an API key instead? Run `chatbase auth login` and paste a key from **Workspace settings → API keys**. See [CLI Auth](/docs/cli/auth) for all login options. ```bash theme={null} chatbase agents list ``` ```bash theme={null} chatbase chat -a agt_123 -m "How do I reset my password?" ``` ## Authenticate in CI In non-interactive environments, set an API key in the environment instead of running `auth login`: ```bash theme={null} export CHATBASE_API_KEY=cb_... chatbase agents list ``` Optionally pin a default agent: ```bash theme={null} export CHATBASE_AGENT_ID=agt_123 # or chatbase config set agent agt_123 ``` Never commit API keys. Prefer secrets managers or CI secret stores. ## Global output flags Most commands support: | Flag | Purpose | | ---------------- | -------------------------------- | | `--json` | Raw API JSON | | `--plain` | Tab-separated output for scripts | | `--quiet` / `-q` | Suppress non-essential output | | `--verbose` | Verbose diagnostics | | `--no-input` | Never prompt; fail instead | | `--no-color` | Disable colored output | ## Privacy The CLI sends no telemetry. Requests include a `chatbase-cli/` User-Agent so Chatbase can distinguish CLI traffic. The only network calls are the API calls you invoke. ## Commands Log in, log out, and inspect credentials. Create, update, train, clone, and delete AI agents. Manage knowledge sources (text, Q\&A, links, files). Send messages and stream responses. List, inspect, and export conversations. Manage helpdesk tickets and lookups. List templates and send template messages. Set default agent and timeout. Call any API v2 endpoint directly. Check that the API is reachable. ## Uninstall ```bash theme={null} npm uninstall -g chatbase rm -rf ~/.config/chatbase ~/.local/state/chatbase ~/.cache/chatbase rm -rf ~/Library/Caches/chatbase # macOS: update-check cache ``` For every flag on a command, run `chatbase --help`. # CLI Sources Source: https://chatbase.co/docs/cli/sources Add, list, update, and delete knowledge sources for a Chatbase AI agent from the terminal. Use source commands to manage an AI agent's knowledge base — text, Q\&A pairs, links, and file uploads. For REST details, see [Sources API](/docs/api-v2/sources). Most commands require an agent ID (`-a agt_123`) or a [default agent](/docs/cli/config). ## Add sources Text snippet: ```bash theme={null} chatbase sources create --type text --name Guide --content "hello" -a agt_123 ``` Website link (crawl mode): ```bash theme={null} chatbase sources create --type link --url https://example.com --link-type crawl -a agt_123 ``` Q\&A pair: ```bash theme={null} chatbase sources create --type qna --data '{"questions":["Q1"],"answer":"A1"}' -a agt_123 ``` File upload: ```bash theme={null} chatbase sources create --file ./guide.pdf -a agt_123 ``` Link types also include `individual` (single page) and `sitemap`. ## List and inspect List sources for an AI agent: ```bash theme={null} chatbase sources list -a agt_123 ``` Show one source: ```bash theme={null} chatbase sources get src_123 -a agt_123 ``` View aggregated counts and sizes: ```bash theme={null} chatbase sources summary -a agt_123 ``` ## Update and delete Update a text, Q\&A, or link source: ```bash theme={null} chatbase sources update src_123 --data '{"type":"text","content":"updated"}' -a agt_123 ``` Replace a file source: ```bash theme={null} chatbase sources update src_123 --file ./updated.pdf -a agt_123 ``` Delete a source (restorable): ```bash theme={null} chatbase sources delete src_123 -a agt_123 ``` Deletes are permanent. `chatbase sources restore` is deprecated and does nothing. ## Changes take effect on their own Every create, update, or delete trains the source right away. There is no train step. Check progress with: ```bash theme={null} chatbase sources get src_123 -a agt_123 ``` The `status` field reads `untrained` while the source trains, `trained` once it is live (usually within seconds), or `failed` if the run did not land. See [CLI Agents](/docs/cli/agents) for the auto-resync schedule. ## Commands | Command | Purpose | | -------------------------- | -------------------------------------------------------- | | `chatbase sources create` | Create a source: text/qna/link (JSON) or a file upload | | `chatbase sources delete` | Permanently delete a source and its knowledge | | `chatbase sources get` | Show one source | | `chatbase sources list` | List sources for an AI agent | | `chatbase sources restore` | Deprecated. Deletes are final; this command does nothing | | `chatbase sources summary` | Show aggregated source counts and sizes for an AI agent | | `chatbase sources update` | Update an existing source (text, qna, link, or file) | For every flag, run `chatbase sources --help`. # CLI Tickets Source: https://chatbase.co/docs/cli/tickets Create, triage, search, and reply to helpdesk tickets and look up teams and statuses from the terminal. Use ticket and helpdesk commands to manage support tickets and their message threads. For REST details, see [Helpdesk](/docs/api-v2/helpdesk). Authenticate first — see [CLI Auth](/docs/cli/auth). Most commands require an agent ID (`-a agt_123`) or a [default agent](/docs/cli/config). ## Create a ticket Open a ticket on behalf of a customer with a subject, description, and email: ```bash theme={null} chatbase tickets create --subject "Export failing" -f description="Customer cannot export." --customer-email jane@example.com -a agt_123 ``` Pass the full body as JSON instead of individual flags: ```bash theme={null} chatbase tickets create --subject "Export failing" --data '{"description":"Customer cannot export.","customer":{"email":"jane@example.com"}}' -a agt_123 ``` Use `--customer-name` when the email creates a new customer record. Set status, assignee, or team at creation time with `-f` or `--data` — see [Create a ticket](/docs/api-v2/helpdesk/create-a-ticket) for assignment rules. ## List and search tickets List tickets for an AI agent, newest first: ```bash theme={null} chatbase tickets list -a agt_123 ``` Filter by status category, assignee, team, channel, or date range: ```bash theme={null} chatbase tickets list -a agt_123 --status new,on_you --assignee-id none ``` Search ticket message content: ```bash theme={null} chatbase tickets search "refund not received" -a agt_123 ``` Fetch every page as JSON: ```bash theme={null} chatbase tickets list -a agt_123 --all --json ``` ## Get a ticket and read messages Show one ticket by its per-agent number: ```bash theme={null} chatbase tickets get 42 -a agt_123 ``` List the ticket's message thread: ```bash theme={null} chatbase tickets messages 42 -a agt_123 ``` Include internal notes or system events with `--types`: ```bash theme={null} chatbase tickets messages 42 -a agt_123 --types reply,note,event ``` ## Reply to a ticket Post a customer-visible reply attributed to a team member: ```bash theme={null} chatbase tickets reply 42 -m "On it" --author-email sam@example.com -a agt_123 ``` The message body is GitHub-flavored Markdown. A 201 means the reply was recorded, not that it reached the customer — delivery is asynchronous. ## Update status, assignee, or team Look up valid status IDs and team IDs before you update: ```bash theme={null} chatbase helpdesk statuses -a agt_123 chatbase helpdesk teams -a agt_123 ``` Close a ticket by status category: ```bash theme={null} chatbase tickets update 42 --data '{"statusCategory":"closed"}' -a agt_123 ``` Assign to a team member or set a specific status ID: ```bash theme={null} chatbase tickets update 42 -f assigneeEmail=sam@example.com -f statusId=sts_abc -a agt_123 ``` Use `statusCategory` to resolve to that category's default status, or `statusId` for a specific configured status. See [Statuses](/docs/api-v2/helpdesk#statuses) and [Update a ticket](/docs/api-v2/helpdesk/update-a-ticket). ## Look up teams and statuses List helpdesk teams (including the default): ```bash theme={null} chatbase helpdesk teams -a agt_123 ``` List configured ticket statuses with IDs and labels: ```bash theme={null} chatbase helpdesk statuses -a agt_123 ``` Use `--json` to pipe IDs into scripts. ## Commands | Command | Purpose | | ---------------------------- | ------------------------------------------------ | | `chatbase helpdesk statuses` | List ticket statuses for an AI agent | | `chatbase helpdesk teams` | List helpdesk teams for an AI agent | | `chatbase tickets create` | Create a helpdesk ticket | | `chatbase tickets get` | Show one helpdesk ticket | | `chatbase tickets list` | List helpdesk tickets for an AI agent | | `chatbase tickets messages` | List a ticket's message thread | | `chatbase tickets reply` | Post an agent reply to a ticket's message thread | | `chatbase tickets search` | Search tickets by message content | | `chatbase tickets update` | Update a ticket's status, assignee, and/or team | For every flag, run `chatbase tickets --help` or `chatbase helpdesk --help`. # CLI WhatsApp Source: https://chatbase.co/docs/cli/whatsapp List approved WhatsApp templates and send template messages to phone numbers from the terminal. Use WhatsApp commands to list approved templates and send outbound template messages. For REST details, see [WhatsApp](/docs/api-v2/whatsapp). Authenticate first — see [CLI Auth](/docs/cli/auth). Most commands require an agent ID (`-a agt_123`) or a [default agent](/docs/cli/config). Connect a WhatsApp number to your AI agent before using these commands — see [WhatsApp integration](/docs/user-guides/integrations/whatsapp) and [WhatsApp templates](/docs/user-guides/integrations/whatsapp-templates) in the user guide. ## List templates List every approved template across the AI agent's connected WhatsApp Business Accounts: ```bash theme={null} chatbase whatsapp templates -a agt_123 ``` Output includes template names, languages, variable placeholders grouped by component, and the connected numbers you can send from. A template can only be sent from a number on its own Business Account — match the template's WABA to a sender before sending. Fetch raw JSON for scripting: ```bash theme={null} chatbase whatsapp templates -a agt_123 --json ``` ## Send a template Send a template with no variables: ```bash theme={null} chatbase whatsapp send-template order_update --to 14155552671 -a agt_123 ``` The recipient number uses international format (digits with country code, no `+` required). When the AI agent has exactly one connected number, `--from` is optional. ## Send with variables Pass template variable values as JSON grouped by component: ```bash theme={null} chatbase whatsapp send-template order_update --to 14155552671 --language en_US --variables '{"body":{"1":"Jane"}}' -a agt_123 ``` Use `--language` when the template name has multiple approved language variants. Header and body placeholders are numbered independently — supply every key the template listing reported. See [Template variables](/docs/api-v2/whatsapp#template-variables) for format rules. Send from a specific connected number when the AI agent has more than one: ```bash theme={null} chatbase whatsapp send-template order_update --to 14155552671 --from 14155552671 -a agt_123 ``` A `201` means WhatsApp accepted the message. Delivery to the recipient is not guaranteed — see [Delivery and conversations](/docs/api-v2/whatsapp#delivery-and-conversations). ## Commands | Command | Purpose | | --------------------------------- | ------------------------------------------------ | | `chatbase whatsapp send-template` | Send an approved WhatsApp template message | | `chatbase whatsapp templates` | List approved WhatsApp templates for an AI agent | For every flag, run `chatbase whatsapp --help`. # REST API Integration Source: https://chatbase.co/docs/developer-guides/api-integration Complete guide to integrating Chatbase AI Agents using the Chatbase API v2 for custom integrations and applications. **Standard Plan required.** The Chatbase API v2 is available starting from the Standard Plan. [View pricing →](https://www.chatbase.co/pricing) ## Overview The Chatbase REST API enables you to integrate AI-powered conversations into any application or workflow. Build custom chat experiences, automate customer interactions, and manage your AI agents programmatically. API v2 features structured error codes, cursor-based pagination, and real-time streaming via Server-Sent Events (SSE). **Base URL:** ``` https://www.chatbase.co/api/v2 ``` Chat with your AI agents and handle real-time streaming responses Create, configure, and update AI agents and their training sources Retrieve conversations and messages from your AI interactions ## Quick Start Creating API key in Chatbase dashboard 1. Visit your [Chatbase Dashboard](https://www.chatbase.co/dashboard) 2. Go to **Workspace settings** → **API keys** 3. Click **Create API Key** and copy the generated key Store your API key securely and never expose it in client-side code. Finding Agent ID in Chatbase settings 1. Select your AI Agent in the dashboard 2. Go to **Settings** → **General** 3. Copy the **Agent ID** from the **Agent details** card Test your integration with a simple chat request: ```bash theme={null} curl -X POST 'https://www.chatbase.co/api/v2/agents/YOUR_AGENT_ID/chat' \ -H 'Authorization: Bearer YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "message": "Hello! How can you help me?", "stream": false }' ``` **Expected Response:** ```json theme={null} { "data": { "id": "msg_abc123", "role": "assistant", "parts": [ { "type": "text", "text": "Hello! I'm here to help answer your questions and assist with any information you need. What can I help you with today?" } ], "metadata": { "userMessageId": "msg_xyz789", "conversationId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "userId": null, "finishReason": "stop", "usage": { "credits": 2 } } } } ``` `stream` defaults to `true` in API v2. Set it to `false` to receive the complete message as a single JSON response. Conversation history is stored by Chatbase — you don't need to resend previous messages. Pass the `conversationId` from the previous response to send a follow-up: ```bash theme={null} curl -X POST 'https://www.chatbase.co/api/v2/agents/YOUR_AGENT_ID/chat' \ -H 'Authorization: Bearer YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "message": "Tell me more about your pricing.", "conversationId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", "stream": false }' ``` To associate a new conversation with one of your users, pass a `userId` on the first message. See [User Conversations](/docs/api-v2/user-conversations) for details. ### Chat API Streaming The [chat API](/docs/api-v2/agents/chat-with-an-agent) streams responses in real time using Server-Sent Events. Each event is a `data:` line containing a JSON object with a `type` field, and the stream ends with `data: [DONE]`. Concatenate the `text-delta` events to build the full reply, and read the `conversationId` from the `finish` event to continue the conversation. ```javascript Node.js theme={null} // streamer.js (Node.js 18+) const apiKey = '' const agentId = '' const apiUrl = `https://www.chatbase.co/api/v2/agents/${agentId}/chat` async function readAgentReply() { const response = await fetch(apiUrl, { method: 'POST', headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ message: '', stream: true, // conversationId: '', // omit to start a new conversation }), }) if (!response.ok) { const { error } = await response.json() throw new Error(`${error.code}: ${error.message}`) } const reader = response.body.getReader() const decoder = new TextDecoder() let buffer = '' let conversationId while (true) { const { done, value } = await reader.read() if (done) break buffer += decoder.decode(value, { stream: true }) const lines = buffer.split('\n') buffer = lines.pop() // keep any incomplete line for the next chunk for (const line of lines) { if (!line.startsWith('data: ')) continue const data = line.slice('data: '.length) if (data === '[DONE]') continue const event = JSON.parse(data) switch (event.type) { case 'text-delta': process.stdout.write(event.delta) break case 'finish': conversationId = event.messageMetadata.conversationId break case 'error': console.error('\nStream error:', event.errorText) break } } } console.log('\nConversation ID:', conversationId) } readAgentReply().catch((error) => console.log('Error:', error.message)) ``` ```python Python theme={null} ## streamer.py import json import requests api_key = '' agent_id = '' api_url = f'https://www.chatbase.co/api/v2/agents/{agent_id}/chat' def read_agent_reply(): try: headers = { 'Authorization': f'Bearer {api_key}', 'Content-Type': 'application/json' } data = { 'message': '', 'stream': True, # 'conversationId': '', # omit to start a new conversation } response = requests.post(api_url, json=data, headers=headers, stream=True) response.raise_for_status() conversation_id = None for line in response.iter_lines(decode_unicode=True): if not line or not line.startswith('data: '): continue payload = line[len('data: '):] if payload == '[DONE]': continue event = json.loads(payload) if event['type'] == 'text-delta': print(event['delta'], end='', flush=True) elif event['type'] == 'finish': conversation_id = event['messageMetadata']['conversationId'] elif event['type'] == 'error': print(f"\nStream error: {event['errorText']}") print(f'\nConversation ID: {conversation_id}') except requests.exceptions.RequestException as error: print('Error:', error) read_agent_reply() ``` For the full list of stream events — including client action (tool call) events — see the [Streaming guide](/docs/api-v2/streaming). ## Error Handling API v2 returns structured errors with a machine-readable `code`: ```json theme={null} { "error": { "code": "RATE_LIMIT_TOO_MANY_REQUESTS", "message": "Too many requests, please try again later" } } ``` Every response includes an `x-request-id` header — include it when contacting support. See [Error Handling](/docs/api-v2/error-handling) for all error codes and [Rate Limiting](/docs/api-v2/authentication#rate-limiting) for limits and retry guidance. ## Performance Best Practices **Optimization Strategies:** * Use streaming for chat responses to improve perceived performance * Reuse `conversationId` for follow-ups instead of starting new conversations * Cache AI agent responses when appropriate * Respect the `Retry-After` header when you receive a `429` response * Use cursor-based [pagination](/docs/api-v2/pagination) when listing conversations, messages, or sources ## 🚀 Try It Live! Ready to see the magic in action? Dive straight into our interactive playground where you can test every API endpoint, experiment with real responses, and build your integration in real-time. Test APIs instantly • No setup required • Real-time responses • Copy working code snippets ### Key API Endpoints Send messages and receive AI responses with streaming support Create, update, and configure AI agents programmatically Access chat history and conversation messages Add and manage the training sources for your AI agents Explore the full API v2 reference — authentication, streaming, client actions, pagination, and more # Chat bubble Control Source: https://chatbase.co/docs/developer-guides/chat-bubble-control Programmatically control your chat bubble with JavaScript methods to open, close, reset conversations, and override configured options at runtime. The Chatbase embed script exposes the `window.chatbase` object with methods to control your chat bubble programmatically. ## Methods ### open(options?) Opens the chat bubble. Optionally sends a message when it opens. ```javascript theme={null} window.chatbase.open(); ``` **Parameters** A message to send automatically when the chat bubble opens. When `true`, the sent message is hidden and the chat bubble stays closed until the bot **starts replying** — so it looks like the assistant reached out proactively. Only applies when `message` is set. ```javascript Open and send a visible message theme={null} window.chatbase.open({ message: "What is Chatbase's pricing?" }); ``` ```javascript Send a hidden message (proactive) theme={null} window.chatbase.open({ message: "What is Chatbase's pricing?", hideMessage: true, }); ``` ### close() Closes the chat bubble. ```javascript theme={null} window.chatbase.close(); ``` ### resetChat() Clears the current conversation and starts a new session. ```javascript theme={null} window.chatbase.resetChat(); ``` Your chat bubble configuration and [custom initial messages](/docs/developer-guides/custom-initial-messages) are preserved after reset. ## Runtime Options Override a bounded set of your agent's configured options for the current page load — the chat bubble's display name, bubble text, footer, message placeholder, dismissible notice, initial messages, and suggested messages. Overrides are never persisted: reloading the page or calling `resetOptions` returns the chat bubble to its dashboard configuration. ### setOptions(options) ```javascript theme={null} window.chatbase.setOptions({ displayName: "Acme Support", bubbleText: "Need help?", footer: "Powered by Acme", messagePlaceholder: "Ask us anything…", dismissibleNotice: "Chats may be recorded for quality purposes.", initialMessages: ["Hi!", "How can I help you today?"], suggestedMessages: ["Track my order", "Talk to a human"], }); ``` In addition, you can show the dictation button only and hide all other input elements (e.g., to use an external input source or to provide a speech-driven experience): ```javascript theme={null} window.chatbase.setOptions({ dictationOnly: true }); ``` All keys are optional — pass only the ones you want to override. **Parameters** Overrides the chat bubble header title. Also updates the launcher button's accessibility labels so assistive technology announces the same name. Maximum 100 characters. Overrides the text shown beside the icon in the floating chat bubble. Maximum 40 characters, and long labels clip on narrow screens. A non-empty value shows the text even when **Show text in the chat bubble** is off in the dashboard; an empty string hides it even when that setting is on. The dashboard's icon alignment still decides which side the icon sits on, so runtime code cannot move it. Reading direction follows the text itself, so a right-to-left label reads correctly on a left-to-right page. Overrides the footer text. Maximum 1000 characters. Overrides the message input placeholder. Maximum 100 characters. Overrides the dismissible notice shown above the message input. Maximum 500 characters. Overrides the agent's initial messages. Array of non-empty strings, limited to 1000 characters in total. Applies immediately if the conversation hasn't started; otherwise it takes effect on the next fresh conversation (for example after `resetChat()`). Writes the same setting as [`setInitialMessages`](/docs/developer-guides/custom-initial-messages) — the last call wins, whichever method made it. Replaces the dashboard-configured suggested message chips. Up to 4 entries, each a non-empty string of at most 200 characters. Suggestions the AI generates during the conversation still take precedence over this override. When `true`, the message box keeps only the dictation button — the text area, send button, voice mode button and attachments button are all hidden, so the only way to compose a message is by speaking. If dictation is turned off for the agent, nothing is left to show and the whole message box disappears. Existing messages, suggested messages and the rest of the chat window are unaffected. Every change to the runtime overrides fires an [`optionsChanged` event](/docs/developer-guides/chatbot-event-listeners) once the agent has re-rendered with it. Use it when you need to wait until an override is actually on screen — for example keeping a plain `