---
title: API Reference - Beeper Developer Docs
description: Complete reference for Beeper Content Provider APIs
---

## 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-31)
- [Parameters](#tab-panel-32)
- [Columns](#tab-panel-33)
- [Example](#tab-panel-34)

```
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-24)
- [Parameters](#tab-panel-25)
- [Example](#tab-panel-26)

```
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-39)
- [Parameters](#tab-panel-40)
- [Columns](#tab-panel-41)
- [Search Example](#tab-panel-42)
- [OpenAtUnread Example](#tab-panel-43)

```
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-27)
- [Example](#tab-panel-28)

```
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-44)
- [Parameters](#tab-panel-45)
- [Return Value](#tab-panel-46)
- [Example](#tab-panel-47)

```
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-35)
- [Parameters](#tab-panel-36)
- [Columns](#tab-panel-37)
- [Example](#tab-panel-38)

```
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-29)
- [Example](#tab-panel-30)

```
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()`
