--- title: API Reference - Beeper Developer Docs description: Complete reference for Beeper Content Provider APIs lastUpdated: 2026-02-23T00:29:17.000Z --- ## Base configuration | Setting | Value | | ------------------------ | ------------------------------------ | | **Authority** | `com.beeper.api` | | **Default Limit** | 100 rows (if not specified) | | **Required Permissions** | `READ_PERMISSION`, `SEND_PERMISSION` | `READ_PERMISSION` and `SEND_PERMISSION` are runtime permissions and must be requested via an in-app prompt. ## Chats API ### List chats Retrieves a list of chats/conversations. - [Endpoint](#tab-panel-17) - [Parameters](#tab-panel-18) - [Columns](#tab-panel-19) - [Example](#tab-panel-20) ``` content://com.beeper.api/chats ``` | Parameter | Type | Description | | ---------------- | ------ | ------------------------------------- | | `limit` | int | Max chats to return Recommended | | `offset` | int | Pagination offset | | `roomIds` | string | Comma-separated room IDs | | `isLowPriority` | 0/1 | Filter low priority chats | | `isArchived` | 0/1 | Filter archived chats | | `isUnread` | 0/1 | Filter unread chats | | `showInAllChats` | 0/1 | Filter visibility | | `protocol` | string | Filter by protocol (e.g., `whatsapp`) | | Column | Type | Description | | ---------------- | ------- | ------------------------- | | `roomId` | string | Unique chat identifier | | `title` | string | Chat display name | | `messagePreview` | string | Last message preview | | `senderEntityId` | string | Last message sender | | `protocol` | string | Network protocol | | `isMuted` | boolean | Mute status | | `unreadCount` | int | Number of unread messages | | `timestamp` | long | Last activity timestamp | | `oneToOne` | boolean | Direct message flag | ``` import androidx.core.net.toUri val cursor = contentResolver.query( "content://com.beeper.api/chats?limit=50&isUnread=1".toUri(), null, null, null, null ) cursor?.use { c -> val roomIdIdx = c.getColumnIndexOrThrow("roomId") val titleIdx = c.getColumnIndexOrThrow("title") val unreadIdx = c.getColumnIndexOrThrow("unreadCount") while (c.moveToNext()) { val roomId = c.getString(roomIdIdx) val title = c.getString(titleIdx) val unread = c.getInt(unreadIdx) println("$title: $unread unread") } } ``` ### Count chats Get the total count of chats matching filters. - [Endpoint](#tab-panel-10) - [Parameters](#tab-panel-11) - [Example](#tab-panel-12) ``` content://com.beeper.api/chats/count ``` Same filters as List Chats ``` val cursor = contentResolver.query( "content://com.beeper.api/chats/count?isUnread=1".toUri(), null, null, null, null ) val count = cursor?.use { if (it.moveToFirst()) { it.getInt(it.getColumnIndexOrThrow("count")) } else 0 } ?: 0 ``` ## Messages API ### List messages Retrieves messages with optional filtering and search. - [Endpoint](#tab-panel-25) - [Parameters](#tab-panel-26) - [Columns](#tab-panel-27) - [Search Example](#tab-panel-28) - [OpenAtUnread Example](#tab-panel-29) ``` content://com.beeper.api/messages ``` | Parameter | Type | Description | | --------------- | ------- | --------------------------- | | `limit` | int | Max messages Default: 100 | | `offset` | int | Pagination offset | | `roomIds` | string | Comma-separated room IDs | | `senderId` | string | Filter by sender contact ID | | `query` | string | Full-text search | | `contextBefore` | int | Messages before match | | `contextAfter` | int | Messages after match | | `openAtUnread` | boolean | Center on first unread | `openAtUnread=true` requires exactly one `roomId` and no other filters When `openAtUnread=true` is used, additional columns `paging_offset` and `last_read` are returned to provide a paging window centered around the first unread message. These columns are not returned otherwise. | Column | Type | Description | | ----------------- | ------- | ----------------------------------- | | `roomId` | string | Chat identifier | | `originalId` | string | Message ID | | `senderContactId` | string | Sender ID | | `timestamp` | long | Message timestamp | | `isSentByMe` | boolean | Sent by current user | | `isDeleted` | boolean | Deletion status | | `type` | string | Message type | | `text_content` | string | Message text | | `reactions` | string | Encoded reactions | | `displayName` | string | Sender name | | `is_search_match` | boolean | Search match flag | | `paging_offset` | int | Pagination helper openAtUnread only | | `last_read` | boolean | Read marker openAtUnread only | ``` // Search with context val searchTerm = "important" val uri = ("content://com.beeper.api/messages?" + "query=${Uri.encode(searchTerm)}" + "&contextBefore=3&contextAfter=2").toUri() contentResolver.query(uri, null, null, null, null)?.use { cursor -> val textIdx = cursor.getColumnIndexOrThrow("text_content") val matchIdx = cursor.getColumnIndexOrThrow("is_search_match") while (cursor.moveToNext()) { val text = cursor.getString(textIdx) val isMatch = cursor.getInt(matchIdx) == 1 if (isMatch) { // This is the matching message println("MATCH: $text") } else { // This is context println("Context: $text") } } } ``` ``` import androidx.core.net.toUri val roomId = "!room:server.com" val uri = ("content://com.beeper.api/messages?" + "roomIds=$roomId&openAtUnread=true&limit=50").toUri() contentResolver.query(uri, null, null, null, null)?.use { cursor -> val textIdx = cursor.getColumnIndexOrThrow("text_content") val offsetIdx = cursor.getColumnIndexOrThrow("paging_offset") val lastReadIdx = cursor.getColumnIndexOrThrow("last_read") while (cursor.moveToNext()) { val text = cursor.getString(textIdx) val offset = cursor.getInt(offsetIdx) val isLastRead = cursor.getInt(lastReadIdx) == 1 // Center UI around first unread using offset/last_read } } ``` ### Count messages Get total message count with filters. - [Endpoint](#tab-panel-13) - [Example](#tab-panel-14) ``` content://com.beeper.api/messages/count ``` ``` val count = contentResolver.query( "content://com.beeper.api/messages/count?roomIds=!room:server.com".toUri(), null, null, null, null )?.use { cursor -> if (cursor.moveToFirst()) { cursor.getInt(cursor.getColumnIndexOrThrow("count")) } else 0 } ?: 0 ``` ### Reactions format The `reactions` column encodes reactions as comma-separated entries: Format `emoji|senderId|isSentByMe|order` Example: `😀|@alice:server.com|0|123,👍|@me:server.com|1|124` ``` // Parse reactions val reactionsRaw = cursor.getString(cursor.getColumnIndexOrThrow("reactions")) ?: "" val reactions = reactionsRaw.split(',') .filter { it.isNotEmpty() } .map { part -> val fields = part.split('|') Reaction( emoji = fields.getOrNull(0) ?: "", senderId = fields.getOrNull(1) ?: "", isSentByMe = fields.getOrNull(2) == "1", order = fields.getOrNull(3)?.toLongOrNull() ?: 0 ) } ``` ### Send message Send a text message to a chat. - [Endpoint](#tab-panel-30) - [Parameters](#tab-panel-31) - [Return Value](#tab-panel-32) - [Example](#tab-panel-33) ``` content://com.beeper.api/messages ``` This is an `insert()` operation, not a query | Parameter | Type | Description | | --------- | ------ | ----------------------------------- | | `roomId` | string | Target room ID Required | | `text` | string | Message body (URL-encoded) Required | - **Success**: URI with `roomId` and `messageId` query parameters - **Failure**: `null` ``` import android.net.Uri import androidx.core.net.toUri val message = "Hello World!" val roomId = "!room:server.com" val resultUri = contentResolver.insert( ("content://com.beeper.api/messages?" + "roomId=$roomId&text=${Uri.encode(message)}").toUri(), null ) if (resultUri != null) { val sentRoomId = resultUri.getQueryParameter("roomId") val messageId = resultUri.getQueryParameter("messageId") println("Sent message $messageId to $sentRoomId") } else { println("Failed to send message") } ``` ## Contacts API ### List contacts Retrieve contacts with optional filtering. - [Endpoint](#tab-panel-21) - [Parameters](#tab-panel-22) - [Columns](#tab-panel-23) - [Example](#tab-panel-24) ``` content://com.beeper.api/contacts ``` | Parameter | Type | Description | | ----------- | ------ | -------------------------- | | `limit` | int | Max contacts Default: 100 | | `offset` | int | Pagination offset | | `senderIds` | string | Comma-separated sender IDs | | `roomIds` | string | Comma-separated room IDs | | `query` | string | Search display names | | `protocol` | string | Filter by protocol | | Column | Type | Description | | -------------------- | ------- | ---------------------- | | `id` | string | Contact identifier | | `roomIds` | string | Associated rooms (CSV) | | `displayName` | string | Display name | | `contactDisplayName` | string | Alternative name | | `linkedContactId` | string | Linked contact | | `itsMe` | boolean | Current user flag | | `protocol` | string | Network protocol | ``` val cursor = contentResolver.query( "content://com.beeper.api/contacts?query=John".toUri(), null, null, null, null ) cursor?.use { c -> val idIdx = c.getColumnIndexOrThrow("id") val nameIdx = c.getColumnIndexOrThrow("displayName") val roomsIdx = c.getColumnIndexOrThrow("roomIds") while (c.moveToNext()) { val contactId = c.getString(idIdx) val name = c.getString(nameIdx) val rooms = c.getString(roomsIdx).split(",") println("$name is in ${rooms.size} rooms") } } ``` ### Count contacts Get total contact count with filters. - [Endpoint](#tab-panel-15) - [Example](#tab-panel-16) ``` content://com.beeper.api/contacts/count ``` ``` val count = contentResolver.query( "content://com.beeper.api/contacts/count?protocol=whatsapp".toUri(), null, null, null, null )?.use { cursor -> if (cursor.moveToFirst()) { cursor.getInt(cursor.getColumnIndexOrThrow("count")) } else 0 } ?: 0 ``` ## Common URI examples ``` # Recent unread chats content://com.beeper.api/chats?isUnread=1&limit=10 # WhatsApp chats only content://com.beeper.api/chats?protocol=whatsapp # Search messages for "meeting" content://com.beeper.api/messages?query=meeting # Active chats (not archived or low priority) content://com.beeper.api/chats?isArchived=0&isLowPriority=0 ``` ## Performance guidelines Best Practices 1. **Always specify `limit`** - Avoid unbounded queries 2. **Use filters** - Reduce data with `roomIds`, `protocol`, etc. 3. **Close cursors** - Use try-with-resources or `.use{}` in Kotlin 4. **Batch reads** - Combine filters instead of multiple queries 5. **URL encode** - Always encode text parameters with `Uri.encode()`