This is the full developer documentation for Beeper Developer Docs # Build on Beeper > Build applications with access to all chats across networks, extend Beeper with new capabilities. import { Card, LinkCard, CardGrid } from "@astrojs/starlight/components"; {/* */} # Integrate with Beeper Android import { CardGrid, LinkCard } from "@astrojs/starlight/components"; There are two ways to integrate with Beeper Android: # Content Providers > Query chats, messages, and contacts from Beeper using Android Content Provider APIs import { Aside, Tabs, TabItem, Steps, Badge, LinkButton } from '@astrojs/starlight/components'; import { Callout } from '@stainless-api/docs/components'; Build Android apps and connected devices that integrate with Beeper using standard Android [content provider](https://developer.android.com/guide/topics/providers/content-providers) APIs. Content providers encapsulate data and provide mechanisms for defining data security, serving as the standard interface between processes. Experimental

Content providers are experimental and APIs are subject to change.

### What you can build - **Universal Search** - Search across all chats and messages - **Widgets & Dashboards** - Display chat summaries and unread counts - **Automation** - Send messages and react to changes - **Wearables** - Integrate with watches and IoT devices ### Key features - **Authority**: `com.beeper.api` - **Permissions**: Runtime (request at first use) - **Data Access**: Chats, messages, contacts - **Operations**: Query, insert, observe changes - **Protocol Support**: WhatsApp, Telegram, Signal, and more ## Quick start 1. **Add permissions to your manifest** ```xml title="AndroidManifest.xml" ``` These are custom Beeper permissions. Declare them in the manifest and request them at runtime with a permission prompt. ```kotlin // Request at runtime (e.g., in an Activity or Fragment) val requestPermissions = registerForActivityResult( ActivityResultContracts.RequestMultiplePermissions() ) { results -> val hasRead = results["com.beeper.android.permission.READ_PERMISSION"] == true val hasSend = results["com.beeper.android.permission.SEND_PERMISSION"] == true // Handle granted/denied states } requestPermissions.launch(arrayOf( "com.beeper.android.permission.READ_PERMISSION", "com.beeper.android.permission.SEND_PERMISSION" )) ``` 2. **Query recent chats** ```kotlin import androidx.core.net.toUri val uri = "content://com.beeper.api/chats?limit=50".toUri() contentResolver.query(uri, null, null, null, null)?.use { cursor -> val titleIdx = cursor.getColumnIndexOrThrow("title") val previewIdx = cursor.getColumnIndexOrThrow("messagePreview") val unreadIdx = cursor.getColumnIndexOrThrow("unreadCount") while (cursor.moveToNext()) { val title = cursor.getString(titleIdx) val preview = cursor.getString(previewIdx) val unread = cursor.getInt(unreadIdx) // Display chat info in your UI } } ``` 3. **Send a message** ```kotlin import android.net.Uri import androidx.core.net.toUri val message = "Hello from my app!" val roomId = "!room:server.com" val result = contentResolver.insert( ("content://com.beeper.api/messages?" + "roomId=$roomId&text=${Uri.encode(message)}").toUri(), null ) result?.let { val messageId = it.getQueryParameter("messageId") // Message sent successfully } ``` 4. **Observe changes** ```kotlin import android.os.Handler import android.os.Looper import android.database.ContentObserver import androidx.core.net.toUri contentResolver.registerContentObserver( "content://com.beeper.api/chats".toUri(), true, object : ContentObserver(Handler(Looper.getMainLooper())) { override fun onChange(selfChange: Boolean) { // Re-query and refresh your UI } } ) ``` Only `content://com.beeper.api/chats` currently emits change notifications. Observers for `messages` or `contacts` will not fire. ## Common use cases ```kotlin // Get total unread count across all chats val uri = "content://com.beeper.api/chats/count?isUnread=1".toUri() val cursor = contentResolver.query(uri, null, null, null, null) val unreadTotal = cursor?.use { if (it.moveToFirst()) { it.getInt(it.getColumnIndexOrThrow("count")) } else 0 } ?: 0 ``` ```kotlin // Search with surrounding context val searchTerm = "meeting" val uri = ("content://com.beeper.api/messages?" + "query=${Uri.encode(searchTerm)}" + "&contextBefore=2&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 // Highlight matches in UI } } ``` ```kotlin // Get WhatsApp chats only val uri = "content://com.beeper.api/chats?protocol=whatsapp".toUri() contentResolver.query(uri, null, null, null, null)?.use { cursor -> // Process WhatsApp-specific chats } ``` ## Performance tips Best practices 1. **Always Set Limits** - Default limit is 100, but always specify `limit` to avoid large queries 2. **Use Filters** - Filter by `roomIds`, `protocol`, or other parameters to reduce data 3. **Batch Operations** - Combine multiple filters in one query instead of multiple queries 4. **Lifecycle Awareness** - Register/unregister observers with your component lifecycle ## Supported protocols
| Protocol | Identifier | Features | |----------|------------|----------| | WhatsApp | `whatsapp` | | | Telegram | `telegram` | | | Signal | `signal` | | | Matrix/Beeper | `beeper`, `matrix` | | | Discord | `discord` | | | Slack | `slack` | | | Google Messages | `gmessages` | | | Others | Various | |
## Learn more - **[API Reference โ†’](/android/content-providers/api-reference)** - Complete API documentation for all endpoints - **[Integration Guide โ†’](/android/content-providers/integration-guide)** - Permissions, notifications, and troubleshooting - **[Android Docs โ†’](https://developer.android.com/guide/topics/providers/content-provider-basics)** - Official content provider documentation # API Reference > Complete reference for Beeper Content Provider APIs import { Aside, Tabs, TabItem, Card, Badge } from '@astrojs/starlight/components'; import { Callout } from '@stainless-api/docs/components'; ## 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. ``` content://com.beeper.api/chats ``` | Parameter | Type | Description | |-----------|------|-------------| | `limit` | int | Max chats to return | | `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 | ```kotlin 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. ``` content://com.beeper.api/chats/count ``` Same filters as List Chats ```kotlin 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. ``` content://com.beeper.api/messages ``` | Parameter | Type | Description | |-----------|------|-------------| | `limit` | int | Max messages | | `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 | | `last_read` | boolean | Read marker | ```kotlin // 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") } } } ``` ```kotlin 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. ``` content://com.beeper.api/messages/count ``` ```kotlin 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: `emoji|senderId|isSentByMe|order` Example: `๐Ÿ˜€|@alice:server.com|0|123,๐Ÿ‘|@me:server.com|1|124` ```kotlin // 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. ``` content://com.beeper.api/messages ``` This is an `insert()` operation, not a query | Parameter | Type | Description | |-----------|------|-------------| | `roomId` | string | Target room ID | | `text` | string | Message body (URL-encoded) | - **Success**: URI with `roomId` and `messageId` query parameters - **Failure**: `null` ```kotlin 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. ``` content://com.beeper.api/contacts ``` | Parameter | Type | Description | |-----------|------|-------------| | `limit` | int | Max contacts | | `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 | ```kotlin 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. ``` content://com.beeper.api/contacts/count ``` ```kotlin 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 ```text # 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()` # Integration Guide > Permissions, notifications, troubleshooting, and best practices for Beeper Content Provider integration import { Steps, Tabs, TabItem } from '@astrojs/starlight/components'; import { Callout } from '@stainless-api/docs/components'; ## Permissions ### Required permissions Beeper uses custom Android permissions that must be declared in your app's manifest: ```xml title="AndroidManifest.xml" ``` | Permission | Type | Purpose | |------------|------|---------| | `READ_PERMISSION` | Runtime | Query chats, messages, contacts | | `SEND_PERMISSION` | Runtime | Send messages | These are runtime permissions. Declare them in the manifest and request them via an in-app permission prompt. ```kotlin // Check and request at runtime val hasRead = ContextCompat.checkSelfPermission( this, "com.beeper.android.permission.READ_PERMISSION" ) == PackageManager.PERMISSION_GRANTED val hasSend = ContextCompat.checkSelfPermission( this, "com.beeper.android.permission.SEND_PERMISSION" ) == PackageManager.PERMISSION_GRANTED if (!hasRead || !hasSend) { val launcher = registerForActivityResult( ActivityResultContracts.RequestMultiplePermissions() ) { results -> val grantedRead = results["com.beeper.android.permission.READ_PERMISSION"] == true val grantedSend = results["com.beeper.android.permission.SEND_PERMISSION"] == true // Handle granted/denied states } launcher.launch(arrayOf( "com.beeper.android.permission.READ_PERMISSION", "com.beeper.android.permission.SEND_PERMISSION" )) } ``` Users can deny permissions; handle gracefully and explain limited functionality if not granted. See Android's [uses-permission](https://developer.android.com/guide/topics/manifest/uses-permission-element) and [permission](https://developer.android.com/guide/topics/manifest/permission-element) documentation. ## Change notifications ### ContentObserver setup Use `ContentObserver` to react to data changes in real-time: 1. **Create an observer** ```kotlin class ChatObserver(handler: Handler) : ContentObserver(handler) { override fun onChange(selfChange: Boolean) { // Data changed - refresh your UI refreshChatList() } } ``` 2. **Register the observer** ```kotlin import androidx.core.net.toUri val observer = ChatObserver(Handler(Looper.getMainLooper())) contentResolver.registerContentObserver( "content://com.beeper.api/chats".toUri(), true, // Notify for descendant URIs observer ) ``` 3. **Unregister when done** ```kotlin override fun onDestroy() { super.onDestroy() contentResolver.unregisterContentObserver(observer) } ``` ### Common observer patterns ```kotlin class ChatViewModel : ViewModel() { private var observer: ContentObserver? = null fun startObserving(contentResolver: ContentResolver) { observer = object : ContentObserver(Handler(Looper.getMainLooper())) { override fun onChange(selfChange: Boolean) { loadChats() } } contentResolver.registerContentObserver( "content://com.beeper.api/chats".toUri(), true, observer!! ) } override fun onCleared() { observer?.let { getApplication() .contentResolver .unregisterContentObserver(it) } } } ```

Only the content://com.beeper.api/chats URI currently emits change notifications. Observers on messages and contacts are not implemented yet.

Some Cursor implementations auto-update, but explicit observers ensure consistent behavior across Android versions. ## Troubleshooting ### Common issues and solutions #### No rows returned **Causes:** - Beeper not installed/logged in - Missing permissions - Too restrictive filters **Solutions:** - Verify Beeper installation - Check manifest permissions - Add `limit` parameter - Adjust filters #### Permission denied **Causes:** - Permissions not declared in manifest - Runtime permissions not granted **Solutions:** - Add permissions to manifest - Request permissions at runtime #### Message send fails **Causes:** - Invalid room ID - Text not URL-encoded - Network issues **Solutions:** - Verify `roomId` format - Use `Uri.encode()` for text - Check Beeper connectivity #### UI not updating **Causes:** - No ContentObserver registered - Wrong Handler/Looper - Observer not on main thread **Solutions:** - Register ContentObserver - Use `Handler(Looper.getMainLooper())` - Refresh on main thread ### Debug checklist 1. **Verify Beeper installation** ```kotlin val packageManager = context.packageManager try { packageManager.getPackageInfo("com.beeper.android", 0) // Beeper is installed } catch (e: PackageManager.NameNotFoundException) { // Beeper not installed } ``` 2. **Check permissions** ```kotlin val permissions = listOf( "com.beeper.android.permission.READ_PERMISSION", "com.beeper.android.permission.SEND_PERMISSION" ) permissions.forEach { permission -> val granted = checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED Log.d("Permissions", "$permission: $granted") } ``` 3. **Test basic query** ```kotlin try { val cursor = contentResolver.query( "content://com.beeper.api/chats?limit=1".toUri(), null, null, null, null ) Log.d("Debug", "Query returned ${cursor?.count ?: 0} rows") cursor?.close() } catch (e: Exception) { Log.e("Debug", "Query failed", e) } ``` ## Frequently asked questions
**What's the authority?**
`com.beeper.api`
**Do I need runtime permissions?**
Yes. Request READ_PERMISSION and SEND_PERMISSION at runtime in addition to declaring them in the manifest.
**What's the default limit?**
Messages and contacts default to 100 if not specified. Always specify `limit` for chats to avoid large queries.
**Can I use this from a service?**
Yes, content providers work from any Android component with a Context.
**How do I open at the first unread message?**
Use `openAtUnread=true` with exactly one `roomId` and no other filters.
**Can I send media messages?**
Currently only text messages are supported via the content provider API.
**Is pagination cursor-based or offset-based?**
Offset-based. Use `limit` and `offset` parameters.
**Are queries case-sensitive?**
Text searches are case-insensitive. Room/sender IDs are case-sensitive.
**What's the maximum limit?**
No hard limit, but use reasonable values (50-200) for UI responsiveness.
**Can I cache query results?**
Yes, but use ContentObserver to invalidate cache on changes.
**How often do observers trigger?**
Immediately on data change. Consider debouncing for high-frequency updates.
**Should I close Cursors?**
Always. Use try-with-resources or Kotlin's `.use{}` extension.
## Best practices ### Always URL encode ```kotlin // โœ… Good Uri.encode("Hello & welcome!") // โŒ Bad "Hello & welcome!" ``` ### Use lifecycle components ```kotlin // Register in onStart override fun onStart() { super.onStart() registerObserver() } // Unregister in onStop override fun onStop() { super.onStop() unregisterObserver() } ``` ### Handle null results ```kotlin // Always check for null val result = contentResolver.insert(uri, null) if (result != null) { // Success } else { // Handle failure } ``` ### Batch operations ```kotlin // โœ… Single query with multiple IDs "roomIds=!room1:server,!room2:server" // โŒ Multiple queries query("roomIds=!room1:server") query("roomIds=!room2:server") ``` ## Testing Tips Development testing 1. **Use Android Studio's Database Inspector** to view ContentProvider data 2. **Test with different Beeper account states** (logged out, no chats, many chats) 3. **Verify permission handling** by testing without declaring permissions 4. **Test observer behavior** by sending messages from another device 5. **Check edge cases** like empty results, special characters, long text ## Need help? - Check the [API Reference](/android/content-providers/api-reference) for detailed endpoint documentation - Review the [Quick start](/android/content-providers#quick-start) for basic examples - Contact [info@beeper.com](mailto:info@beeper.com) for integration support # Android Intents > Control Beeper Android through Android Intents from other apps, Tasker, or command-line import { Steps, Tabs, TabItem, Code } from '@astrojs/starlight/components'; import { Callout } from '@stainless-api/docs/components'; Beeper Android supports Android Intents that allow other apps, automation tools like Tasker, or command-line tools to interact with and control certain Beeper features. This enables powerful automation and integration possibilities. ## Supported intents ### Toggle incognito mode Control Beeper's incognito mode programmatically from external sources. - Requires Beeper Android version 4.31.1 or later - Requires a Beeper Plus or PlusPlus subscription to enable Incognito Mode **Intent details:** - **Action**: `com.beeper.android.TOGGLE_INCOGNITO_MODE` - **Component**: `com.beeper.android/com.beeper.ext.ExternalBroadcastReceiver` - **Extra**: `enabled` (boolean) - `true` to enable, `false` to disable incognito mode ## Usage examples ```kotlin import android.content.Intent import android.content.ComponentName // Enable incognito mode fun enableIncognitoMode() { val intent = Intent().apply { action = "com.beeper.android.TOGGLE_INCOGNITO_MODE" component = ComponentName( "com.beeper.android", "com.beeper.ext.ExternalBroadcastReceiver" ) putExtra("enabled", true) } sendBroadcast(intent) } // Disable incognito mode fun disableIncognitoMode() { val intent = Intent().apply { action = "com.beeper.android.TOGGLE_INCOGNITO_MODE" component = ComponentName( "com.beeper.android", "com.beeper.ext.ExternalBroadcastReceiver" ) putExtra("enabled", false) } sendBroadcast(intent) } ``` **Task setup:** 1. Create a new Task in Tasker 2. Add Action โ†’ System โ†’ Send Intent 3. Configure the intent: - **Action**: `com.beeper.android.TOGGLE_INCOGNITO_MODE` - **Package**: `com.beeper.android` - **Class**: `com.beeper.ext.ExternalBroadcastReceiver` - **Extra**: `enabled:true` (or `enabled:false`) - **Target**: Broadcast Receiver ```bash # Enable incognito mode adb shell am broadcast \ -a com.beeper.android.TOGGLE_INCOGNITO_MODE \ --ez enabled true \ -n com.beeper.android/com.beeper.ext.ExternalBroadcastReceiver # Disable incognito mode adb shell am broadcast \ -a com.beeper.android.TOGGLE_INCOGNITO_MODE \ --ez enabled false \ -n com.beeper.android/com.beeper.ext.ExternalBroadcastReceiver ``` Requires USB debugging to be enabled on the target device. ## Troubleshooting Common issues **Intent not received:** - Verify Beeper Android is installed and updated - Check that the component name is exactly: `com.beeper.android/com.beeper.ext.ExternalBroadcastReceiver` - Ensure the action string is correct: `com.beeper.android.TOGGLE_INCOGNITO_MODE` **ADB command fails:** - Confirm ADB is properly installed and device is connected - Check that the device is authorized for ADB access ### Verification To verify the intent was received, check Beeper's incognito mode status in the app UI after sending the intent. # Bridges & Self-Hosting > Understanding Matrix bridges and how Beeper uses them import { CardGrid, LinkCard, Icon } from '@astrojs/starlight/components'; import { Callout } from '@stainless-api/docs/components'; Beeper is built on top of the Matrix protocol. When you use Beeper to message someone on WhatsApp, Telegram, or any other supported network, you're using a Matrix bridge - a piece of software that translates messages between Matrix and the other chat network. Beeper natively supports 11 networks out of the box with integrated bridges. They either run on your device (when using On-Device Connections) or in our cloud (when using Beeper Cloud). All of our bridges are open source and we maintain [a library](https://github.com/mautrix/go) to easily build new ones. Any user can run their own bridge, connected to any network they wish, and integrate it with Beeper seamlessly with everything syncing across all devices. What is Matrix? [Matrix](https://matrix.org) is an open standard for secure, decentralized, real-time communication. It provides an open network where users can communicate across different service providers, similar to how email works. ## Community & support - Join [#self-hosting:beeper.com](https://matrix.to/#/#self-hosting:beeper.com) for self-hosting help - Email [info@beeper.com](mailto:info@beeper.com) for sponsorship opportunities # Self-Hosting Bridges > Run your own bridges with Beeper Bridge Manager import { Aside, CardGrid, LinkCard, Steps, Tabs, TabItem } from '@astrojs/starlight/components'; import { Callout } from '@stainless-api/docs/components'; Support notice Self-hosting bridges are not entitled to the usual level of customer support. For help with self-hosting, join [#self-hosting:beeper.com](https://matrix.to/#/#self-hosting:beeper.com). ## Overview The easiest way to self-host bridges is using **Beeper Bridge Manager** (`bbctl`). It's a tool for running self-hosted bridges with your Beeper account. ### Key benefits - **Security** - Run official Beeper bridges on your own hardware securely - **Flexibility** - Connect custom or third-party bridges without self-hosting a full Matrix server - **Control** - Maintain complete control over message processing and encryption Accounts bridged with self-hosting are **free** and do not count against your account limits. ## Prerequisites Before you begin, ensure you have: - **Operating System**: Linux or macOS (amd64 or arm64) Windows users should use WSL - **Python**: Python 3 with `venv` module for Python-based bridges - **ffmpeg**: Required for media conversion in some bridges - **Beeper account**: You'll need an active Beeper account ## Installation ### Step 1: Download bbctl Download the appropriate binary for your system: You can check the [GitHub releases](https://github.com/beeper/bridge-manager/releases) for the latest version, checksum(s) and source code. ```bash git clone https://github.com/beeper/bridge-manager.git cd bridge-manager ./build.sh ``` ### Step 2: Install dependencies ```bash sudo apt install python3 python3-venv ffmpeg ``` ```bash brew install python ffmpeg ``` ### Step 3: Log in to Beeper ```bash bbctl login ``` ## Running official bridges 1. **Start a bridge** ```bash bbctl run sh- ``` Example: `bbctl run sh-whatsapp` 2. **Configure the bridge** - Send a DM to the bridge bot: `@sh-bot:beeper.local` - Follow the bot's instructions to log in to the remote network 3. **Keep it running** - Currently runs in foreground (use `tmux` or `screen` for persistence) - Service mode for automatic startup coming soon Data storage Bridge data is stored in `~/.local/share/bbctl` by default. You can change this location in `~/.config/bbctl.json`. ## Supported official bridges
| Bridge | Identifier(s) | Repository | |----------------------|--------------------------------------|-----------------------------------------------------| | Telegram | `telegram` | [mautrix/telegram](https://github.com/mautrix/telegram) | | WhatsApp | `whatsapp` | [mautrix/whatsapp](https://github.com/mautrix/whatsapp) | | Signal | `signal` | [mautrix/signal](https://github.com/mautrix/signal) | | Discord | `discord` | [mautrix/discord](https://github.com/mautrix/discord) | | Slack | `slack` | [mautrix/slack](https://github.com/mautrix/slack) | | Google Messages | `gmessages`, `googlemessages`, `rcs`, `sms` | [mautrix/gmessages](https://github.com/mautrix/gmessages) | | Google Voice | `gvoice`, `googlevoice` | [mautrix/gvoice](https://github.com/mautrix/gvoice) | | Meta (FB/Instagram) | `meta`, `instagram`, `facebook` | [mautrix/meta](https://github.com/mautrix/meta) | | Google Chat | `googlechat`, `gchat` | [mautrix/googlechat](https://github.com/mautrix/googlechat) | | X (formerly Twitter) | `twitter` | [mautrix/twitter](https://github.com/mautrix/twitter) | | Bluesky | `bluesky`, `bsky` | [mautrix/bluesky](https://github.com/mautrix/bluesky) | | iMessage | `imessage` | [mautrix/imessage](https://github.com/mautrix/imessage) | | iMessage (Go) | `imessagego` | [beeper/imessage](https://github.com/beeper/imessage) | | LinkedIn | `linkedin` | [mautrix/linkedin](https://github.com/mautrix/linkedin) | | IRC (Heisenbridge) | `heisenbridge`, `irc` | [hifi/heisenbridge](https://github.com/hifi/heisenbridge) |
## Running third-party bridges ### Bridge v2-based bridges For bridges built with mautrix-go's Bridge v2 framework: 1. **Generate config** ```bash bbctl config --type bridgev2 sh- ``` 2. **Add network configuration** Add the `network` section to the generated config with bridge-specific settings 3. **Run the bridge** Run the bridge with your config file ### Custom bridges For other third-party bridges: 1. **Generate registration** ```bash bbctl register sh- ``` 2. **Configure the bridge** Configure the bridge according to its documentation 3. **Run the proxy** ```bash bbctl proxy -r registration.yaml ``` This connects your bridge to Beeper ## Managing bridges ### Delete a bridge This action is permanent and cannot be undone. ```bash bbctl delete sh- ``` This permanently removes the bridge and all associated data from Beeper servers. # Deploy to Fly.io > Deploy Beeper self-hosted bridges to Fly.io from your browser import DeployToFlyApp from '../../../../components/DeployToFlyApp'; Use this tool to deploy and manage Beeper self-hosted bridges on Fly.io. # Beeper Desktop API > Local API & MCP for Beeper Desktop import { Card, CardGrid, Steps, } from "@astrojs/starlight/components"; import { Callout, Button } from "@stainless-api/docs/components"; {/* # Overview :badge[Experimental]{variant=note} */} Beeper Desktop API is a fully local API for all your chats across WhatsApp, Instagram, Telegram, Google Messages, Google Voice, Google Chat, Messenger, Signal, LinkedIn, X, Discord, Slack, and more. It comes with an API and SDKs for JavaScript, Python, Go, and PHP that allow you to search chats, send messages, and control Beeper Desktop. You can search your message history, send or draft messages, and integrate your desktop client with Beeper Desktop. **Personal use recommended** We recommend Beeper Desktop API for personal use only. Sending too many messages might result in account suspension by the networks. Actions like searching or fetching existing chats or messages are always local and can be used without limitations. **Limitations** - Beeper Desktop API runs inside Beeper Desktop and requires Beeper Desktop to be running to be accessible. By default, it can't be accessed from other devices. - Message history might be limited. Beeper indexes your messages from the networks in the background, when you first add an account, only recent messages might be available. For best results, prefer using On-Device Connections instead of Beeper Cloud when connecting accounts. - iMessage is only supported on macOS. ## Get started If you are looking for the MCP to integrate into apps like Claude Desktop or Codex, see the [MCP documentation](/desktop-api/mcp). The easiest way to start building with the Desktop API directly is to ask your agent to use our SDKs. ``` I want to build an integration with Beeper Desktop API. Please visit https://developers.beeper.com/desktop-api/index.md Use the official SDKs to build it. ``` You can also use the [REST API](https://developers.beeper.com/desktop-api-reference/) directly or use the [built-in MCP server](/desktop-api/mcp). Support for more languages coming soon! ## Projects built on Desktop API - **[f/deeper](https://github.com/f/deeper)** (Swift) - A macOS messaging analytics app for Beeper that connects to the local Beeper Desktop API and visualizes conversations across platforms. - **[blqke/beepctl](https://github.com/blqke/beepctl)** (TypeScript) - CLI for Beeper Desktop API; unified messaging from your terminal, including AI-agent workflows across platforms. - **[cameronaaron/beeper-go-sdk](https://github.com/cameronaaron/beeper-go-sdk)** (Rust) - Pure Go SDK for the Beeper Desktop API with a chat-archiver CLI tool. - **[foeken/beeper-cli](https://github.com/foeken/beeper-cli)** (Go) - CLI for Beeper Desktop API. - **[mimen/beeper-messaging-tools](https://github.com/mimen/beeper-messaging-tools)** (TypeScript) - A web app for interacting with the Beeper Desktop API. - **[adamanz/omnichannel-messenger](https://github.com/adamanz/omnichannel-messenger)** (TypeScript) - Multi-platform messaging app using Beeper Desktop API to send messages to WhatsApp, LinkedIn, Slack, and more from one interface. - **[BosTheCoder/beeper-wsl-proxy](https://github.com/BosTheCoder/beeper-wsl-proxy)** (Python) - Beeper WSL proxy that allows access to the Beeper Desktop API from WSL. - **[ErdemGKSL/beeper-desktop-api](https://github.com/ErdemGKSL/beeper-desktop-api)** (Rust) - Beeper Desktop API project (no repo description provided). - **[nerveband/beeper-api-cli](https://github.com/nerveband/beeper-api-cli)** (Go) - Cross-platform CLI for using your Beeper client from the command line via the Beeper Desktop API. - **[raycast/extensions/extensions/beeper](https://github.com/raycast/extensions/tree/main/extensions/beeper)** (TypeScript) - Raycast extension built using the Desktop API JS SDK. ## Questions and feedback - Join our community room on Beeper. In Beeper Desktop, go to **Settings** โ†’ **Integrations** and click "**Join Community**". Or join with [Matrix](https://matrix.to/#/#beeper-developers:beeper.com). - You can always reach out to us on [help@beeper.com](mailto:help@beeper.com) or DM us on [X](https://x.com/beeper) with any questions or feedback. # Advanced > Advanced guides for Beeper Desktop API import { CardGrid, LinkCard } from "@astrojs/starlight/components"; # Remote Access > Connect Beeper Desktop API to external services like ChatGPT, n8n, Claude.ai, and more. import { Aside, CardGrid, LinkCard } from "@astrojs/starlight/components"; import { Callout } from "@stainless-api/docs/components"; Understand the risks Beeper Desktop API is designed to be used locally on your computer. Exposing it to the internet can expose chat history and might allow others to send messages on your behalf. Please only continue if you understand the risks. Beeper is not responsible for any consequences arising from exposing Beeper Desktop API to the internet. By default, Beeper Desktop API and MCP server come with limitations for security: - The server binds to `localhost`, so only the local machine is allowed to connect. - URLs in the OpenAPI spec and OAuth discovery endpoints use `http://localhost:23373` (or the custom port you set) - CORS headers also limit access to the local machine You can enable **Remote Access** in **Settings** โ†’ **Integrations** โ†’ **Advanced** to expose the API to the internet. This will change the following: - The server binds to `0.0.0.0`, listening on every network interface, including VPNs and tunnel adapters. - Base URL is computed from `X-Forwarded-Host`, `X-Forwarded-Proto`, and forwarded port headers when traffic arrives via a proxy or tunnel. If those headers are missing, it falls back to the direct host that reached your machine. - OAuth discovery endpoints (`/.well-known/...`) allow cross-origin `GET` requests so they can be accessed and used by external services. This allows you to tunnel/proxy traffic to your local machine and make sure everything works as expected. For example, this is required for PKCE authentication to work properly with external services like ChatGPT, n8n, Claude.ai, and more. ## Tunneling Beeper does not provide any tunneling services. The easiest way to expose the API to the internet is to use a third-party tunneling service like Cloudflare or Tailscale. # Cloudflare Quick Tunnels > Use Cloudflare's TryCloudflare tunnels to share the Beeper Desktop API or MCP server over HTTPS in a few commands. import { Steps, Tabs, TabItem, Code, LinkButton } from "@astrojs/starlight/components"; import { Callout, Button } from "@stainless-api/docs/components"; Understand the risks Beeper Desktop API is designed to be used locally on your computer. Exposing it to the internet can expose chat history and might allow others to send messages on your behalf. Please only continue if you understand the risks. Beeper is not responsible for any consequences arising from exposing Beeper Desktop API to the internet. Also note that Beeper has no affiliation with Cloudflare and this guide is for educational purposes only. Cloudflare's Quick Tunnels spin up an HTTPS endpoint that forwards traffic straight to your local Beeper Desktop API. The tunnel lives only as long as the `cloudflared` process runs, making it ideal for testing and personal usage. Before you begin, make sure **Remote Access** is enabled in **Settings** โ†’ **Integrations** โ†’ **Advanced** so the server listens on all interfaces. Cloudflare Quick Tunnels do not support Server-Sent Events (SSE) (`/v0/sse`), so if you are using the MCP server you must use the Streamable HTTP transport (`/v0/mcp`). ## Install `cloudflared` Install via Homebrew: Use Windows Package Manager: Download the latest release and install it manually: Make sure to check Cloudflare's [Downloads page](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/) for the most up-to-date instructions. ## Launch a quick tunnel 1. **Start the tunnel** 2. **Copy the forwarding URL** `cloudflared` prints a line similar to `https://example.trycloudflare.com`. Append `/v0/mcp` for the MCP server. 3. **Authenticate from your remote tool** Use the standard OAuth or token flow. Cloudflare preserves forwarded headers, so the Desktop API generates redirect URLs based on the public hostname automatically. ### Keep the tunnel online The URL is only valid while the `cloudflared` process runs. Use a session manager such as `screen`, `tmux`, or a background service if you need to keep it available. Press Ctrl + C to close the tunnel immediately. ## Harden for stable deployments Cloudflare Quick Tunnels has request limits and no authentication. For persistent deployments on Cloudflare, you can use Cloudflare Zero Trust: Zero Trust tunnels let you pin a hostname, enforce identity checks, and restrict IP rangesโ€”all without changing your local Beeper Desktop configuration. # Authentication > Authentication methods for Beeper Desktop API import { Aside, Steps, Tabs, TabItem, Badge } from '@astrojs/starlight/components'; Beeper Desktop API requires an access token for all endpoints. ## Create a token 1. **[Make sure Beeper Desktop API is enabled and running](/desktop-api#get-started)** 2. **Open Settings** โ†’ **Integrations** in Beeper Desktop 3. **Click the "+" button** next to "Approved connections" 4. **Follow the instructions to create your token** If you want to allow your users to interactively allow your application to access their Beeper Desktop, you can use OAuth 2.0 with [PKCE](https://oauth.net/2/pkce/) for authentication. [RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414) compliant metadata is available at `http://localhost:23373/.well-known/oauth-authorization-server` Built-in MCP server uses OAuth natively for authentication. See the [authorization](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) page of the MCP specification for more details. [Get in touch with us](mailto:help@beeper.com) for help, questions, or feedback. ## Use your token All requests to Beeper Desktop API, including requests to the MCP endpoints (`/v0/mcp`, `/v0/sse`), support the `Authorization` header with the `Bearer` token for authentication. When used with the MCP server, MCP authentication is bypassed and the token is used directly. ``` Authorization: Bearer ``` ## Desktop API discovery and introspection - `GET /v1/info` returns metadata about the active Desktop API server and exposes endpoint URLs for discovery. OAuth token introspection is available via `POST /oauth/introspect` and accepts form-encoded payloads: ```text Content-Type: application/x-www-form-urlencoded token=&token_type_hint=access_token ``` The endpoint returns `active: true` with token metadata (when active), or `active: false` for inactive/invalid tokens. # Beeper Desktop MCP > Connect Beeper Desktop with AI assistants via Model Context Protocol import { Code, Aside, Steps, LinkButton, Tabs, TabItem, } from "@astrojs/starlight/components"; import DetailsHashHandler from "../../../components/DetailsHashHandler.astro"; import NodeInstallInstructions from "../../../components/NodeInstallInstructions.astro"; import { CardGrid, LinkCard } from "@astrojs/starlight/components"; import { generateCursorDeeplink, generateVSCodeDeeplink, generateRaycastDeeplink } from "../../../components/mcp-deeplink-helper"; import { Callout, Button } from "@stainless-api/docs/components"; Beeper Desktop API includes a built-in MCP (Model Context Protocol) server, enabling seamless integration with AI assistants like Claude Desktop, Claude Code, Codex, Cursor, and more.
What is Model Context Protocol? MCP is an open protocol that standardizes how applications provide context to large language models (LLMs). Think of MCP like a USB-C port for AI applications. Just as USB-C provides a standardized way to connect your devices to various peripherals and accessories, MCP provides a standardized way to connect AI models to different data sources and tools. MCP enables you to build agents and complex workflows on top of LLMs and connects your models with the world.
**Beeper MCP runs on your device and requires Beeper Desktop to be running.** ## Quick start 1. **[Install Beeper Desktop](https://www.beeper.com/download)** and make sure it's running. 2. **Connect your MCP client** Additional options like Claude Code, Codex, Streamable HTTP, and stdio are available in **Settings** โ†’ **Integrations**. ## Manual setup As long as your MCP client supports OAuth and Streamable HTTP, you just need to add the MCP server URL to your client and the authentication will be handled automatically.
Manually setting the token

If you are having issues with authentication, or want to skip the authentication process, you can create an access token manually. See the Authentication Guide for details on how to obtain tokens. When used with the MCP server, MCP authentication is bypassed and the token is used directly.

Streamable HTTP with custom token

stdio via mcp-remote with custom token

### Client-specific instructions
Cursor Install MCP Server If you are running on different port, you can setup manually: **Configuration file:** `~/.cursor/mcp.json` **Streamable HTTP**
Claude Code **Install globally:** **Install within a directory:** **With manual token:** Read more on Claude Code's official documentation.
Visual Studio Code You can use this link to install globally in VS Code.

Workspace or user configuration (mcp.json)

Create .vscode/mcp.json in your workspace, or open MCP: Open User Configuration to edit your profile mcp.json:

Command line (code --add-mcp)

Read more on Visual Studio Code's official documentation.
Codex **Add via CLI:** **With manual token:** Read more on Codex's official documentation.
stdio Prerequisite Using Beeper MCP server with stdio requires `node` to be installed on your system. See instructions below. **We are working on lifting this restriction.** If your MCP client supports Streamable HTTP, prefer those instead of stdio.
# WebSocket (Experimental) > Experimental live event stream for Beeper Desktop API import { Callout } from "@stainless-api/docs/components"; This WebSocket interface is experimental and may change between desktop releases. We would love to hear your feedback on [X](https://x.com/beeper) or [help@beeper.com](mailto:help@beeper.com). Beeper Desktop API exposes a live WebSocket endpoint at: ```text ws://localhost:23373/v1/ws ``` Use the same token as HTTP requests: ```text Authorization: Bearer ``` ## Subscription model Use a single command, `subscriptions.set`, to fully replace current subscriptions. - `chatIDs: ["*"]` subscribes to all chats. - `chatIDs: ["!tdvY9_XjNZ6F5P8DzBxDiqBwla0:ba_aBcD1234EfGhIjKlMnOpQrStUvWxYz.local-ai.localhost", "!tdvY9_XjNZ6F5P8DzBxDiqBwla0:ba_ZxYwVuTsRqPoNmLkJiHgFeDcBa9876.local-whatsapp.localhost"]` subscribes to specific chats. - `chatIDs: []` pauses events. - `"*"` cannot be mixed with specific chat IDs. ### Set all chats ```json { "type": "subscriptions.set", "requestID": "r1", "chatIDs": ["*"] } ``` ### Set specific chats ```json { "type": "subscriptions.set", "requestID": "r2", "chatIDs": ["!tdvY9_XjNZ6F5P8DzBxDiqBwla0:ba_aBcD1234EfGhIjKlMnOpQrStUvWxYz.local-ai.localhost", "!tdvY9_XjNZ6F5P8DzBxDiqBwla0:ba_ZxYwVuTsRqPoNmLkJiHgFeDcBa9876.local-whatsapp.localhost"] } ``` ## Control messages ### `ready` ```json { "type": "ready", "version": 1, "chatIDs": [] } ``` ### `subscriptions.updated` ```json { "type": "subscriptions.updated", "requestID": "r2", "chatIDs": ["!tdvY9_XjNZ6F5P8DzBxDiqBwla0:ba_aBcD1234EfGhIjKlMnOpQrStUvWxYz.local-ai.localhost", "!tdvY9_XjNZ6F5P8DzBxDiqBwla0:ba_ZxYwVuTsRqPoNmLkJiHgFeDcBa9876.local-whatsapp.localhost"] } ``` ### `error` ```json { "type": "error", "requestID": "r2", "code": "INVALID_PAYLOAD", "message": "chatIDs cannot combine '*' with specific IDs" } ``` ## Domain events All live domain events use the same flat shape: ```json { "type": "message.upserted", "seq": 42, "ts": 1739320000000, "chatID": "chat_a", "ids": ["m1", "m2"], "entries": [{ "id": "m1", "reactions": [] }], } ``` Field meanings: - `type`: domain event name. - `seq`: monotonic per-connection sequence number. - `ts`: server timestamp in milliseconds. - `chatID`: canonical chat identifier. - `ids`: changed entity IDs. - `entries`: optional best-effort full payload objects for `message.upserted` events. Mutation types are normalized so both `upsert` and `update` sync mutations are delivered as `.upserted` events. Deletion mutations are delivered as `.deleted`. `message.upserted` events are hydrated from the local message endpoint before emission. This means: - `entries` will usually include a full message payload (including attachments when available). - If the message is not yet retrievable, the event is skipped. - `message.deleted` and `chat.deleted` are emitted as IDs-only payloads (no `entries`). ## Event names Current event names: - `chat.upserted` - `chat.deleted` - `message.upserted` - `message.deleted` # Open Source at Beeper > Beeper is built on open source. Explore our open source projects, from bridges and bots to libraries and tools. import { Card, LinkCard, CardGrid } from "@astrojs/starlight/components"; ## Bridges ### Official bridges These bridges are the same bridges we run with On-Device Connections and also on Beeper Cloud. ยน Not available in the app. ### Community-maintained bridges These are bridges that are maintained by the community and sponsored by Beeper. They are only available for self-hosting. ### Tools ## Example projects {/* */} ## Libraries {/* ## Matrix Bots */} {/* ## Forks */} ## Deprecated Texts.com integrations JavaScript integrations for chat networks built for the legacy Texts.com Desktop app. These are no longer maintained. This page represents a selection of our open source work. To see more you can, visit our GitHub organizations: [beeper](https://github.com/beeper) and [mautrix](https://github.com/mautrix). Beeper is part of [Automattic](https://github.com/automattic).