diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1ca0ca2..9a5cafb 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -18,8 +18,7 @@ jobs:
lint:
timeout-minutes: 10
name: lint
- runs-on: ${{ github.repository == 'stainless-sdks/beeper-desktop-api-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
- if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
+ runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
@@ -38,13 +37,9 @@ jobs:
run: ./scripts/lint
build:
- if: (github.event_name == 'push' || github.event.pull_request.head.repo.fork) && (github.event_name != 'push' || github.event.head_commit.message != 'codegen metadata')
timeout-minutes: 10
name: build
- permissions:
- contents: read
- id-token: write
- runs-on: ${{ github.repository == 'stainless-sdks/beeper-desktop-api-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
+ runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
@@ -62,30 +57,10 @@ jobs:
- name: Run build
run: rye build
- - name: Get GitHub OIDC Token
- if: |-
- github.repository == 'stainless-sdks/beeper-desktop-api-python' &&
- !startsWith(github.ref, 'refs/heads/stl/')
- id: github-oidc
- uses: actions/github-script@v8
- with:
- script: core.setOutput('github_token', await core.getIDToken());
-
- - name: Upload tarball
- if: |-
- github.repository == 'stainless-sdks/beeper-desktop-api-python' &&
- !startsWith(github.ref, 'refs/heads/stl/')
- env:
- URL: https://pkg.stainless.com/s
- AUTH: ${{ steps.github-oidc.outputs.github_token }}
- SHA: ${{ github.sha }}
- run: ./scripts/utils/upload-artifact.sh
-
test:
timeout-minutes: 10
name: test
- runs-on: ${{ github.repository == 'stainless-sdks/beeper-desktop-api-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }}
- if: github.event_name == 'push' || github.event.pull_request.head.repo.fork
+ runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 8e76abb..112e32b 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "5.0.0"
+ ".": "6.0.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index 2dd3fee..b369758 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1 @@
-configured_endpoints: 30
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/beeper/beeper-desktop-api-c08c14bb754b4cb0e02b21fabb680469368286be339dec0aaa8c69d04a1f021a.yml
-openapi_spec_hash: a10246aaf7cdc33b682fc245bd5f893b
-config_hash: 72f9d43b9b51a5da912e9f3730e53ae2
+configured_endpoints: 60
diff --git a/README.md b/README.md
index 645fd65..7877b66 100644
--- a/README.md
+++ b/README.md
@@ -23,13 +23,9 @@ The REST API documentation can be found on [developers.beeper.com](https://devel
## Installation
```sh
-# install from the production repo
-pip install git+ssh://git@github.com/beeper/desktop-api-python.git
+pip install beeper_desktop_api
```
-> [!NOTE]
-> Once this package is [published to PyPI](https://www.stainless.com/docs/guides/publish), this will become: `pip install beeper_desktop_api`
-
## Usage
The full API of this library can be found in [api.md](api.md).
@@ -92,8 +88,7 @@ By default, the async client uses `httpx` for HTTP requests. However, for improv
You can enable this by installing `aiohttp`:
```sh
-# install from the production repo
-pip install 'beeper_desktop_api[aiohttp] @ git+ssh://git@github.com/beeper/desktop-api-python.git'
+pip install 'beeper_desktop_api[aiohttp]'
```
Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`:
diff --git a/api.md b/api.md
index 4818056..ff7b536 100644
--- a/api.md
+++ b/api.md
@@ -1,7 +1,22 @@
# Shared Types
```python
-from beeper_desktop_api.types import Attachment, Error, Message, Reaction, User
+from beeper_desktop_api.types import (
+ APIError,
+ AppStateSnapshot,
+ Attachment,
+ AttachmentCapabilities,
+ ChatCapabilities,
+ ChatDraft,
+ ChatStateCapabilities,
+ DraftAttachment,
+ Error,
+ LinkPreview,
+ Message,
+ Reaction,
+ SendStatus,
+ User,
+)
```
# BeeperDesktop
@@ -22,11 +37,17 @@ Methods:
Types:
```python
-from beeper_desktop_api.types import Account, AccountListResponse
+from beeper_desktop_api.types import (
+ Account,
+ AccountBridge,
+ AccountRetrieveResponse,
+ AccountListResponse,
+)
```
Methods:
+- client.accounts.retrieve(account_id) -> AccountRetrieveResponse
- client.accounts.list() -> AccountListResponse
## Contacts
@@ -42,6 +63,80 @@ Methods:
- client.accounts.contacts.list(account_id, \*\*params) -> SyncCursorSearch[User]
- client.accounts.contacts.search(account_id, \*\*params) -> ContactSearchResponse
+# Bridges
+
+Types:
+
+```python
+from beeper_desktop_api.types import (
+ Bridge,
+ BridgeLogin,
+ CookieField,
+ DisappearingTimerCapability,
+ GroupFieldCapability,
+ GroupTypeCapabilities,
+ LoginFlow,
+ LoginInputField,
+ LoginSession,
+ ProvisioningCapabilities,
+ ResolveIdentifierCapabilities,
+ BridgeRetrieveResponse,
+ BridgeListResponse,
+)
+```
+
+Methods:
+
+- client.bridges.retrieve(bridge_id) -> BridgeRetrieveResponse
+- client.bridges.list() -> BridgeListResponse
+- client.bridges.retrieve_capabilities(bridge_id) -> ProvisioningCapabilities
+
+## LoginFlows
+
+Types:
+
+```python
+from beeper_desktop_api.types.bridges import LoginFlowListResponse
+```
+
+Methods:
+
+- client.bridges.login_flows.list(bridge_id) -> LoginFlowListResponse
+
+## Logins
+
+Types:
+
+```python
+from beeper_desktop_api.types.bridges import LoginListResponse, LoginRemoveResponse
+```
+
+Methods:
+
+- client.bridges.logins.retrieve(login_id, \*, bridge_id) -> BridgeLogin
+- client.bridges.logins.list(bridge_id) -> LoginListResponse
+- client.bridges.logins.remove(login_id, \*, bridge_id, \*\*params) -> LoginRemoveResponse
+
+## LoginSessions
+
+Types:
+
+```python
+from beeper_desktop_api.types.bridges import LoginSessionCancelResponse
+```
+
+Methods:
+
+- client.bridges.login_sessions.create(bridge_id, \*\*params) -> LoginSession
+- client.bridges.login_sessions.retrieve(login_session_id, \*, bridge_id) -> LoginSession
+- client.bridges.login_sessions.cancel(login_session_id, \*, bridge_id) -> LoginSessionCancelResponse
+
+### Steps
+
+Methods:
+
+- client.bridges.login_sessions.steps.submit(step_id, \*, bridge_id, login_session_id, \*\*params) -> LoginSession
+
# Chats
Types:
@@ -85,6 +180,18 @@ Methods:
- client.chats.messages.reactions.delete(reaction_key, \*, chat_id, message_id) -> ReactionDeleteResponse
- client.chats.messages.reactions.add(message_id, \*, chat_id, \*\*params) -> ReactionAddResponse
+# Labels
+
+Types:
+
+```python
+from beeper_desktop_api.types import Label, LabelListResponse
+```
+
+Methods:
+
+- client.labels.list() -> LabelListResponse
+
# Messages
Types:
@@ -132,3 +239,119 @@ from beeper_desktop_api.types import InfoRetrieveResponse
Methods:
- client.info.retrieve() -> InfoRetrieveResponse
+
+# App
+
+Types:
+
+```python
+from beeper_desktop_api.types import (
+ RecoveryKeyResetResponse,
+ SetupCompleteResponse,
+ SetupRegistrationRequiredResponse,
+ SetupResponseOutput,
+ SetupStateResponse,
+ Verification,
+ VerificationResponse,
+)
+```
+
+## Setup
+
+Types:
+
+```python
+from beeper_desktop_api.types.app import (
+ SetupRetrieveResponse,
+ SetupRegisterResponse,
+ SetupResponseResponse,
+ SetupStartResponse,
+)
+```
+
+Methods:
+
+- client.app.setup.retrieve() -> SetupRetrieveResponse
+- client.app.setup.email(\*\*params) -> None
+- client.app.setup.register(\*\*params) -> SetupRegisterResponse
+- client.app.setup.response(\*\*params) -> SetupResponseResponse
+- client.app.setup.start() -> SetupStartResponse
+
+### RecoveryKey
+
+Types:
+
+```python
+from beeper_desktop_api.types.app.setup import RecoveryKeyVerifyResponse
+```
+
+Methods:
+
+- client.app.setup.recovery_key.verify(\*\*params) -> RecoveryKeyVerifyResponse
+
+#### Reset
+
+Types:
+
+```python
+from beeper_desktop_api.types.app.setup.recovery_key import (
+ ResetCreateResponse,
+ ResetConfirmResponse,
+)
+```
+
+Methods:
+
+- client.app.setup.recovery_key.reset.create(\*\*params) -> ResetCreateResponse
+- client.app.setup.recovery_key.reset.confirm(\*\*params) -> ResetConfirmResponse
+
+### Verifications
+
+Types:
+
+```python
+from beeper_desktop_api.types.app.setup import (
+ VerificationCreateResponse,
+ VerificationRetrieveResponse,
+ VerificationListResponse,
+ VerificationAcceptResponse,
+ VerificationCancelResponse,
+)
+```
+
+Methods:
+
+- client.app.setup.verifications.create(\*\*params) -> VerificationCreateResponse
+- client.app.setup.verifications.retrieve(verification_id) -> VerificationRetrieveResponse
+- client.app.setup.verifications.list() -> VerificationListResponse
+- client.app.setup.verifications.accept(verification_id) -> VerificationAcceptResponse
+- client.app.setup.verifications.cancel(verification_id, \*\*params) -> VerificationCancelResponse
+
+#### QR
+
+Types:
+
+```python
+from beeper_desktop_api.types.app.setup.verifications import (
+ QRConfirmScannedResponse,
+ QRScanResponse,
+)
+```
+
+Methods:
+
+- client.app.setup.verifications.qr.confirm_scanned(verification_id) -> QRConfirmScannedResponse
+- client.app.setup.verifications.qr.scan(\*\*params) -> QRScanResponse
+
+#### SAS
+
+Types:
+
+```python
+from beeper_desktop_api.types.app.setup.verifications import SASConfirmResponse, SASStartResponse
+```
+
+Methods:
+
+- client.app.setup.verifications.sas.confirm(verification_id) -> SASConfirmResponse
+- client.app.setup.verifications.sas.start(verification_id) -> SASStartResponse
diff --git a/pyproject.toml b/pyproject.toml
index 83d327e..5457a3e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "beeper_desktop_api"
-version = "5.0.0"
+version = "6.0.0"
description = "The official Python library for the beeperdesktop API"
dynamic = ["readme"]
license = "MIT"
diff --git a/scripts/mock b/scripts/mock
index 9c7c439..d961d45 100755
--- a/scripts/mock
+++ b/scripts/mock
@@ -7,14 +7,15 @@ cd "$(dirname "$0")/.."
if [[ -n "$1" && "$1" != '--'* ]]; then
URL="$1"
shift
+elif [ -n "$STAINLESS_OPENAPI_SPEC_URL" ]; then
+ URL="$STAINLESS_OPENAPI_SPEC_URL"
else
- URL="$(grep 'openapi_spec_url' .stats.yml | cut -d' ' -f2)"
-fi
-
-# Check if the URL is empty
-if [ -z "$URL" ]; then
- echo "Error: No OpenAPI spec path/url provided or found in .stats.yml"
- exit 1
+ # Embedded OpenAPI spec (base64-encoded, gzipped)
+ EMBEDDED_SPEC="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"
+ SPEC_TMPFILE="$(mktemp)"
+ trap 'rm -f "$SPEC_TMPFILE"' EXIT
+ echo "$EMBEDDED_SPEC" | base64 --decode | gunzip > "$SPEC_TMPFILE"
+ URL="$SPEC_TMPFILE"
fi
echo "==> Starting mock server with URL ${URL}"
diff --git a/src/beeper_desktop_api/_client.py b/src/beeper_desktop_api/_client.py
index 6c43fcb..e5505fe 100644
--- a/src/beeper_desktop_api/_client.py
+++ b/src/beeper_desktop_api/_client.py
@@ -52,11 +52,14 @@
from .types.search_response import SearchResponse
if TYPE_CHECKING:
- from .resources import info, chats, assets, accounts, messages
+ from .resources import app, info, chats, assets, labels, bridges, accounts, messages
from .resources.info import InfoResource, AsyncInfoResource
from .resources.assets import AssetsResource, AsyncAssetsResource
+ from .resources.labels import LabelsResource, AsyncLabelsResource
+ from .resources.app.app import AppResource, AsyncAppResource
from .resources.messages import MessagesResource, AsyncMessagesResource
from .resources.chats.chats import ChatsResource, AsyncChatsResource
+ from .resources.bridges.bridges import BridgesResource, AsyncBridgesResource
from .resources.accounts.accounts import AccountsResource, AsyncAccountsResource
__all__ = [
@@ -142,6 +145,13 @@ def accounts(self) -> AccountsResource:
return AccountsResource(self)
+ @cached_property
+ def bridges(self) -> BridgesResource:
+ """Manage available bridges, connect or reconnect chat accounts"""
+ from .resources.bridges import BridgesResource
+
+ return BridgesResource(self)
+
@cached_property
def chats(self) -> ChatsResource:
"""Manage chats"""
@@ -149,6 +159,13 @@ def chats(self) -> ChatsResource:
return ChatsResource(self)
+ @cached_property
+ def labels(self) -> LabelsResource:
+ """User-created labels that organize chats"""
+ from .resources.labels import LabelsResource
+
+ return LabelsResource(self)
+
@cached_property
def messages(self) -> MessagesResource:
"""Manage messages in chats"""
@@ -158,7 +175,7 @@ def messages(self) -> MessagesResource:
@cached_property
def assets(self) -> AssetsResource:
- """Manage assets in Beeper Desktop, like message attachments"""
+ """Manage files for message attachments"""
from .resources.assets import AssetsResource
return AssetsResource(self)
@@ -173,6 +190,13 @@ def info(self) -> InfoResource:
return InfoResource(self)
+ @cached_property
+ def app(self) -> AppResource:
+ """Manage Beeper account setup and encrypted messaging setup"""
+ from .resources.app import AppResource
+
+ return AppResource(self)
+
@cached_property
def with_raw_response(self) -> BeeperDesktopWithRawResponse:
return BeeperDesktopWithRawResponse(self)
@@ -272,14 +296,14 @@ def focus(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> FocusResponse:
"""
- Focus Beeper Desktop and optionally navigate to a specific chat, message, or
- pre-fill plain text and an image path.
+ Focus Beeper Desktop and optionally open a specific chat, jump to a message, or
+ pre-fill text and an image.
Args:
chat_id: Optional Beeper chat ID (or local chat ID) to focus after opening the app. If
omitted, only opens/focuses the app.
- draft_attachment_path: Optional image path to populate in the message input field.
+ draft_attachment_path: Optional local image path to populate in the message input field.
draft_text: Optional plain text to populate in the message input field.
@@ -322,12 +346,12 @@ def search(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> SearchResponse:
"""
- Returns matching chats, participant name matches in groups, and the first page
- of messages in one call. Paginate messages via search-messages. Paginate chats
- via search-chats.
+ Return matching chats, participant matches in group chats, and the first page of
+ message results in one call. Use the dedicated chat and message search endpoints
+ for pagination.
Args:
- query: User-typed search text. Literal word matching (non-semantic).
+ query: User-typed search text. Uses literal word matching.
extra_headers: Send extra headers
@@ -454,6 +478,13 @@ def accounts(self) -> AsyncAccountsResource:
return AsyncAccountsResource(self)
+ @cached_property
+ def bridges(self) -> AsyncBridgesResource:
+ """Manage available bridges, connect or reconnect chat accounts"""
+ from .resources.bridges import AsyncBridgesResource
+
+ return AsyncBridgesResource(self)
+
@cached_property
def chats(self) -> AsyncChatsResource:
"""Manage chats"""
@@ -461,6 +492,13 @@ def chats(self) -> AsyncChatsResource:
return AsyncChatsResource(self)
+ @cached_property
+ def labels(self) -> AsyncLabelsResource:
+ """User-created labels that organize chats"""
+ from .resources.labels import AsyncLabelsResource
+
+ return AsyncLabelsResource(self)
+
@cached_property
def messages(self) -> AsyncMessagesResource:
"""Manage messages in chats"""
@@ -470,7 +508,7 @@ def messages(self) -> AsyncMessagesResource:
@cached_property
def assets(self) -> AsyncAssetsResource:
- """Manage assets in Beeper Desktop, like message attachments"""
+ """Manage files for message attachments"""
from .resources.assets import AsyncAssetsResource
return AsyncAssetsResource(self)
@@ -485,6 +523,13 @@ def info(self) -> AsyncInfoResource:
return AsyncInfoResource(self)
+ @cached_property
+ def app(self) -> AsyncAppResource:
+ """Manage Beeper account setup and encrypted messaging setup"""
+ from .resources.app import AsyncAppResource
+
+ return AsyncAppResource(self)
+
@cached_property
def with_raw_response(self) -> AsyncBeeperDesktopWithRawResponse:
return AsyncBeeperDesktopWithRawResponse(self)
@@ -584,14 +629,14 @@ async def focus(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> FocusResponse:
"""
- Focus Beeper Desktop and optionally navigate to a specific chat, message, or
- pre-fill plain text and an image path.
+ Focus Beeper Desktop and optionally open a specific chat, jump to a message, or
+ pre-fill text and an image.
Args:
chat_id: Optional Beeper chat ID (or local chat ID) to focus after opening the app. If
omitted, only opens/focuses the app.
- draft_attachment_path: Optional image path to populate in the message input field.
+ draft_attachment_path: Optional local image path to populate in the message input field.
draft_text: Optional plain text to populate in the message input field.
@@ -634,12 +679,12 @@ async def search(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> SearchResponse:
"""
- Returns matching chats, participant name matches in groups, and the first page
- of messages in one call. Paginate messages via search-messages. Paginate chats
- via search-chats.
+ Return matching chats, participant matches in group chats, and the first page of
+ message results in one call. Use the dedicated chat and message search endpoints
+ for pagination.
Args:
- query: User-typed search text. Literal word matching (non-semantic).
+ query: User-typed search text. Uses literal word matching.
extra_headers: Send extra headers
@@ -715,6 +760,13 @@ def accounts(self) -> accounts.AccountsResourceWithRawResponse:
return AccountsResourceWithRawResponse(self._client.accounts)
+ @cached_property
+ def bridges(self) -> bridges.BridgesResourceWithRawResponse:
+ """Manage available bridges, connect or reconnect chat accounts"""
+ from .resources.bridges import BridgesResourceWithRawResponse
+
+ return BridgesResourceWithRawResponse(self._client.bridges)
+
@cached_property
def chats(self) -> chats.ChatsResourceWithRawResponse:
"""Manage chats"""
@@ -722,6 +774,13 @@ def chats(self) -> chats.ChatsResourceWithRawResponse:
return ChatsResourceWithRawResponse(self._client.chats)
+ @cached_property
+ def labels(self) -> labels.LabelsResourceWithRawResponse:
+ """User-created labels that organize chats"""
+ from .resources.labels import LabelsResourceWithRawResponse
+
+ return LabelsResourceWithRawResponse(self._client.labels)
+
@cached_property
def messages(self) -> messages.MessagesResourceWithRawResponse:
"""Manage messages in chats"""
@@ -731,7 +790,7 @@ def messages(self) -> messages.MessagesResourceWithRawResponse:
@cached_property
def assets(self) -> assets.AssetsResourceWithRawResponse:
- """Manage assets in Beeper Desktop, like message attachments"""
+ """Manage files for message attachments"""
from .resources.assets import AssetsResourceWithRawResponse
return AssetsResourceWithRawResponse(self._client.assets)
@@ -746,6 +805,13 @@ def info(self) -> info.InfoResourceWithRawResponse:
return InfoResourceWithRawResponse(self._client.info)
+ @cached_property
+ def app(self) -> app.AppResourceWithRawResponse:
+ """Manage Beeper account setup and encrypted messaging setup"""
+ from .resources.app import AppResourceWithRawResponse
+
+ return AppResourceWithRawResponse(self._client.app)
+
class AsyncBeeperDesktopWithRawResponse:
_client: AsyncBeeperDesktop
@@ -767,6 +833,13 @@ def accounts(self) -> accounts.AsyncAccountsResourceWithRawResponse:
return AsyncAccountsResourceWithRawResponse(self._client.accounts)
+ @cached_property
+ def bridges(self) -> bridges.AsyncBridgesResourceWithRawResponse:
+ """Manage available bridges, connect or reconnect chat accounts"""
+ from .resources.bridges import AsyncBridgesResourceWithRawResponse
+
+ return AsyncBridgesResourceWithRawResponse(self._client.bridges)
+
@cached_property
def chats(self) -> chats.AsyncChatsResourceWithRawResponse:
"""Manage chats"""
@@ -774,6 +847,13 @@ def chats(self) -> chats.AsyncChatsResourceWithRawResponse:
return AsyncChatsResourceWithRawResponse(self._client.chats)
+ @cached_property
+ def labels(self) -> labels.AsyncLabelsResourceWithRawResponse:
+ """User-created labels that organize chats"""
+ from .resources.labels import AsyncLabelsResourceWithRawResponse
+
+ return AsyncLabelsResourceWithRawResponse(self._client.labels)
+
@cached_property
def messages(self) -> messages.AsyncMessagesResourceWithRawResponse:
"""Manage messages in chats"""
@@ -783,7 +863,7 @@ def messages(self) -> messages.AsyncMessagesResourceWithRawResponse:
@cached_property
def assets(self) -> assets.AsyncAssetsResourceWithRawResponse:
- """Manage assets in Beeper Desktop, like message attachments"""
+ """Manage files for message attachments"""
from .resources.assets import AsyncAssetsResourceWithRawResponse
return AsyncAssetsResourceWithRawResponse(self._client.assets)
@@ -798,6 +878,13 @@ def info(self) -> info.AsyncInfoResourceWithRawResponse:
return AsyncInfoResourceWithRawResponse(self._client.info)
+ @cached_property
+ def app(self) -> app.AsyncAppResourceWithRawResponse:
+ """Manage Beeper account setup and encrypted messaging setup"""
+ from .resources.app import AsyncAppResourceWithRawResponse
+
+ return AsyncAppResourceWithRawResponse(self._client.app)
+
class BeeperDesktopWithStreamedResponse:
_client: BeeperDesktop
@@ -819,6 +906,13 @@ def accounts(self) -> accounts.AccountsResourceWithStreamingResponse:
return AccountsResourceWithStreamingResponse(self._client.accounts)
+ @cached_property
+ def bridges(self) -> bridges.BridgesResourceWithStreamingResponse:
+ """Manage available bridges, connect or reconnect chat accounts"""
+ from .resources.bridges import BridgesResourceWithStreamingResponse
+
+ return BridgesResourceWithStreamingResponse(self._client.bridges)
+
@cached_property
def chats(self) -> chats.ChatsResourceWithStreamingResponse:
"""Manage chats"""
@@ -826,6 +920,13 @@ def chats(self) -> chats.ChatsResourceWithStreamingResponse:
return ChatsResourceWithStreamingResponse(self._client.chats)
+ @cached_property
+ def labels(self) -> labels.LabelsResourceWithStreamingResponse:
+ """User-created labels that organize chats"""
+ from .resources.labels import LabelsResourceWithStreamingResponse
+
+ return LabelsResourceWithStreamingResponse(self._client.labels)
+
@cached_property
def messages(self) -> messages.MessagesResourceWithStreamingResponse:
"""Manage messages in chats"""
@@ -835,7 +936,7 @@ def messages(self) -> messages.MessagesResourceWithStreamingResponse:
@cached_property
def assets(self) -> assets.AssetsResourceWithStreamingResponse:
- """Manage assets in Beeper Desktop, like message attachments"""
+ """Manage files for message attachments"""
from .resources.assets import AssetsResourceWithStreamingResponse
return AssetsResourceWithStreamingResponse(self._client.assets)
@@ -850,6 +951,13 @@ def info(self) -> info.InfoResourceWithStreamingResponse:
return InfoResourceWithStreamingResponse(self._client.info)
+ @cached_property
+ def app(self) -> app.AppResourceWithStreamingResponse:
+ """Manage Beeper account setup and encrypted messaging setup"""
+ from .resources.app import AppResourceWithStreamingResponse
+
+ return AppResourceWithStreamingResponse(self._client.app)
+
class AsyncBeeperDesktopWithStreamedResponse:
_client: AsyncBeeperDesktop
@@ -871,6 +979,13 @@ def accounts(self) -> accounts.AsyncAccountsResourceWithStreamingResponse:
return AsyncAccountsResourceWithStreamingResponse(self._client.accounts)
+ @cached_property
+ def bridges(self) -> bridges.AsyncBridgesResourceWithStreamingResponse:
+ """Manage available bridges, connect or reconnect chat accounts"""
+ from .resources.bridges import AsyncBridgesResourceWithStreamingResponse
+
+ return AsyncBridgesResourceWithStreamingResponse(self._client.bridges)
+
@cached_property
def chats(self) -> chats.AsyncChatsResourceWithStreamingResponse:
"""Manage chats"""
@@ -878,6 +993,13 @@ def chats(self) -> chats.AsyncChatsResourceWithStreamingResponse:
return AsyncChatsResourceWithStreamingResponse(self._client.chats)
+ @cached_property
+ def labels(self) -> labels.AsyncLabelsResourceWithStreamingResponse:
+ """User-created labels that organize chats"""
+ from .resources.labels import AsyncLabelsResourceWithStreamingResponse
+
+ return AsyncLabelsResourceWithStreamingResponse(self._client.labels)
+
@cached_property
def messages(self) -> messages.AsyncMessagesResourceWithStreamingResponse:
"""Manage messages in chats"""
@@ -887,7 +1009,7 @@ def messages(self) -> messages.AsyncMessagesResourceWithStreamingResponse:
@cached_property
def assets(self) -> assets.AsyncAssetsResourceWithStreamingResponse:
- """Manage assets in Beeper Desktop, like message attachments"""
+ """Manage files for message attachments"""
from .resources.assets import AsyncAssetsResourceWithStreamingResponse
return AsyncAssetsResourceWithStreamingResponse(self._client.assets)
@@ -902,6 +1024,13 @@ def info(self) -> info.AsyncInfoResourceWithStreamingResponse:
return AsyncInfoResourceWithStreamingResponse(self._client.info)
+ @cached_property
+ def app(self) -> app.AsyncAppResourceWithStreamingResponse:
+ """Manage Beeper account setup and encrypted messaging setup"""
+ from .resources.app import AsyncAppResourceWithStreamingResponse
+
+ return AsyncAppResourceWithStreamingResponse(self._client.app)
+
Client = BeeperDesktop
diff --git a/src/beeper_desktop_api/_files.py b/src/beeper_desktop_api/_files.py
index 8a371d3..be8e0e1 100644
--- a/src/beeper_desktop_api/_files.py
+++ b/src/beeper_desktop_api/_files.py
@@ -99,7 +99,7 @@ async def async_to_httpx_files(files: RequestFiles | None) -> HttpxRequestFiles
elif is_sequence_t(files):
files = [(key, await _async_transform_file(file)) for key, file in files]
else:
- raise TypeError("Unexpected file type input {type(files)}, expected mapping or sequence")
+ raise TypeError(f"Unexpected file type input {type(files)}, expected mapping or sequence")
return files
diff --git a/src/beeper_desktop_api/_models.py b/src/beeper_desktop_api/_models.py
index e22dd2a..69f41a6 100644
--- a/src/beeper_desktop_api/_models.py
+++ b/src/beeper_desktop_api/_models.py
@@ -25,7 +25,9 @@
ClassVar,
Protocol,
Required,
+ Annotated,
ParamSpec,
+ TypeAlias,
TypedDict,
TypeGuard,
final,
@@ -79,7 +81,15 @@
from ._constants import RAW_RESPONSE_HEADER
if TYPE_CHECKING:
+ from pydantic import GetCoreSchemaHandler, ValidatorFunctionWrapHandler
+ from pydantic_core import CoreSchema, core_schema
from pydantic_core.core_schema import ModelField, ModelSchema, LiteralSchema, ModelFieldsSchema
+else:
+ try:
+ from pydantic_core import CoreSchema, core_schema
+ except ImportError:
+ CoreSchema = None
+ core_schema = None
__all__ = ["BaseModel", "GenericModel"]
@@ -396,6 +406,76 @@ def model_dump_json(
)
+class _EagerIterable(list[_T], Generic[_T]):
+ """
+ Accepts any Iterable[T] input (including generators), consumes it
+ eagerly, and validates all items upfront.
+
+ Validation preserves the original container type where possible
+ (e.g. a set[T] stays a set[T]). Serialization (model_dump / JSON)
+ always emits a list — round-tripping through model_dump() will not
+ restore the original container type.
+ """
+
+ @classmethod
+ def __get_pydantic_core_schema__(
+ cls,
+ source_type: Any,
+ handler: GetCoreSchemaHandler,
+ ) -> CoreSchema:
+ (item_type,) = get_args(source_type) or (Any,)
+ item_schema: CoreSchema = handler.generate_schema(item_type)
+ list_of_items_schema: CoreSchema = core_schema.list_schema(item_schema)
+
+ return core_schema.no_info_wrap_validator_function(
+ cls._validate,
+ list_of_items_schema,
+ serialization=core_schema.plain_serializer_function_ser_schema(
+ cls._serialize,
+ info_arg=False,
+ ),
+ )
+
+ @staticmethod
+ def _validate(v: Iterable[_T], handler: "ValidatorFunctionWrapHandler") -> Any:
+ original_type: type[Any] = type(v)
+
+ # Normalize to list so list_schema can validate each item
+ if isinstance(v, list):
+ items: list[_T] = v
+ else:
+ try:
+ items = list(v)
+ except TypeError as e:
+ raise TypeError("Value is not iterable") from e
+
+ # Validate items against the inner schema
+ validated: list[_T] = handler(items)
+
+ # Reconstruct original container type
+ if original_type is list:
+ return validated
+ # str(list) produces the list's repr, not a string built from items,
+ # so skip reconstruction for str and its subclasses.
+ if issubclass(original_type, str):
+ return validated
+ try:
+ return original_type(validated)
+ except (TypeError, ValueError):
+ # If the type cannot be reconstructed, just return the validated list
+ return validated
+
+ @staticmethod
+ def _serialize(v: Iterable[_T]) -> list[_T]:
+ """Always serialize as a list so Pydantic's JSON encoder is happy."""
+ if isinstance(v, list):
+ return v
+ return list(v)
+
+
+EagerIterable: TypeAlias = Annotated[Iterable[_T], _EagerIterable]
+
+
def _construct_field(value: object, field: FieldInfo, key: str) -> object:
if value is None:
return field_get_default(field)
diff --git a/src/beeper_desktop_api/_version.py b/src/beeper_desktop_api/_version.py
index 60fb169..8b011ca 100644
--- a/src/beeper_desktop_api/_version.py
+++ b/src/beeper_desktop_api/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "beeper_desktop_api"
-__version__ = "5.0.0" # x-release-please-version
+__version__ = "6.0.0" # x-release-please-version
diff --git a/src/beeper_desktop_api/resources/__init__.py b/src/beeper_desktop_api/resources/__init__.py
index a066e9b..d0dbcf3 100644
--- a/src/beeper_desktop_api/resources/__init__.py
+++ b/src/beeper_desktop_api/resources/__init__.py
@@ -1,5 +1,13 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+from .app import (
+ AppResource,
+ AsyncAppResource,
+ AppResourceWithRawResponse,
+ AsyncAppResourceWithRawResponse,
+ AppResourceWithStreamingResponse,
+ AsyncAppResourceWithStreamingResponse,
+)
from .info import (
InfoResource,
AsyncInfoResource,
@@ -24,6 +32,22 @@
AssetsResourceWithStreamingResponse,
AsyncAssetsResourceWithStreamingResponse,
)
+from .labels import (
+ LabelsResource,
+ AsyncLabelsResource,
+ LabelsResourceWithRawResponse,
+ AsyncLabelsResourceWithRawResponse,
+ LabelsResourceWithStreamingResponse,
+ AsyncLabelsResourceWithStreamingResponse,
+)
+from .bridges import (
+ BridgesResource,
+ AsyncBridgesResource,
+ BridgesResourceWithRawResponse,
+ AsyncBridgesResourceWithRawResponse,
+ BridgesResourceWithStreamingResponse,
+ AsyncBridgesResourceWithStreamingResponse,
+)
from .accounts import (
AccountsResource,
AsyncAccountsResource,
@@ -48,12 +72,24 @@
"AsyncAccountsResourceWithRawResponse",
"AccountsResourceWithStreamingResponse",
"AsyncAccountsResourceWithStreamingResponse",
+ "BridgesResource",
+ "AsyncBridgesResource",
+ "BridgesResourceWithRawResponse",
+ "AsyncBridgesResourceWithRawResponse",
+ "BridgesResourceWithStreamingResponse",
+ "AsyncBridgesResourceWithStreamingResponse",
"ChatsResource",
"AsyncChatsResource",
"ChatsResourceWithRawResponse",
"AsyncChatsResourceWithRawResponse",
"ChatsResourceWithStreamingResponse",
"AsyncChatsResourceWithStreamingResponse",
+ "LabelsResource",
+ "AsyncLabelsResource",
+ "LabelsResourceWithRawResponse",
+ "AsyncLabelsResourceWithRawResponse",
+ "LabelsResourceWithStreamingResponse",
+ "AsyncLabelsResourceWithStreamingResponse",
"MessagesResource",
"AsyncMessagesResource",
"MessagesResourceWithRawResponse",
@@ -72,4 +108,10 @@
"AsyncInfoResourceWithRawResponse",
"InfoResourceWithStreamingResponse",
"AsyncInfoResourceWithStreamingResponse",
+ "AppResource",
+ "AsyncAppResource",
+ "AppResourceWithRawResponse",
+ "AsyncAppResourceWithRawResponse",
+ "AppResourceWithStreamingResponse",
+ "AsyncAppResourceWithStreamingResponse",
]
diff --git a/src/beeper_desktop_api/resources/accounts/accounts.py b/src/beeper_desktop_api/resources/accounts/accounts.py
index 15cfd33..8c247e4 100644
--- a/src/beeper_desktop_api/resources/accounts/accounts.py
+++ b/src/beeper_desktop_api/resources/accounts/accounts.py
@@ -5,6 +5,7 @@
import httpx
from ..._types import Body, Query, Headers, NotGiven, not_given
+from ..._utils import path_template
from .contacts import (
ContactsResource,
AsyncContactsResource,
@@ -23,6 +24,7 @@
)
from ..._base_client import make_request_options
from ...types.account_list_response import AccountListResponse
+from ...types.account_retrieve_response import AccountRetrieveResponse
__all__ = ["AccountsResource", "AsyncAccountsResource"]
@@ -54,6 +56,41 @@ def with_streaming_response(self) -> AccountsResourceWithStreamingResponse:
"""
return AccountsResourceWithStreamingResponse(self)
+ def retrieve(
+ self,
+ account_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AccountRetrieveResponse:
+ """
+ Get one chat account connected to this Beeper Client API server.
+
+ Args:
+ account_id: Account ID this resource belongs to.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not account_id:
+ raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}")
+ return self._get(
+ path_template("/v1/accounts/{account_id}", account_id=account_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=AccountRetrieveResponse,
+ )
+
def list(
self,
*,
@@ -65,8 +102,8 @@ def list(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AccountListResponse:
"""
- List Chat Accounts connected to this Beeper Desktop instance, including bridge
- metadata and network identity.
+ List chat accounts connected to this Beeper Client API server, including bridge,
+ network, user identity, and connection status.
"""
return self._get(
"/v1/accounts",
@@ -104,6 +141,41 @@ def with_streaming_response(self) -> AsyncAccountsResourceWithStreamingResponse:
"""
return AsyncAccountsResourceWithStreamingResponse(self)
+ async def retrieve(
+ self,
+ account_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AccountRetrieveResponse:
+ """
+ Get one chat account connected to this Beeper Client API server.
+
+ Args:
+ account_id: Account ID this resource belongs to.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not account_id:
+ raise ValueError(f"Expected a non-empty value for `account_id` but received {account_id!r}")
+ return await self._get(
+ path_template("/v1/accounts/{account_id}", account_id=account_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=AccountRetrieveResponse,
+ )
+
async def list(
self,
*,
@@ -115,8 +187,8 @@ async def list(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AccountListResponse:
"""
- List Chat Accounts connected to this Beeper Desktop instance, including bridge
- metadata and network identity.
+ List chat accounts connected to this Beeper Client API server, including bridge,
+ network, user identity, and connection status.
"""
return await self._get(
"/v1/accounts",
@@ -131,6 +203,9 @@ class AccountsResourceWithRawResponse:
def __init__(self, accounts: AccountsResource) -> None:
self._accounts = accounts
+ self.retrieve = to_raw_response_wrapper(
+ accounts.retrieve,
+ )
self.list = to_raw_response_wrapper(
accounts.list,
)
@@ -145,6 +220,9 @@ class AsyncAccountsResourceWithRawResponse:
def __init__(self, accounts: AsyncAccountsResource) -> None:
self._accounts = accounts
+ self.retrieve = async_to_raw_response_wrapper(
+ accounts.retrieve,
+ )
self.list = async_to_raw_response_wrapper(
accounts.list,
)
@@ -159,6 +237,9 @@ class AccountsResourceWithStreamingResponse:
def __init__(self, accounts: AccountsResource) -> None:
self._accounts = accounts
+ self.retrieve = to_streamed_response_wrapper(
+ accounts.retrieve,
+ )
self.list = to_streamed_response_wrapper(
accounts.list,
)
@@ -173,6 +254,9 @@ class AsyncAccountsResourceWithStreamingResponse:
def __init__(self, accounts: AsyncAccountsResource) -> None:
self._accounts = accounts
+ self.retrieve = async_to_streamed_response_wrapper(
+ accounts.retrieve,
+ )
self.list = async_to_streamed_response_wrapper(
accounts.list,
)
diff --git a/src/beeper_desktop_api/resources/accounts/contacts.py b/src/beeper_desktop_api/resources/accounts/contacts.py
index ba704bb..347894f 100644
--- a/src/beeper_desktop_api/resources/accounts/contacts.py
+++ b/src/beeper_desktop_api/resources/accounts/contacts.py
@@ -75,7 +75,7 @@ def list(
limit: Maximum contacts to return per page.
- query: Optional search query for blended contact lookup.
+ query: Optional search query for contact lookup.
extra_headers: Send extra headers
@@ -122,12 +122,17 @@ def search(
) -> ContactSearchResponse:
"""
Search contacts on a specific account using merged account contacts, network
- search, and exact identifier lookup.
+ search, and exact identifier lookup. The exact lookup only runs when the query
+ is a phone number, email address, or username; pass one of those to resolve a
+ specific person.
Args:
account_id: Account ID this resource belongs to.
- query: Text to search users by. Network-specific behavior.
+ query: Text to search contacts by. A phone number, email address, or username written
+ with a leading @ is additionally looked up as an exact identifier on the
+ network; any other text, such as a bare handle or a person or business name,
+ searches existing contacts only. Matching behavior depends on the network.
extra_headers: Send extra headers
@@ -202,7 +207,7 @@ def list(
limit: Maximum contacts to return per page.
- query: Optional search query for blended contact lookup.
+ query: Optional search query for contact lookup.
extra_headers: Send extra headers
@@ -249,12 +254,17 @@ async def search(
) -> ContactSearchResponse:
"""
Search contacts on a specific account using merged account contacts, network
- search, and exact identifier lookup.
+ search, and exact identifier lookup. The exact lookup only runs when the query
+ is a phone number, email address, or username; pass one of those to resolve a
+ specific person.
Args:
account_id: Account ID this resource belongs to.
- query: Text to search users by. Network-specific behavior.
+ query: Text to search contacts by. A phone number, email address, or username written
+ with a leading @ is additionally looked up as an exact identifier on the
+ network; any other text, such as a bare handle or a person or business name,
+ searches existing contacts only. Matching behavior depends on the network.
extra_headers: Send extra headers
diff --git a/src/beeper_desktop_api/resources/app/__init__.py b/src/beeper_desktop_api/resources/app/__init__.py
new file mode 100644
index 0000000..896f2f1
--- /dev/null
+++ b/src/beeper_desktop_api/resources/app/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .app import (
+ AppResource,
+ AsyncAppResource,
+ AppResourceWithRawResponse,
+ AsyncAppResourceWithRawResponse,
+ AppResourceWithStreamingResponse,
+ AsyncAppResourceWithStreamingResponse,
+)
+from .setup import (
+ SetupResource,
+ AsyncSetupResource,
+ SetupResourceWithRawResponse,
+ AsyncSetupResourceWithRawResponse,
+ SetupResourceWithStreamingResponse,
+ AsyncSetupResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "SetupResource",
+ "AsyncSetupResource",
+ "SetupResourceWithRawResponse",
+ "AsyncSetupResourceWithRawResponse",
+ "SetupResourceWithStreamingResponse",
+ "AsyncSetupResourceWithStreamingResponse",
+ "AppResource",
+ "AsyncAppResource",
+ "AppResourceWithRawResponse",
+ "AsyncAppResourceWithRawResponse",
+ "AppResourceWithStreamingResponse",
+ "AsyncAppResourceWithStreamingResponse",
+]
diff --git a/src/beeper_desktop_api/resources/app/app.py b/src/beeper_desktop_api/resources/app/app.py
new file mode 100644
index 0000000..85879f9
--- /dev/null
+++ b/src/beeper_desktop_api/resources/app/app.py
@@ -0,0 +1,112 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from .setup.setup import (
+ SetupResource,
+ AsyncSetupResource,
+ SetupResourceWithRawResponse,
+ AsyncSetupResourceWithRawResponse,
+ SetupResourceWithStreamingResponse,
+ AsyncSetupResourceWithStreamingResponse,
+)
+
+__all__ = ["AppResource", "AsyncAppResource"]
+
+
+class AppResource(SyncAPIResource):
+ """Manage Beeper account setup and encrypted messaging setup"""
+
+ @cached_property
+ def setup(self) -> SetupResource:
+ """Complete first-party Beeper app setup"""
+ return SetupResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AppResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#accessing-raw-response-data-eg-headers
+ """
+ return AppResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AppResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#with_streaming_response
+ """
+ return AppResourceWithStreamingResponse(self)
+
+
+class AsyncAppResource(AsyncAPIResource):
+ """Manage Beeper account setup and encrypted messaging setup"""
+
+ @cached_property
+ def setup(self) -> AsyncSetupResource:
+ """Complete first-party Beeper app setup"""
+ return AsyncSetupResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncAppResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncAppResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncAppResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#with_streaming_response
+ """
+ return AsyncAppResourceWithStreamingResponse(self)
+
+
+class AppResourceWithRawResponse:
+ def __init__(self, app: AppResource) -> None:
+ self._app = app
+
+ @cached_property
+ def setup(self) -> SetupResourceWithRawResponse:
+ """Complete first-party Beeper app setup"""
+ return SetupResourceWithRawResponse(self._app.setup)
+
+
+class AsyncAppResourceWithRawResponse:
+ def __init__(self, app: AsyncAppResource) -> None:
+ self._app = app
+
+ @cached_property
+ def setup(self) -> AsyncSetupResourceWithRawResponse:
+ """Complete first-party Beeper app setup"""
+ return AsyncSetupResourceWithRawResponse(self._app.setup)
+
+
+class AppResourceWithStreamingResponse:
+ def __init__(self, app: AppResource) -> None:
+ self._app = app
+
+ @cached_property
+ def setup(self) -> SetupResourceWithStreamingResponse:
+ """Complete first-party Beeper app setup"""
+ return SetupResourceWithStreamingResponse(self._app.setup)
+
+
+class AsyncAppResourceWithStreamingResponse:
+ def __init__(self, app: AsyncAppResource) -> None:
+ self._app = app
+
+ @cached_property
+ def setup(self) -> AsyncSetupResourceWithStreamingResponse:
+ """Complete first-party Beeper app setup"""
+ return AsyncSetupResourceWithStreamingResponse(self._app.setup)
diff --git a/src/beeper_desktop_api/resources/app/setup/__init__.py b/src/beeper_desktop_api/resources/app/setup/__init__.py
new file mode 100644
index 0000000..ef72aaf
--- /dev/null
+++ b/src/beeper_desktop_api/resources/app/setup/__init__.py
@@ -0,0 +1,47 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .setup import (
+ SetupResource,
+ AsyncSetupResource,
+ SetupResourceWithRawResponse,
+ AsyncSetupResourceWithRawResponse,
+ SetupResourceWithStreamingResponse,
+ AsyncSetupResourceWithStreamingResponse,
+)
+from .recovery_key import (
+ RecoveryKeyResource,
+ AsyncRecoveryKeyResource,
+ RecoveryKeyResourceWithRawResponse,
+ AsyncRecoveryKeyResourceWithRawResponse,
+ RecoveryKeyResourceWithStreamingResponse,
+ AsyncRecoveryKeyResourceWithStreamingResponse,
+)
+from .verifications import (
+ VerificationsResource,
+ AsyncVerificationsResource,
+ VerificationsResourceWithRawResponse,
+ AsyncVerificationsResourceWithRawResponse,
+ VerificationsResourceWithStreamingResponse,
+ AsyncVerificationsResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "RecoveryKeyResource",
+ "AsyncRecoveryKeyResource",
+ "RecoveryKeyResourceWithRawResponse",
+ "AsyncRecoveryKeyResourceWithRawResponse",
+ "RecoveryKeyResourceWithStreamingResponse",
+ "AsyncRecoveryKeyResourceWithStreamingResponse",
+ "VerificationsResource",
+ "AsyncVerificationsResource",
+ "VerificationsResourceWithRawResponse",
+ "AsyncVerificationsResourceWithRawResponse",
+ "VerificationsResourceWithStreamingResponse",
+ "AsyncVerificationsResourceWithStreamingResponse",
+ "SetupResource",
+ "AsyncSetupResource",
+ "SetupResourceWithRawResponse",
+ "AsyncSetupResourceWithRawResponse",
+ "SetupResourceWithStreamingResponse",
+ "AsyncSetupResourceWithStreamingResponse",
+]
diff --git a/src/beeper_desktop_api/resources/app/setup/recovery_key/__init__.py b/src/beeper_desktop_api/resources/app/setup/recovery_key/__init__.py
new file mode 100644
index 0000000..ec6a210
--- /dev/null
+++ b/src/beeper_desktop_api/resources/app/setup/recovery_key/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .reset import (
+ ResetResource,
+ AsyncResetResource,
+ ResetResourceWithRawResponse,
+ AsyncResetResourceWithRawResponse,
+ ResetResourceWithStreamingResponse,
+ AsyncResetResourceWithStreamingResponse,
+)
+from .recovery_key import (
+ RecoveryKeyResource,
+ AsyncRecoveryKeyResource,
+ RecoveryKeyResourceWithRawResponse,
+ AsyncRecoveryKeyResourceWithRawResponse,
+ RecoveryKeyResourceWithStreamingResponse,
+ AsyncRecoveryKeyResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "ResetResource",
+ "AsyncResetResource",
+ "ResetResourceWithRawResponse",
+ "AsyncResetResourceWithRawResponse",
+ "ResetResourceWithStreamingResponse",
+ "AsyncResetResourceWithStreamingResponse",
+ "RecoveryKeyResource",
+ "AsyncRecoveryKeyResource",
+ "RecoveryKeyResourceWithRawResponse",
+ "AsyncRecoveryKeyResourceWithRawResponse",
+ "RecoveryKeyResourceWithStreamingResponse",
+ "AsyncRecoveryKeyResourceWithStreamingResponse",
+]
diff --git a/src/beeper_desktop_api/resources/app/setup/recovery_key/recovery_key.py b/src/beeper_desktop_api/resources/app/setup/recovery_key/recovery_key.py
new file mode 100644
index 0000000..ab566d9
--- /dev/null
+++ b/src/beeper_desktop_api/resources/app/setup/recovery_key/recovery_key.py
@@ -0,0 +1,223 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from .reset import (
+ ResetResource,
+ AsyncResetResource,
+ ResetResourceWithRawResponse,
+ AsyncResetResourceWithRawResponse,
+ ResetResourceWithStreamingResponse,
+ AsyncResetResourceWithStreamingResponse,
+)
+from ....._types import Body, Query, Headers, NotGiven, not_given
+from ....._utils import maybe_transform, async_maybe_transform
+from ....._compat import cached_property
+from ....._resource import SyncAPIResource, AsyncAPIResource
+from ....._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ....._base_client import make_request_options
+from .....types.app.setup import recovery_key_verify_params
+from .....types.app.setup.recovery_key_verify_response import RecoveryKeyVerifyResponse
+
+__all__ = ["RecoveryKeyResource", "AsyncRecoveryKeyResource"]
+
+
+class RecoveryKeyResource(SyncAPIResource):
+ """Manage recovery-key setup for encrypted messages"""
+
+ @cached_property
+ def reset(self) -> ResetResource:
+ """
+ First-party sign-in and encrypted messaging setup for Beeper Desktop and Beeper Server.
+ """
+ return ResetResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> RecoveryKeyResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#accessing-raw-response-data-eg-headers
+ """
+ return RecoveryKeyResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> RecoveryKeyResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#with_streaming_response
+ """
+ return RecoveryKeyResourceWithStreamingResponse(self)
+
+ def verify(
+ self,
+ *,
+ recovery_key: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> RecoveryKeyVerifyResponse:
+ """
+ Unlock encrypted messages with the user recovery key.
+
+ Args:
+ recovery_key: Recovery key saved by the user.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/v1/app/setup/verification/recovery-key",
+ body=maybe_transform({"recovery_key": recovery_key}, recovery_key_verify_params.RecoveryKeyVerifyParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=RecoveryKeyVerifyResponse,
+ )
+
+
+class AsyncRecoveryKeyResource(AsyncAPIResource):
+ """Manage recovery-key setup for encrypted messages"""
+
+ @cached_property
+ def reset(self) -> AsyncResetResource:
+ """
+ First-party sign-in and encrypted messaging setup for Beeper Desktop and Beeper Server.
+ """
+ return AsyncResetResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncRecoveryKeyResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncRecoveryKeyResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncRecoveryKeyResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#with_streaming_response
+ """
+ return AsyncRecoveryKeyResourceWithStreamingResponse(self)
+
+ async def verify(
+ self,
+ *,
+ recovery_key: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> RecoveryKeyVerifyResponse:
+ """
+ Unlock encrypted messages with the user recovery key.
+
+ Args:
+ recovery_key: Recovery key saved by the user.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/v1/app/setup/verification/recovery-key",
+ body=await async_maybe_transform(
+ {"recovery_key": recovery_key}, recovery_key_verify_params.RecoveryKeyVerifyParams
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=RecoveryKeyVerifyResponse,
+ )
+
+
+class RecoveryKeyResourceWithRawResponse:
+ def __init__(self, recovery_key: RecoveryKeyResource) -> None:
+ self._recovery_key = recovery_key
+
+ self.verify = to_raw_response_wrapper(
+ recovery_key.verify,
+ )
+
+ @cached_property
+ def reset(self) -> ResetResourceWithRawResponse:
+ """
+ First-party sign-in and encrypted messaging setup for Beeper Desktop and Beeper Server.
+ """
+ return ResetResourceWithRawResponse(self._recovery_key.reset)
+
+
+class AsyncRecoveryKeyResourceWithRawResponse:
+ def __init__(self, recovery_key: AsyncRecoveryKeyResource) -> None:
+ self._recovery_key = recovery_key
+
+ self.verify = async_to_raw_response_wrapper(
+ recovery_key.verify,
+ )
+
+ @cached_property
+ def reset(self) -> AsyncResetResourceWithRawResponse:
+ """
+ First-party sign-in and encrypted messaging setup for Beeper Desktop and Beeper Server.
+ """
+ return AsyncResetResourceWithRawResponse(self._recovery_key.reset)
+
+
+class RecoveryKeyResourceWithStreamingResponse:
+ def __init__(self, recovery_key: RecoveryKeyResource) -> None:
+ self._recovery_key = recovery_key
+
+ self.verify = to_streamed_response_wrapper(
+ recovery_key.verify,
+ )
+
+ @cached_property
+ def reset(self) -> ResetResourceWithStreamingResponse:
+ """
+ First-party sign-in and encrypted messaging setup for Beeper Desktop and Beeper Server.
+ """
+ return ResetResourceWithStreamingResponse(self._recovery_key.reset)
+
+
+class AsyncRecoveryKeyResourceWithStreamingResponse:
+ def __init__(self, recovery_key: AsyncRecoveryKeyResource) -> None:
+ self._recovery_key = recovery_key
+
+ self.verify = async_to_streamed_response_wrapper(
+ recovery_key.verify,
+ )
+
+ @cached_property
+ def reset(self) -> AsyncResetResourceWithStreamingResponse:
+ """
+ First-party sign-in and encrypted messaging setup for Beeper Desktop and Beeper Server.
+ """
+ return AsyncResetResourceWithStreamingResponse(self._recovery_key.reset)
diff --git a/src/beeper_desktop_api/resources/app/setup/recovery_key/reset.py b/src/beeper_desktop_api/resources/app/setup/recovery_key/reset.py
new file mode 100644
index 0000000..2fdffe8
--- /dev/null
+++ b/src/beeper_desktop_api/resources/app/setup/recovery_key/reset.py
@@ -0,0 +1,260 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from ....._utils import maybe_transform, async_maybe_transform
+from ....._compat import cached_property
+from ....._resource import SyncAPIResource, AsyncAPIResource
+from ....._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ....._base_client import make_request_options
+from .....types.app.setup.recovery_key import reset_create_params, reset_confirm_params
+from .....types.app.setup.recovery_key.reset_create_response import ResetCreateResponse
+from .....types.app.setup.recovery_key.reset_confirm_response import ResetConfirmResponse
+
+__all__ = ["ResetResource", "AsyncResetResource"]
+
+
+class ResetResource(SyncAPIResource):
+ """
+ First-party sign-in and encrypted messaging setup for Beeper Desktop and Beeper Server.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> ResetResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#accessing-raw-response-data-eg-headers
+ """
+ return ResetResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> ResetResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#with_streaming_response
+ """
+ return ResetResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ *,
+ existing_recovery_key: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ResetCreateResponse:
+ """
+ Create a new recovery key when the user cannot use the existing one.
+
+ Args:
+ existing_recovery_key: Existing recovery key, if the user has it.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/v1/app/setup/verification/recovery-key/reset",
+ body=maybe_transform(
+ {"existing_recovery_key": existing_recovery_key}, reset_create_params.ResetCreateParams
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ResetCreateResponse,
+ )
+
+ def confirm(
+ self,
+ *,
+ recovery_key: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ResetConfirmResponse:
+ """
+ Confirm that the new recovery key should be used for this account.
+
+ Args:
+ recovery_key: New recovery key returned by the reset step.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/v1/app/setup/verification/recovery-key/reset/confirm",
+ body=maybe_transform({"recovery_key": recovery_key}, reset_confirm_params.ResetConfirmParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ResetConfirmResponse,
+ )
+
+
+class AsyncResetResource(AsyncAPIResource):
+ """
+ First-party sign-in and encrypted messaging setup for Beeper Desktop and Beeper Server.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> AsyncResetResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncResetResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncResetResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#with_streaming_response
+ """
+ return AsyncResetResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ *,
+ existing_recovery_key: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ResetCreateResponse:
+ """
+ Create a new recovery key when the user cannot use the existing one.
+
+ Args:
+ existing_recovery_key: Existing recovery key, if the user has it.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/v1/app/setup/verification/recovery-key/reset",
+ body=await async_maybe_transform(
+ {"existing_recovery_key": existing_recovery_key}, reset_create_params.ResetCreateParams
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ResetCreateResponse,
+ )
+
+ async def confirm(
+ self,
+ *,
+ recovery_key: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ResetConfirmResponse:
+ """
+ Confirm that the new recovery key should be used for this account.
+
+ Args:
+ recovery_key: New recovery key returned by the reset step.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/v1/app/setup/verification/recovery-key/reset/confirm",
+ body=await async_maybe_transform({"recovery_key": recovery_key}, reset_confirm_params.ResetConfirmParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ResetConfirmResponse,
+ )
+
+
+class ResetResourceWithRawResponse:
+ def __init__(self, reset: ResetResource) -> None:
+ self._reset = reset
+
+ self.create = to_raw_response_wrapper(
+ reset.create,
+ )
+ self.confirm = to_raw_response_wrapper(
+ reset.confirm,
+ )
+
+
+class AsyncResetResourceWithRawResponse:
+ def __init__(self, reset: AsyncResetResource) -> None:
+ self._reset = reset
+
+ self.create = async_to_raw_response_wrapper(
+ reset.create,
+ )
+ self.confirm = async_to_raw_response_wrapper(
+ reset.confirm,
+ )
+
+
+class ResetResourceWithStreamingResponse:
+ def __init__(self, reset: ResetResource) -> None:
+ self._reset = reset
+
+ self.create = to_streamed_response_wrapper(
+ reset.create,
+ )
+ self.confirm = to_streamed_response_wrapper(
+ reset.confirm,
+ )
+
+
+class AsyncResetResourceWithStreamingResponse:
+ def __init__(self, reset: AsyncResetResource) -> None:
+ self._reset = reset
+
+ self.create = async_to_streamed_response_wrapper(
+ reset.create,
+ )
+ self.confirm = async_to_streamed_response_wrapper(
+ reset.confirm,
+ )
diff --git a/src/beeper_desktop_api/resources/app/setup/setup.py b/src/beeper_desktop_api/resources/app/setup/setup.py
new file mode 100644
index 0000000..e57a709
--- /dev/null
+++ b/src/beeper_desktop_api/resources/app/setup/setup.py
@@ -0,0 +1,655 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Any, cast
+from typing_extensions import Literal
+
+import httpx
+
+from ...._types import Body, Query, Headers, NoneType, NotGiven, not_given
+from ...._utils import maybe_transform, async_maybe_transform
+from ...._compat import cached_property
+from ...._resource import SyncAPIResource, AsyncAPIResource
+from ...._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ....types.app import setup_email_params, setup_register_params, setup_response_params
+from ...._base_client import make_request_options
+from .recovery_key.recovery_key import (
+ RecoveryKeyResource,
+ AsyncRecoveryKeyResource,
+ RecoveryKeyResourceWithRawResponse,
+ AsyncRecoveryKeyResourceWithRawResponse,
+ RecoveryKeyResourceWithStreamingResponse,
+ AsyncRecoveryKeyResourceWithStreamingResponse,
+)
+from .verifications.verifications import (
+ VerificationsResource,
+ AsyncVerificationsResource,
+ VerificationsResourceWithRawResponse,
+ AsyncVerificationsResourceWithRawResponse,
+ VerificationsResourceWithStreamingResponse,
+ AsyncVerificationsResourceWithStreamingResponse,
+)
+from ....types.app.setup_start_response import SetupStartResponse
+from ....types.app.setup_register_response import SetupRegisterResponse
+from ....types.app.setup_response_response import SetupResponseResponse
+from ....types.app.setup_retrieve_response import SetupRetrieveResponse
+
+__all__ = ["SetupResource", "AsyncSetupResource"]
+
+
+class SetupResource(SyncAPIResource):
+ """Complete first-party Beeper app setup"""
+
+ @cached_property
+ def recovery_key(self) -> RecoveryKeyResource:
+ """Manage recovery-key setup for encrypted messages"""
+ return RecoveryKeyResource(self._client)
+
+ @cached_property
+ def verifications(self) -> VerificationsResource:
+ """Manage device verification transactions"""
+ return VerificationsResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> SetupResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#accessing-raw-response-data-eg-headers
+ """
+ return SetupResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> SetupResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#with_streaming_response
+ """
+ return SetupResourceWithStreamingResponse(self)
+
+ def retrieve(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SetupRetrieveResponse:
+ """
+ Return the current Beeper Desktop or Beeper Server sign-in and encrypted
+ messaging setup state. This endpoint is public before sign-in so apps can
+ discover that sign-in is needed; after sign-in, pass a read token.
+ """
+ return self._get(
+ "/v1/app/setup",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SetupRetrieveResponse,
+ )
+
+ def email(
+ self,
+ *,
+ email: str,
+ setup_request_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Send a sign-in code to the user email address for app setup.
+
+ Args:
+ email: Email address to send the sign-in code to.
+
+ setup_request_id: Setup request ID returned by the start step.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ "/v1/app/setup/email",
+ body=maybe_transform(
+ {
+ "email": email,
+ "setup_request_id": setup_request_id,
+ },
+ setup_email_params.SetupEmailParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ security={},
+ ),
+ cast_to=NoneType,
+ )
+
+ def register(
+ self,
+ *,
+ accept_terms: Literal[True],
+ lead_token: str,
+ setup_request_id: str,
+ username: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SetupRegisterResponse:
+ """
+ Create a Beeper account after the user chooses a username and accepts the Terms
+ of Use.
+
+ Args:
+ accept_terms: Confirms that the user agreed to our
+ [terms of use](https://www.beeper.com/terms-onboarding) and has read our
+ [privacy policy](https://www.beeper.com/privacy).
+
+ lead_token: Registration token returned by Beeper.
+
+ setup_request_id: Setup request ID returned by the start step.
+
+ username: Username selected by the user.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/v1/app/setup/register",
+ body=maybe_transform(
+ {
+ "accept_terms": accept_terms,
+ "lead_token": lead_token,
+ "setup_request_id": setup_request_id,
+ "username": username,
+ },
+ setup_register_params.SetupRegisterParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ security={},
+ ),
+ cast_to=SetupRegisterResponse,
+ )
+
+ def response(
+ self,
+ *,
+ response: str,
+ setup_request_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SetupResponseResponse:
+ """Finish setup sign-in with the code sent to the user email address.
+
+ If the user
+ needs a new account, the response includes account creation copy and username
+ suggestions.
+
+ Args:
+ response: Sign-in code from the user email.
+
+ setup_request_id: Setup request ID returned by the start step.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return cast(
+ SetupResponseResponse,
+ self._post(
+ "/v1/app/setup/response",
+ body=maybe_transform(
+ {
+ "response": response,
+ "setup_request_id": setup_request_id,
+ },
+ setup_response_params.SetupResponseParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ security={},
+ ),
+ cast_to=cast(
+ Any, SetupResponseResponse
+ ), # Union types cannot be passed in as arguments in the type system
+ ),
+ )
+
+ def start(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SetupStartResponse:
+ """Start setting up Beeper Desktop or Beeper Server.
+
+ The flow supports existing
+ Beeper accounts and new account creation.
+ """
+ return self._post(
+ "/v1/app/setup/start",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ security={},
+ ),
+ cast_to=SetupStartResponse,
+ )
+
+
+class AsyncSetupResource(AsyncAPIResource):
+ """Complete first-party Beeper app setup"""
+
+ @cached_property
+ def recovery_key(self) -> AsyncRecoveryKeyResource:
+ """Manage recovery-key setup for encrypted messages"""
+ return AsyncRecoveryKeyResource(self._client)
+
+ @cached_property
+ def verifications(self) -> AsyncVerificationsResource:
+ """Manage device verification transactions"""
+ return AsyncVerificationsResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncSetupResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncSetupResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncSetupResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#with_streaming_response
+ """
+ return AsyncSetupResourceWithStreamingResponse(self)
+
+ async def retrieve(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SetupRetrieveResponse:
+ """
+ Return the current Beeper Desktop or Beeper Server sign-in and encrypted
+ messaging setup state. This endpoint is public before sign-in so apps can
+ discover that sign-in is needed; after sign-in, pass a read token.
+ """
+ return await self._get(
+ "/v1/app/setup",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SetupRetrieveResponse,
+ )
+
+ async def email(
+ self,
+ *,
+ email: str,
+ setup_request_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Send a sign-in code to the user email address for app setup.
+
+ Args:
+ email: Email address to send the sign-in code to.
+
+ setup_request_id: Setup request ID returned by the start step.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ "/v1/app/setup/email",
+ body=await async_maybe_transform(
+ {
+ "email": email,
+ "setup_request_id": setup_request_id,
+ },
+ setup_email_params.SetupEmailParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ security={},
+ ),
+ cast_to=NoneType,
+ )
+
+ async def register(
+ self,
+ *,
+ accept_terms: Literal[True],
+ lead_token: str,
+ setup_request_id: str,
+ username: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SetupRegisterResponse:
+ """
+ Create a Beeper account after the user chooses a username and accepts the Terms
+ of Use.
+
+ Args:
+ accept_terms: Confirms that the user agreed to our
+ [terms of use](https://www.beeper.com/terms-onboarding) and has read our
+ [privacy policy](https://www.beeper.com/privacy).
+
+ lead_token: Registration token returned by Beeper.
+
+ setup_request_id: Setup request ID returned by the start step.
+
+ username: Username selected by the user.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/v1/app/setup/register",
+ body=await async_maybe_transform(
+ {
+ "accept_terms": accept_terms,
+ "lead_token": lead_token,
+ "setup_request_id": setup_request_id,
+ "username": username,
+ },
+ setup_register_params.SetupRegisterParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ security={},
+ ),
+ cast_to=SetupRegisterResponse,
+ )
+
+ async def response(
+ self,
+ *,
+ response: str,
+ setup_request_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SetupResponseResponse:
+ """Finish setup sign-in with the code sent to the user email address.
+
+ If the user
+ needs a new account, the response includes account creation copy and username
+ suggestions.
+
+ Args:
+ response: Sign-in code from the user email.
+
+ setup_request_id: Setup request ID returned by the start step.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return cast(
+ SetupResponseResponse,
+ await self._post(
+ "/v1/app/setup/response",
+ body=await async_maybe_transform(
+ {
+ "response": response,
+ "setup_request_id": setup_request_id,
+ },
+ setup_response_params.SetupResponseParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ security={},
+ ),
+ cast_to=cast(
+ Any, SetupResponseResponse
+ ), # Union types cannot be passed in as arguments in the type system
+ ),
+ )
+
+ async def start(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SetupStartResponse:
+ """Start setting up Beeper Desktop or Beeper Server.
+
+ The flow supports existing
+ Beeper accounts and new account creation.
+ """
+ return await self._post(
+ "/v1/app/setup/start",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ security={},
+ ),
+ cast_to=SetupStartResponse,
+ )
+
+
+class SetupResourceWithRawResponse:
+ def __init__(self, setup: SetupResource) -> None:
+ self._setup = setup
+
+ self.retrieve = to_raw_response_wrapper(
+ setup.retrieve,
+ )
+ self.email = to_raw_response_wrapper(
+ setup.email,
+ )
+ self.register = to_raw_response_wrapper(
+ setup.register,
+ )
+ self.response = to_raw_response_wrapper(
+ setup.response,
+ )
+ self.start = to_raw_response_wrapper(
+ setup.start,
+ )
+
+ @cached_property
+ def recovery_key(self) -> RecoveryKeyResourceWithRawResponse:
+ """Manage recovery-key setup for encrypted messages"""
+ return RecoveryKeyResourceWithRawResponse(self._setup.recovery_key)
+
+ @cached_property
+ def verifications(self) -> VerificationsResourceWithRawResponse:
+ """Manage device verification transactions"""
+ return VerificationsResourceWithRawResponse(self._setup.verifications)
+
+
+class AsyncSetupResourceWithRawResponse:
+ def __init__(self, setup: AsyncSetupResource) -> None:
+ self._setup = setup
+
+ self.retrieve = async_to_raw_response_wrapper(
+ setup.retrieve,
+ )
+ self.email = async_to_raw_response_wrapper(
+ setup.email,
+ )
+ self.register = async_to_raw_response_wrapper(
+ setup.register,
+ )
+ self.response = async_to_raw_response_wrapper(
+ setup.response,
+ )
+ self.start = async_to_raw_response_wrapper(
+ setup.start,
+ )
+
+ @cached_property
+ def recovery_key(self) -> AsyncRecoveryKeyResourceWithRawResponse:
+ """Manage recovery-key setup for encrypted messages"""
+ return AsyncRecoveryKeyResourceWithRawResponse(self._setup.recovery_key)
+
+ @cached_property
+ def verifications(self) -> AsyncVerificationsResourceWithRawResponse:
+ """Manage device verification transactions"""
+ return AsyncVerificationsResourceWithRawResponse(self._setup.verifications)
+
+
+class SetupResourceWithStreamingResponse:
+ def __init__(self, setup: SetupResource) -> None:
+ self._setup = setup
+
+ self.retrieve = to_streamed_response_wrapper(
+ setup.retrieve,
+ )
+ self.email = to_streamed_response_wrapper(
+ setup.email,
+ )
+ self.register = to_streamed_response_wrapper(
+ setup.register,
+ )
+ self.response = to_streamed_response_wrapper(
+ setup.response,
+ )
+ self.start = to_streamed_response_wrapper(
+ setup.start,
+ )
+
+ @cached_property
+ def recovery_key(self) -> RecoveryKeyResourceWithStreamingResponse:
+ """Manage recovery-key setup for encrypted messages"""
+ return RecoveryKeyResourceWithStreamingResponse(self._setup.recovery_key)
+
+ @cached_property
+ def verifications(self) -> VerificationsResourceWithStreamingResponse:
+ """Manage device verification transactions"""
+ return VerificationsResourceWithStreamingResponse(self._setup.verifications)
+
+
+class AsyncSetupResourceWithStreamingResponse:
+ def __init__(self, setup: AsyncSetupResource) -> None:
+ self._setup = setup
+
+ self.retrieve = async_to_streamed_response_wrapper(
+ setup.retrieve,
+ )
+ self.email = async_to_streamed_response_wrapper(
+ setup.email,
+ )
+ self.register = async_to_streamed_response_wrapper(
+ setup.register,
+ )
+ self.response = async_to_streamed_response_wrapper(
+ setup.response,
+ )
+ self.start = async_to_streamed_response_wrapper(
+ setup.start,
+ )
+
+ @cached_property
+ def recovery_key(self) -> AsyncRecoveryKeyResourceWithStreamingResponse:
+ """Manage recovery-key setup for encrypted messages"""
+ return AsyncRecoveryKeyResourceWithStreamingResponse(self._setup.recovery_key)
+
+ @cached_property
+ def verifications(self) -> AsyncVerificationsResourceWithStreamingResponse:
+ """Manage device verification transactions"""
+ return AsyncVerificationsResourceWithStreamingResponse(self._setup.verifications)
diff --git a/src/beeper_desktop_api/resources/app/setup/verifications/__init__.py b/src/beeper_desktop_api/resources/app/setup/verifications/__init__.py
new file mode 100644
index 0000000..db2a226
--- /dev/null
+++ b/src/beeper_desktop_api/resources/app/setup/verifications/__init__.py
@@ -0,0 +1,47 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .qr import (
+ QRResource,
+ AsyncQRResource,
+ QRResourceWithRawResponse,
+ AsyncQRResourceWithRawResponse,
+ QRResourceWithStreamingResponse,
+ AsyncQRResourceWithStreamingResponse,
+)
+from .sas import (
+ SASResource,
+ AsyncSASResource,
+ SASResourceWithRawResponse,
+ AsyncSASResourceWithRawResponse,
+ SASResourceWithStreamingResponse,
+ AsyncSASResourceWithStreamingResponse,
+)
+from .verifications import (
+ VerificationsResource,
+ AsyncVerificationsResource,
+ VerificationsResourceWithRawResponse,
+ AsyncVerificationsResourceWithRawResponse,
+ VerificationsResourceWithStreamingResponse,
+ AsyncVerificationsResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "QRResource",
+ "AsyncQRResource",
+ "QRResourceWithRawResponse",
+ "AsyncQRResourceWithRawResponse",
+ "QRResourceWithStreamingResponse",
+ "AsyncQRResourceWithStreamingResponse",
+ "SASResource",
+ "AsyncSASResource",
+ "SASResourceWithRawResponse",
+ "AsyncSASResourceWithRawResponse",
+ "SASResourceWithStreamingResponse",
+ "AsyncSASResourceWithStreamingResponse",
+ "VerificationsResource",
+ "AsyncVerificationsResource",
+ "VerificationsResourceWithRawResponse",
+ "AsyncVerificationsResourceWithRawResponse",
+ "VerificationsResourceWithStreamingResponse",
+ "AsyncVerificationsResourceWithStreamingResponse",
+]
diff --git a/src/beeper_desktop_api/resources/app/setup/verifications/qr.py b/src/beeper_desktop_api/resources/app/setup/verifications/qr.py
new file mode 100644
index 0000000..b0fe536
--- /dev/null
+++ b/src/beeper_desktop_api/resources/app/setup/verifications/qr.py
@@ -0,0 +1,262 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ....._types import Body, Query, Headers, NotGiven, not_given
+from ....._utils import path_template, maybe_transform, async_maybe_transform
+from ....._compat import cached_property
+from ....._resource import SyncAPIResource, AsyncAPIResource
+from ....._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ....._base_client import make_request_options
+from .....types.app.setup.verifications import qr_scan_params
+from .....types.app.setup.verifications.qr_scan_response import QRScanResponse
+from .....types.app.setup.verifications.qr_confirm_scanned_response import QRConfirmScannedResponse
+
+__all__ = ["QRResource", "AsyncQRResource"]
+
+
+class QRResource(SyncAPIResource):
+ """
+ First-party sign-in and encrypted messaging setup for Beeper Desktop and Beeper Server.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> QRResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#accessing-raw-response-data-eg-headers
+ """
+ return QRResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> QRResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#with_streaming_response
+ """
+ return QRResourceWithStreamingResponse(self)
+
+ def confirm_scanned(
+ self,
+ verification_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> QRConfirmScannedResponse:
+ """
+ Confirm that another device scanned this device QR code.
+
+ Args:
+ verification_id: Verification ID.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not verification_id:
+ raise ValueError(f"Expected a non-empty value for `verification_id` but received {verification_id!r}")
+ return self._post(
+ path_template(
+ "/v1/app/setup/verifications/{verification_id}/qr/confirm-scanned", verification_id=verification_id
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=QRConfirmScannedResponse,
+ )
+
+ def scan(
+ self,
+ *,
+ data: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> QRScanResponse:
+ """
+ Submit the QR code scanned from another signed-in device.
+
+ Args:
+ data: QR code payload scanned from the other device.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/v1/app/setup/verifications/qr/scan",
+ body=maybe_transform({"data": data}, qr_scan_params.QRScanParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=QRScanResponse,
+ )
+
+
+class AsyncQRResource(AsyncAPIResource):
+ """
+ First-party sign-in and encrypted messaging setup for Beeper Desktop and Beeper Server.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> AsyncQRResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncQRResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncQRResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#with_streaming_response
+ """
+ return AsyncQRResourceWithStreamingResponse(self)
+
+ async def confirm_scanned(
+ self,
+ verification_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> QRConfirmScannedResponse:
+ """
+ Confirm that another device scanned this device QR code.
+
+ Args:
+ verification_id: Verification ID.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not verification_id:
+ raise ValueError(f"Expected a non-empty value for `verification_id` but received {verification_id!r}")
+ return await self._post(
+ path_template(
+ "/v1/app/setup/verifications/{verification_id}/qr/confirm-scanned", verification_id=verification_id
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=QRConfirmScannedResponse,
+ )
+
+ async def scan(
+ self,
+ *,
+ data: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> QRScanResponse:
+ """
+ Submit the QR code scanned from another signed-in device.
+
+ Args:
+ data: QR code payload scanned from the other device.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/v1/app/setup/verifications/qr/scan",
+ body=await async_maybe_transform({"data": data}, qr_scan_params.QRScanParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=QRScanResponse,
+ )
+
+
+class QRResourceWithRawResponse:
+ def __init__(self, qr: QRResource) -> None:
+ self._qr = qr
+
+ self.confirm_scanned = to_raw_response_wrapper(
+ qr.confirm_scanned,
+ )
+ self.scan = to_raw_response_wrapper(
+ qr.scan,
+ )
+
+
+class AsyncQRResourceWithRawResponse:
+ def __init__(self, qr: AsyncQRResource) -> None:
+ self._qr = qr
+
+ self.confirm_scanned = async_to_raw_response_wrapper(
+ qr.confirm_scanned,
+ )
+ self.scan = async_to_raw_response_wrapper(
+ qr.scan,
+ )
+
+
+class QRResourceWithStreamingResponse:
+ def __init__(self, qr: QRResource) -> None:
+ self._qr = qr
+
+ self.confirm_scanned = to_streamed_response_wrapper(
+ qr.confirm_scanned,
+ )
+ self.scan = to_streamed_response_wrapper(
+ qr.scan,
+ )
+
+
+class AsyncQRResourceWithStreamingResponse:
+ def __init__(self, qr: AsyncQRResource) -> None:
+ self._qr = qr
+
+ self.confirm_scanned = async_to_streamed_response_wrapper(
+ qr.confirm_scanned,
+ )
+ self.scan = async_to_streamed_response_wrapper(
+ qr.scan,
+ )
diff --git a/src/beeper_desktop_api/resources/app/setup/verifications/sas.py b/src/beeper_desktop_api/resources/app/setup/verifications/sas.py
new file mode 100644
index 0000000..852c14b
--- /dev/null
+++ b/src/beeper_desktop_api/resources/app/setup/verifications/sas.py
@@ -0,0 +1,259 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ....._types import Body, Query, Headers, NotGiven, not_given
+from ....._utils import path_template
+from ....._compat import cached_property
+from ....._resource import SyncAPIResource, AsyncAPIResource
+from ....._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ....._base_client import make_request_options
+from .....types.app.setup.verifications.sas_start_response import SASStartResponse
+from .....types.app.setup.verifications.sas_confirm_response import SASConfirmResponse
+
+__all__ = ["SASResource", "AsyncSASResource"]
+
+
+class SASResource(SyncAPIResource):
+ """
+ First-party sign-in and encrypted messaging setup for Beeper Desktop and Beeper Server.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> SASResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#accessing-raw-response-data-eg-headers
+ """
+ return SASResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> SASResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#with_streaming_response
+ """
+ return SASResourceWithStreamingResponse(self)
+
+ def confirm(
+ self,
+ verification_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SASConfirmResponse:
+ """
+ Confirm that the emoji or number sequence matches on both devices.
+
+ Args:
+ verification_id: Verification ID.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not verification_id:
+ raise ValueError(f"Expected a non-empty value for `verification_id` but received {verification_id!r}")
+ return self._post(
+ path_template("/v1/app/setup/verifications/{verification_id}/sas/confirm", verification_id=verification_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SASConfirmResponse,
+ )
+
+ def start(
+ self,
+ verification_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SASStartResponse:
+ """
+ Start emoji comparison for device verification.
+
+ Args:
+ verification_id: Verification ID.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not verification_id:
+ raise ValueError(f"Expected a non-empty value for `verification_id` but received {verification_id!r}")
+ return self._post(
+ path_template("/v1/app/setup/verifications/{verification_id}/sas/start", verification_id=verification_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SASStartResponse,
+ )
+
+
+class AsyncSASResource(AsyncAPIResource):
+ """
+ First-party sign-in and encrypted messaging setup for Beeper Desktop and Beeper Server.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> AsyncSASResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncSASResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncSASResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#with_streaming_response
+ """
+ return AsyncSASResourceWithStreamingResponse(self)
+
+ async def confirm(
+ self,
+ verification_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SASConfirmResponse:
+ """
+ Confirm that the emoji or number sequence matches on both devices.
+
+ Args:
+ verification_id: Verification ID.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not verification_id:
+ raise ValueError(f"Expected a non-empty value for `verification_id` but received {verification_id!r}")
+ return await self._post(
+ path_template("/v1/app/setup/verifications/{verification_id}/sas/confirm", verification_id=verification_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SASConfirmResponse,
+ )
+
+ async def start(
+ self,
+ verification_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SASStartResponse:
+ """
+ Start emoji comparison for device verification.
+
+ Args:
+ verification_id: Verification ID.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not verification_id:
+ raise ValueError(f"Expected a non-empty value for `verification_id` but received {verification_id!r}")
+ return await self._post(
+ path_template("/v1/app/setup/verifications/{verification_id}/sas/start", verification_id=verification_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SASStartResponse,
+ )
+
+
+class SASResourceWithRawResponse:
+ def __init__(self, sas: SASResource) -> None:
+ self._sas = sas
+
+ self.confirm = to_raw_response_wrapper(
+ sas.confirm,
+ )
+ self.start = to_raw_response_wrapper(
+ sas.start,
+ )
+
+
+class AsyncSASResourceWithRawResponse:
+ def __init__(self, sas: AsyncSASResource) -> None:
+ self._sas = sas
+
+ self.confirm = async_to_raw_response_wrapper(
+ sas.confirm,
+ )
+ self.start = async_to_raw_response_wrapper(
+ sas.start,
+ )
+
+
+class SASResourceWithStreamingResponse:
+ def __init__(self, sas: SASResource) -> None:
+ self._sas = sas
+
+ self.confirm = to_streamed_response_wrapper(
+ sas.confirm,
+ )
+ self.start = to_streamed_response_wrapper(
+ sas.start,
+ )
+
+
+class AsyncSASResourceWithStreamingResponse:
+ def __init__(self, sas: AsyncSASResource) -> None:
+ self._sas = sas
+
+ self.confirm = async_to_streamed_response_wrapper(
+ sas.confirm,
+ )
+ self.start = async_to_streamed_response_wrapper(
+ sas.start,
+ )
diff --git a/src/beeper_desktop_api/resources/app/setup/verifications/verifications.py b/src/beeper_desktop_api/resources/app/setup/verifications/verifications.py
new file mode 100644
index 0000000..21fe51e
--- /dev/null
+++ b/src/beeper_desktop_api/resources/app/setup/verifications/verifications.py
@@ -0,0 +1,625 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal
+
+import httpx
+
+from .qr import (
+ QRResource,
+ AsyncQRResource,
+ QRResourceWithRawResponse,
+ AsyncQRResourceWithRawResponse,
+ QRResourceWithStreamingResponse,
+ AsyncQRResourceWithStreamingResponse,
+)
+from .sas import (
+ SASResource,
+ AsyncSASResource,
+ SASResourceWithRawResponse,
+ AsyncSASResourceWithRawResponse,
+ SASResourceWithStreamingResponse,
+ AsyncSASResourceWithStreamingResponse,
+)
+from ....._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from ....._utils import path_template, maybe_transform, async_maybe_transform
+from ....._compat import cached_property
+from ....._resource import SyncAPIResource, AsyncAPIResource
+from ....._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ....._base_client import make_request_options
+from .....types.app.setup import verification_cancel_params, verification_create_params
+from .....types.app.setup.verification_list_response import VerificationListResponse
+from .....types.app.setup.verification_accept_response import VerificationAcceptResponse
+from .....types.app.setup.verification_cancel_response import VerificationCancelResponse
+from .....types.app.setup.verification_create_response import VerificationCreateResponse
+from .....types.app.setup.verification_retrieve_response import VerificationRetrieveResponse
+
+__all__ = ["VerificationsResource", "AsyncVerificationsResource"]
+
+
+class VerificationsResource(SyncAPIResource):
+ """Manage device verification transactions"""
+
+ @cached_property
+ def qr(self) -> QRResource:
+ """
+ First-party sign-in and encrypted messaging setup for Beeper Desktop and Beeper Server.
+ """
+ return QRResource(self._client)
+
+ @cached_property
+ def sas(self) -> SASResource:
+ """
+ First-party sign-in and encrypted messaging setup for Beeper Desktop and Beeper Server.
+ """
+ return SASResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> VerificationsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#accessing-raw-response-data-eg-headers
+ """
+ return VerificationsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> VerificationsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#with_streaming_response
+ """
+ return VerificationsResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ *,
+ purpose: Literal["login", "device"] | Omit = omit,
+ user_id: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VerificationCreateResponse:
+ """
+ Start verifying this device from another signed-in device.
+
+ Args:
+ purpose: Why this verification is being started.
+
+ user_id: Beeper user ID to verify. Defaults to the signed-in user.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/v1/app/setup/verifications",
+ body=maybe_transform(
+ {
+ "purpose": purpose,
+ "user_id": user_id,
+ },
+ verification_create_params.VerificationCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=VerificationCreateResponse,
+ )
+
+ def retrieve(
+ self,
+ verification_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VerificationRetrieveResponse:
+ """
+ Get the current state of a device verification transaction.
+
+ Args:
+ verification_id: Verification ID.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not verification_id:
+ raise ValueError(f"Expected a non-empty value for `verification_id` but received {verification_id!r}")
+ return self._get(
+ path_template("/v1/app/setup/verifications/{verification_id}", verification_id=verification_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=VerificationRetrieveResponse,
+ )
+
+ def list(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VerificationListResponse:
+ """List pending and active device verifications.
+
+ Use this to recover state without
+ a WebSocket connection.
+ """
+ return self._get(
+ "/v1/app/setup/verifications",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=VerificationListResponse,
+ )
+
+ def accept(
+ self,
+ verification_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VerificationAcceptResponse:
+ """
+ Accept an incoming device verification request.
+
+ Args:
+ verification_id: Verification ID.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not verification_id:
+ raise ValueError(f"Expected a non-empty value for `verification_id` but received {verification_id!r}")
+ return self._post(
+ path_template("/v1/app/setup/verifications/{verification_id}/accept", verification_id=verification_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=VerificationAcceptResponse,
+ )
+
+ def cancel(
+ self,
+ verification_id: str,
+ *,
+ code: str | Omit = omit,
+ reason: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VerificationCancelResponse:
+ """
+ Cancel an active device verification request.
+
+ Args:
+ verification_id: Verification ID.
+
+ code: Optional cancellation code.
+
+ reason: Optional user-facing cancellation reason.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not verification_id:
+ raise ValueError(f"Expected a non-empty value for `verification_id` but received {verification_id!r}")
+ return self._post(
+ path_template("/v1/app/setup/verifications/{verification_id}/cancel", verification_id=verification_id),
+ body=maybe_transform(
+ {
+ "code": code,
+ "reason": reason,
+ },
+ verification_cancel_params.VerificationCancelParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=VerificationCancelResponse,
+ )
+
+
+class AsyncVerificationsResource(AsyncAPIResource):
+ """Manage device verification transactions"""
+
+ @cached_property
+ def qr(self) -> AsyncQRResource:
+ """
+ First-party sign-in and encrypted messaging setup for Beeper Desktop and Beeper Server.
+ """
+ return AsyncQRResource(self._client)
+
+ @cached_property
+ def sas(self) -> AsyncSASResource:
+ """
+ First-party sign-in and encrypted messaging setup for Beeper Desktop and Beeper Server.
+ """
+ return AsyncSASResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncVerificationsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncVerificationsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncVerificationsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#with_streaming_response
+ """
+ return AsyncVerificationsResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ *,
+ purpose: Literal["login", "device"] | Omit = omit,
+ user_id: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VerificationCreateResponse:
+ """
+ Start verifying this device from another signed-in device.
+
+ Args:
+ purpose: Why this verification is being started.
+
+ user_id: Beeper user ID to verify. Defaults to the signed-in user.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/v1/app/setup/verifications",
+ body=await async_maybe_transform(
+ {
+ "purpose": purpose,
+ "user_id": user_id,
+ },
+ verification_create_params.VerificationCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=VerificationCreateResponse,
+ )
+
+ async def retrieve(
+ self,
+ verification_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VerificationRetrieveResponse:
+ """
+ Get the current state of a device verification transaction.
+
+ Args:
+ verification_id: Verification ID.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not verification_id:
+ raise ValueError(f"Expected a non-empty value for `verification_id` but received {verification_id!r}")
+ return await self._get(
+ path_template("/v1/app/setup/verifications/{verification_id}", verification_id=verification_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=VerificationRetrieveResponse,
+ )
+
+ async def list(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VerificationListResponse:
+ """List pending and active device verifications.
+
+ Use this to recover state without
+ a WebSocket connection.
+ """
+ return await self._get(
+ "/v1/app/setup/verifications",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=VerificationListResponse,
+ )
+
+ async def accept(
+ self,
+ verification_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VerificationAcceptResponse:
+ """
+ Accept an incoming device verification request.
+
+ Args:
+ verification_id: Verification ID.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not verification_id:
+ raise ValueError(f"Expected a non-empty value for `verification_id` but received {verification_id!r}")
+ return await self._post(
+ path_template("/v1/app/setup/verifications/{verification_id}/accept", verification_id=verification_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=VerificationAcceptResponse,
+ )
+
+ async def cancel(
+ self,
+ verification_id: str,
+ *,
+ code: str | Omit = omit,
+ reason: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> VerificationCancelResponse:
+ """
+ Cancel an active device verification request.
+
+ Args:
+ verification_id: Verification ID.
+
+ code: Optional cancellation code.
+
+ reason: Optional user-facing cancellation reason.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not verification_id:
+ raise ValueError(f"Expected a non-empty value for `verification_id` but received {verification_id!r}")
+ return await self._post(
+ path_template("/v1/app/setup/verifications/{verification_id}/cancel", verification_id=verification_id),
+ body=await async_maybe_transform(
+ {
+ "code": code,
+ "reason": reason,
+ },
+ verification_cancel_params.VerificationCancelParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=VerificationCancelResponse,
+ )
+
+
+class VerificationsResourceWithRawResponse:
+ def __init__(self, verifications: VerificationsResource) -> None:
+ self._verifications = verifications
+
+ self.create = to_raw_response_wrapper(
+ verifications.create,
+ )
+ self.retrieve = to_raw_response_wrapper(
+ verifications.retrieve,
+ )
+ self.list = to_raw_response_wrapper(
+ verifications.list,
+ )
+ self.accept = to_raw_response_wrapper(
+ verifications.accept,
+ )
+ self.cancel = to_raw_response_wrapper(
+ verifications.cancel,
+ )
+
+ @cached_property
+ def qr(self) -> QRResourceWithRawResponse:
+ """
+ First-party sign-in and encrypted messaging setup for Beeper Desktop and Beeper Server.
+ """
+ return QRResourceWithRawResponse(self._verifications.qr)
+
+ @cached_property
+ def sas(self) -> SASResourceWithRawResponse:
+ """
+ First-party sign-in and encrypted messaging setup for Beeper Desktop and Beeper Server.
+ """
+ return SASResourceWithRawResponse(self._verifications.sas)
+
+
+class AsyncVerificationsResourceWithRawResponse:
+ def __init__(self, verifications: AsyncVerificationsResource) -> None:
+ self._verifications = verifications
+
+ self.create = async_to_raw_response_wrapper(
+ verifications.create,
+ )
+ self.retrieve = async_to_raw_response_wrapper(
+ verifications.retrieve,
+ )
+ self.list = async_to_raw_response_wrapper(
+ verifications.list,
+ )
+ self.accept = async_to_raw_response_wrapper(
+ verifications.accept,
+ )
+ self.cancel = async_to_raw_response_wrapper(
+ verifications.cancel,
+ )
+
+ @cached_property
+ def qr(self) -> AsyncQRResourceWithRawResponse:
+ """
+ First-party sign-in and encrypted messaging setup for Beeper Desktop and Beeper Server.
+ """
+ return AsyncQRResourceWithRawResponse(self._verifications.qr)
+
+ @cached_property
+ def sas(self) -> AsyncSASResourceWithRawResponse:
+ """
+ First-party sign-in and encrypted messaging setup for Beeper Desktop and Beeper Server.
+ """
+ return AsyncSASResourceWithRawResponse(self._verifications.sas)
+
+
+class VerificationsResourceWithStreamingResponse:
+ def __init__(self, verifications: VerificationsResource) -> None:
+ self._verifications = verifications
+
+ self.create = to_streamed_response_wrapper(
+ verifications.create,
+ )
+ self.retrieve = to_streamed_response_wrapper(
+ verifications.retrieve,
+ )
+ self.list = to_streamed_response_wrapper(
+ verifications.list,
+ )
+ self.accept = to_streamed_response_wrapper(
+ verifications.accept,
+ )
+ self.cancel = to_streamed_response_wrapper(
+ verifications.cancel,
+ )
+
+ @cached_property
+ def qr(self) -> QRResourceWithStreamingResponse:
+ """
+ First-party sign-in and encrypted messaging setup for Beeper Desktop and Beeper Server.
+ """
+ return QRResourceWithStreamingResponse(self._verifications.qr)
+
+ @cached_property
+ def sas(self) -> SASResourceWithStreamingResponse:
+ """
+ First-party sign-in and encrypted messaging setup for Beeper Desktop and Beeper Server.
+ """
+ return SASResourceWithStreamingResponse(self._verifications.sas)
+
+
+class AsyncVerificationsResourceWithStreamingResponse:
+ def __init__(self, verifications: AsyncVerificationsResource) -> None:
+ self._verifications = verifications
+
+ self.create = async_to_streamed_response_wrapper(
+ verifications.create,
+ )
+ self.retrieve = async_to_streamed_response_wrapper(
+ verifications.retrieve,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ verifications.list,
+ )
+ self.accept = async_to_streamed_response_wrapper(
+ verifications.accept,
+ )
+ self.cancel = async_to_streamed_response_wrapper(
+ verifications.cancel,
+ )
+
+ @cached_property
+ def qr(self) -> AsyncQRResourceWithStreamingResponse:
+ """
+ First-party sign-in and encrypted messaging setup for Beeper Desktop and Beeper Server.
+ """
+ return AsyncQRResourceWithStreamingResponse(self._verifications.qr)
+
+ @cached_property
+ def sas(self) -> AsyncSASResourceWithStreamingResponse:
+ """
+ First-party sign-in and encrypted messaging setup for Beeper Desktop and Beeper Server.
+ """
+ return AsyncSASResourceWithStreamingResponse(self._verifications.sas)
diff --git a/src/beeper_desktop_api/resources/assets.py b/src/beeper_desktop_api/resources/assets.py
index ccfeab4..1841ca9 100644
--- a/src/beeper_desktop_api/resources/assets.py
+++ b/src/beeper_desktop_api/resources/assets.py
@@ -35,7 +35,7 @@
class AssetsResource(SyncAPIResource):
- """Manage assets in Beeper Desktop, like message attachments"""
+ """Manage files for message attachments"""
@cached_property
def with_raw_response(self) -> AssetsResourceWithRawResponse:
@@ -68,11 +68,11 @@ def download(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AssetDownloadResponse:
"""
- Download a Matrix file using its mxc:// or localmxc:// URL to the device running
- Beeper Desktop and return the local file URL.
+ Download a file from an mxc:// or localmxc:// URL to the device running the
+ Beeper Client API and return the local file URL.
Args:
- url: Matrix content URL (mxc:// or localmxc://) for the file to download.
+ url: Beeper media URL (mxc:// or localmxc://) for the file to download.
extra_headers: Send extra headers
@@ -147,7 +147,7 @@ def upload(
"""Upload a file to a temporary location using multipart/form-data.
Returns an
- uploadID that can be referenced when sending a message or materializing a draft
+ uploadID that can be referenced when sending a message or creating a draft
attachment.
Args:
@@ -204,8 +204,8 @@ def upload_base64(
"""Upload a file using a JSON body with base64-encoded content.
Returns an uploadID
- that can be referenced when sending a message or materializing a draft
- attachment. Alternative to the multipart upload endpoint.
+ that can be referenced when sending a message or creating a draft attachment.
+ Alternative to the multipart upload endpoint.
Args:
content: Base64-encoded file content (max ~500MB decoded)
@@ -240,7 +240,7 @@ def upload_base64(
class AsyncAssetsResource(AsyncAPIResource):
- """Manage assets in Beeper Desktop, like message attachments"""
+ """Manage files for message attachments"""
@cached_property
def with_raw_response(self) -> AsyncAssetsResourceWithRawResponse:
@@ -273,11 +273,11 @@ async def download(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AssetDownloadResponse:
"""
- Download a Matrix file using its mxc:// or localmxc:// URL to the device running
- Beeper Desktop and return the local file URL.
+ Download a file from an mxc:// or localmxc:// URL to the device running the
+ Beeper Client API and return the local file URL.
Args:
- url: Matrix content URL (mxc:// or localmxc://) for the file to download.
+ url: Beeper media URL (mxc:// or localmxc://) for the file to download.
extra_headers: Send extra headers
@@ -352,7 +352,7 @@ async def upload(
"""Upload a file to a temporary location using multipart/form-data.
Returns an
- uploadID that can be referenced when sending a message or materializing a draft
+ uploadID that can be referenced when sending a message or creating a draft
attachment.
Args:
@@ -409,8 +409,8 @@ async def upload_base64(
"""Upload a file using a JSON body with base64-encoded content.
Returns an uploadID
- that can be referenced when sending a message or materializing a draft
- attachment. Alternative to the multipart upload endpoint.
+ that can be referenced when sending a message or creating a draft attachment.
+ Alternative to the multipart upload endpoint.
Args:
content: Base64-encoded file content (max ~500MB decoded)
diff --git a/src/beeper_desktop_api/resources/bridges/__init__.py b/src/beeper_desktop_api/resources/bridges/__init__.py
new file mode 100644
index 0000000..f6ee06d
--- /dev/null
+++ b/src/beeper_desktop_api/resources/bridges/__init__.py
@@ -0,0 +1,61 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .logins import (
+ LoginsResource,
+ AsyncLoginsResource,
+ LoginsResourceWithRawResponse,
+ AsyncLoginsResourceWithRawResponse,
+ LoginsResourceWithStreamingResponse,
+ AsyncLoginsResourceWithStreamingResponse,
+)
+from .bridges import (
+ BridgesResource,
+ AsyncBridgesResource,
+ BridgesResourceWithRawResponse,
+ AsyncBridgesResourceWithRawResponse,
+ BridgesResourceWithStreamingResponse,
+ AsyncBridgesResourceWithStreamingResponse,
+)
+from .login_flows import (
+ LoginFlowsResource,
+ AsyncLoginFlowsResource,
+ LoginFlowsResourceWithRawResponse,
+ AsyncLoginFlowsResourceWithRawResponse,
+ LoginFlowsResourceWithStreamingResponse,
+ AsyncLoginFlowsResourceWithStreamingResponse,
+)
+from .login_sessions import (
+ LoginSessionsResource,
+ AsyncLoginSessionsResource,
+ LoginSessionsResourceWithRawResponse,
+ AsyncLoginSessionsResourceWithRawResponse,
+ LoginSessionsResourceWithStreamingResponse,
+ AsyncLoginSessionsResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "LoginFlowsResource",
+ "AsyncLoginFlowsResource",
+ "LoginFlowsResourceWithRawResponse",
+ "AsyncLoginFlowsResourceWithRawResponse",
+ "LoginFlowsResourceWithStreamingResponse",
+ "AsyncLoginFlowsResourceWithStreamingResponse",
+ "LoginsResource",
+ "AsyncLoginsResource",
+ "LoginsResourceWithRawResponse",
+ "AsyncLoginsResourceWithRawResponse",
+ "LoginsResourceWithStreamingResponse",
+ "AsyncLoginsResourceWithStreamingResponse",
+ "LoginSessionsResource",
+ "AsyncLoginSessionsResource",
+ "LoginSessionsResourceWithRawResponse",
+ "AsyncLoginSessionsResourceWithRawResponse",
+ "LoginSessionsResourceWithStreamingResponse",
+ "AsyncLoginSessionsResourceWithStreamingResponse",
+ "BridgesResource",
+ "AsyncBridgesResource",
+ "BridgesResourceWithRawResponse",
+ "AsyncBridgesResourceWithRawResponse",
+ "BridgesResourceWithStreamingResponse",
+ "AsyncBridgesResourceWithStreamingResponse",
+]
diff --git a/src/beeper_desktop_api/resources/bridges/bridges.py b/src/beeper_desktop_api/resources/bridges/bridges.py
new file mode 100644
index 0000000..fb36efd
--- /dev/null
+++ b/src/beeper_desktop_api/resources/bridges/bridges.py
@@ -0,0 +1,470 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from .logins import (
+ LoginsResource,
+ AsyncLoginsResource,
+ LoginsResourceWithRawResponse,
+ AsyncLoginsResourceWithRawResponse,
+ LoginsResourceWithStreamingResponse,
+ AsyncLoginsResourceWithStreamingResponse,
+)
+from ..._types import Body, Query, Headers, NotGiven, not_given
+from ..._utils import path_template
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from .login_flows import (
+ LoginFlowsResource,
+ AsyncLoginFlowsResource,
+ LoginFlowsResourceWithRawResponse,
+ AsyncLoginFlowsResourceWithRawResponse,
+ LoginFlowsResourceWithStreamingResponse,
+ AsyncLoginFlowsResourceWithStreamingResponse,
+)
+from ..._base_client import make_request_options
+from ...types.bridge_list_response import BridgeListResponse
+from .login_sessions.login_sessions import (
+ LoginSessionsResource,
+ AsyncLoginSessionsResource,
+ LoginSessionsResourceWithRawResponse,
+ AsyncLoginSessionsResourceWithRawResponse,
+ LoginSessionsResourceWithStreamingResponse,
+ AsyncLoginSessionsResourceWithStreamingResponse,
+)
+from ...types.bridge_retrieve_response import BridgeRetrieveResponse
+from ...types.provisioning_capabilities import ProvisioningCapabilities
+
+__all__ = ["BridgesResource", "AsyncBridgesResource"]
+
+
+class BridgesResource(SyncAPIResource):
+ """Manage available bridges, connect or reconnect chat accounts"""
+
+ @cached_property
+ def login_flows(self) -> LoginFlowsResource:
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+ return LoginFlowsResource(self._client)
+
+ @cached_property
+ def logins(self) -> LoginsResource:
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+ return LoginsResource(self._client)
+
+ @cached_property
+ def login_sessions(self) -> LoginSessionsResource:
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+ return LoginSessionsResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> BridgesResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#accessing-raw-response-data-eg-headers
+ """
+ return BridgesResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> BridgesResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#with_streaming_response
+ """
+ return BridgesResourceWithStreamingResponse(self)
+
+ def retrieve(
+ self,
+ bridge_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> BridgeRetrieveResponse:
+ """
+ Get one bridge, including the chat accounts connected through it.
+
+ Args:
+ bridge_id: Bridge ID.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not bridge_id:
+ raise ValueError(f"Expected a non-empty value for `bridge_id` but received {bridge_id!r}")
+ return self._get(
+ path_template("/v1/bridges/{bridge_id}", bridge_id=bridge_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=BridgeRetrieveResponse,
+ )
+
+ def list(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> BridgeListResponse:
+ """List available bridges.
+
+ A bridge is a chat-network connector that can connect or
+ reconnect chat accounts. Connected accounts use the same Account schema as GET
+ /v1/accounts.
+ """
+ return self._get(
+ "/v1/bridges",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=BridgeListResponse,
+ )
+
+ def retrieve_capabilities(
+ self,
+ bridge_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ProvisioningCapabilities:
+ """Get advanced network capabilities for a bridge.
+
+ This endpoint is intended for
+ clients that build custom connect or chat-creation flows.
+
+ Args:
+ bridge_id: Bridge ID.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not bridge_id:
+ raise ValueError(f"Expected a non-empty value for `bridge_id` but received {bridge_id!r}")
+ return self._get(
+ path_template("/v1/bridges/{bridge_id}/capabilities", bridge_id=bridge_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ProvisioningCapabilities,
+ )
+
+
+class AsyncBridgesResource(AsyncAPIResource):
+ """Manage available bridges, connect or reconnect chat accounts"""
+
+ @cached_property
+ def login_flows(self) -> AsyncLoginFlowsResource:
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+ return AsyncLoginFlowsResource(self._client)
+
+ @cached_property
+ def logins(self) -> AsyncLoginsResource:
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+ return AsyncLoginsResource(self._client)
+
+ @cached_property
+ def login_sessions(self) -> AsyncLoginSessionsResource:
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+ return AsyncLoginSessionsResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncBridgesResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncBridgesResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncBridgesResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#with_streaming_response
+ """
+ return AsyncBridgesResourceWithStreamingResponse(self)
+
+ async def retrieve(
+ self,
+ bridge_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> BridgeRetrieveResponse:
+ """
+ Get one bridge, including the chat accounts connected through it.
+
+ Args:
+ bridge_id: Bridge ID.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not bridge_id:
+ raise ValueError(f"Expected a non-empty value for `bridge_id` but received {bridge_id!r}")
+ return await self._get(
+ path_template("/v1/bridges/{bridge_id}", bridge_id=bridge_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=BridgeRetrieveResponse,
+ )
+
+ async def list(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> BridgeListResponse:
+ """List available bridges.
+
+ A bridge is a chat-network connector that can connect or
+ reconnect chat accounts. Connected accounts use the same Account schema as GET
+ /v1/accounts.
+ """
+ return await self._get(
+ "/v1/bridges",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=BridgeListResponse,
+ )
+
+ async def retrieve_capabilities(
+ self,
+ bridge_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ProvisioningCapabilities:
+ """Get advanced network capabilities for a bridge.
+
+ This endpoint is intended for
+ clients that build custom connect or chat-creation flows.
+
+ Args:
+ bridge_id: Bridge ID.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not bridge_id:
+ raise ValueError(f"Expected a non-empty value for `bridge_id` but received {bridge_id!r}")
+ return await self._get(
+ path_template("/v1/bridges/{bridge_id}/capabilities", bridge_id=bridge_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ProvisioningCapabilities,
+ )
+
+
+class BridgesResourceWithRawResponse:
+ def __init__(self, bridges: BridgesResource) -> None:
+ self._bridges = bridges
+
+ self.retrieve = to_raw_response_wrapper(
+ bridges.retrieve,
+ )
+ self.list = to_raw_response_wrapper(
+ bridges.list,
+ )
+ self.retrieve_capabilities = to_raw_response_wrapper(
+ bridges.retrieve_capabilities,
+ )
+
+ @cached_property
+ def login_flows(self) -> LoginFlowsResourceWithRawResponse:
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+ return LoginFlowsResourceWithRawResponse(self._bridges.login_flows)
+
+ @cached_property
+ def logins(self) -> LoginsResourceWithRawResponse:
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+ return LoginsResourceWithRawResponse(self._bridges.logins)
+
+ @cached_property
+ def login_sessions(self) -> LoginSessionsResourceWithRawResponse:
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+ return LoginSessionsResourceWithRawResponse(self._bridges.login_sessions)
+
+
+class AsyncBridgesResourceWithRawResponse:
+ def __init__(self, bridges: AsyncBridgesResource) -> None:
+ self._bridges = bridges
+
+ self.retrieve = async_to_raw_response_wrapper(
+ bridges.retrieve,
+ )
+ self.list = async_to_raw_response_wrapper(
+ bridges.list,
+ )
+ self.retrieve_capabilities = async_to_raw_response_wrapper(
+ bridges.retrieve_capabilities,
+ )
+
+ @cached_property
+ def login_flows(self) -> AsyncLoginFlowsResourceWithRawResponse:
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+ return AsyncLoginFlowsResourceWithRawResponse(self._bridges.login_flows)
+
+ @cached_property
+ def logins(self) -> AsyncLoginsResourceWithRawResponse:
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+ return AsyncLoginsResourceWithRawResponse(self._bridges.logins)
+
+ @cached_property
+ def login_sessions(self) -> AsyncLoginSessionsResourceWithRawResponse:
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+ return AsyncLoginSessionsResourceWithRawResponse(self._bridges.login_sessions)
+
+
+class BridgesResourceWithStreamingResponse:
+ def __init__(self, bridges: BridgesResource) -> None:
+ self._bridges = bridges
+
+ self.retrieve = to_streamed_response_wrapper(
+ bridges.retrieve,
+ )
+ self.list = to_streamed_response_wrapper(
+ bridges.list,
+ )
+ self.retrieve_capabilities = to_streamed_response_wrapper(
+ bridges.retrieve_capabilities,
+ )
+
+ @cached_property
+ def login_flows(self) -> LoginFlowsResourceWithStreamingResponse:
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+ return LoginFlowsResourceWithStreamingResponse(self._bridges.login_flows)
+
+ @cached_property
+ def logins(self) -> LoginsResourceWithStreamingResponse:
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+ return LoginsResourceWithStreamingResponse(self._bridges.logins)
+
+ @cached_property
+ def login_sessions(self) -> LoginSessionsResourceWithStreamingResponse:
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+ return LoginSessionsResourceWithStreamingResponse(self._bridges.login_sessions)
+
+
+class AsyncBridgesResourceWithStreamingResponse:
+ def __init__(self, bridges: AsyncBridgesResource) -> None:
+ self._bridges = bridges
+
+ self.retrieve = async_to_streamed_response_wrapper(
+ bridges.retrieve,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ bridges.list,
+ )
+ self.retrieve_capabilities = async_to_streamed_response_wrapper(
+ bridges.retrieve_capabilities,
+ )
+
+ @cached_property
+ def login_flows(self) -> AsyncLoginFlowsResourceWithStreamingResponse:
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+ return AsyncLoginFlowsResourceWithStreamingResponse(self._bridges.login_flows)
+
+ @cached_property
+ def logins(self) -> AsyncLoginsResourceWithStreamingResponse:
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+ return AsyncLoginsResourceWithStreamingResponse(self._bridges.logins)
+
+ @cached_property
+ def login_sessions(self) -> AsyncLoginSessionsResourceWithStreamingResponse:
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+ return AsyncLoginSessionsResourceWithStreamingResponse(self._bridges.login_sessions)
diff --git a/src/beeper_desktop_api/resources/bridges/login_flows.py b/src/beeper_desktop_api/resources/bridges/login_flows.py
new file mode 100644
index 0000000..bc14e73
--- /dev/null
+++ b/src/beeper_desktop_api/resources/bridges/login_flows.py
@@ -0,0 +1,180 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..._types import Body, Query, Headers, NotGiven, not_given
+from ..._utils import path_template
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.bridges.login_flow_list_response import LoginFlowListResponse
+
+__all__ = ["LoginFlowsResource", "AsyncLoginFlowsResource"]
+
+
+class LoginFlowsResource(SyncAPIResource):
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> LoginFlowsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#accessing-raw-response-data-eg-headers
+ """
+ return LoginFlowsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> LoginFlowsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#with_streaming_response
+ """
+ return LoginFlowsResourceWithStreamingResponse(self)
+
+ def list(
+ self,
+ bridge_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> LoginFlowListResponse:
+ """List connect and reconnect flow options for a bridge.
+
+ Use a flowID when creating
+ a bridge login session.
+
+ Args:
+ bridge_id: Bridge ID.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not bridge_id:
+ raise ValueError(f"Expected a non-empty value for `bridge_id` but received {bridge_id!r}")
+ return self._get(
+ path_template("/v1/bridges/{bridge_id}/login-flows", bridge_id=bridge_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=LoginFlowListResponse,
+ )
+
+
+class AsyncLoginFlowsResource(AsyncAPIResource):
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> AsyncLoginFlowsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncLoginFlowsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncLoginFlowsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#with_streaming_response
+ """
+ return AsyncLoginFlowsResourceWithStreamingResponse(self)
+
+ async def list(
+ self,
+ bridge_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> LoginFlowListResponse:
+ """List connect and reconnect flow options for a bridge.
+
+ Use a flowID when creating
+ a bridge login session.
+
+ Args:
+ bridge_id: Bridge ID.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not bridge_id:
+ raise ValueError(f"Expected a non-empty value for `bridge_id` but received {bridge_id!r}")
+ return await self._get(
+ path_template("/v1/bridges/{bridge_id}/login-flows", bridge_id=bridge_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=LoginFlowListResponse,
+ )
+
+
+class LoginFlowsResourceWithRawResponse:
+ def __init__(self, login_flows: LoginFlowsResource) -> None:
+ self._login_flows = login_flows
+
+ self.list = to_raw_response_wrapper(
+ login_flows.list,
+ )
+
+
+class AsyncLoginFlowsResourceWithRawResponse:
+ def __init__(self, login_flows: AsyncLoginFlowsResource) -> None:
+ self._login_flows = login_flows
+
+ self.list = async_to_raw_response_wrapper(
+ login_flows.list,
+ )
+
+
+class LoginFlowsResourceWithStreamingResponse:
+ def __init__(self, login_flows: LoginFlowsResource) -> None:
+ self._login_flows = login_flows
+
+ self.list = to_streamed_response_wrapper(
+ login_flows.list,
+ )
+
+
+class AsyncLoginFlowsResourceWithStreamingResponse:
+ def __init__(self, login_flows: AsyncLoginFlowsResource) -> None:
+ self._login_flows = login_flows
+
+ self.list = async_to_streamed_response_wrapper(
+ login_flows.list,
+ )
diff --git a/src/beeper_desktop_api/resources/bridges/login_sessions/__init__.py b/src/beeper_desktop_api/resources/bridges/login_sessions/__init__.py
new file mode 100644
index 0000000..18becaa
--- /dev/null
+++ b/src/beeper_desktop_api/resources/bridges/login_sessions/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .steps import (
+ StepsResource,
+ AsyncStepsResource,
+ StepsResourceWithRawResponse,
+ AsyncStepsResourceWithRawResponse,
+ StepsResourceWithStreamingResponse,
+ AsyncStepsResourceWithStreamingResponse,
+)
+from .login_sessions import (
+ LoginSessionsResource,
+ AsyncLoginSessionsResource,
+ LoginSessionsResourceWithRawResponse,
+ AsyncLoginSessionsResourceWithRawResponse,
+ LoginSessionsResourceWithStreamingResponse,
+ AsyncLoginSessionsResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "StepsResource",
+ "AsyncStepsResource",
+ "StepsResourceWithRawResponse",
+ "AsyncStepsResourceWithRawResponse",
+ "StepsResourceWithStreamingResponse",
+ "AsyncStepsResourceWithStreamingResponse",
+ "LoginSessionsResource",
+ "AsyncLoginSessionsResource",
+ "LoginSessionsResourceWithRawResponse",
+ "AsyncLoginSessionsResourceWithRawResponse",
+ "LoginSessionsResourceWithStreamingResponse",
+ "AsyncLoginSessionsResourceWithStreamingResponse",
+]
diff --git a/src/beeper_desktop_api/resources/bridges/login_sessions/login_sessions.py b/src/beeper_desktop_api/resources/bridges/login_sessions/login_sessions.py
new file mode 100644
index 0000000..5fd6051
--- /dev/null
+++ b/src/beeper_desktop_api/resources/bridges/login_sessions/login_sessions.py
@@ -0,0 +1,468 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from .steps import (
+ StepsResource,
+ AsyncStepsResource,
+ StepsResourceWithRawResponse,
+ AsyncStepsResourceWithRawResponse,
+ StepsResourceWithStreamingResponse,
+ AsyncStepsResourceWithStreamingResponse,
+)
+from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from ...._utils import path_template, maybe_transform, async_maybe_transform
+from ...._compat import cached_property
+from ...._resource import SyncAPIResource, AsyncAPIResource
+from ...._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ...._base_client import make_request_options
+from ....types.bridges import login_session_create_params
+from ....types.login_session import LoginSession
+from ....types.bridges.login_session_cancel_response import LoginSessionCancelResponse
+
+__all__ = ["LoginSessionsResource", "AsyncLoginSessionsResource"]
+
+
+class LoginSessionsResource(SyncAPIResource):
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+
+ @cached_property
+ def steps(self) -> StepsResource:
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+ return StepsResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> LoginSessionsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#accessing-raw-response-data-eg-headers
+ """
+ return LoginSessionsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> LoginSessionsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#with_streaming_response
+ """
+ return LoginSessionsResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ bridge_id: str,
+ *,
+ account_id: str | Omit = omit,
+ flow_id: str | Omit = omit,
+ login_id: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> LoginSession:
+ """
+ Start a temporary bridge login session to connect a new chat account or
+ reconnect an existing bridge login. Omit loginID and accountID to connect a new
+ account.
+
+ Args:
+ bridge_id: Bridge ID.
+
+ account_id: Existing chat account ID to reconnect. Omit to connect a new account.
+
+ flow_id: Optional flow ID returned by the list login flows endpoint. If omitted, Beeper
+ chooses the default flow.
+
+ login_id: Existing bridge login ID to reconnect. Omit to connect a new account.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not bridge_id:
+ raise ValueError(f"Expected a non-empty value for `bridge_id` but received {bridge_id!r}")
+ return self._post(
+ path_template("/v1/bridges/{bridge_id}/login-sessions", bridge_id=bridge_id),
+ body=maybe_transform(
+ {
+ "account_id": account_id,
+ "flow_id": flow_id,
+ "login_id": login_id,
+ },
+ login_session_create_params.LoginSessionCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=LoginSession,
+ )
+
+ def retrieve(
+ self,
+ login_session_id: str,
+ *,
+ bridge_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> LoginSession:
+ """
+ Get the current state of a temporary bridge login session.
+
+ Args:
+ bridge_id: Bridge ID.
+
+ login_session_id: Temporary bridge login session ID.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not bridge_id:
+ raise ValueError(f"Expected a non-empty value for `bridge_id` but received {bridge_id!r}")
+ if not login_session_id:
+ raise ValueError(f"Expected a non-empty value for `login_session_id` but received {login_session_id!r}")
+ return self._get(
+ path_template(
+ "/v1/bridges/{bridge_id}/login-sessions/{login_session_id}",
+ bridge_id=bridge_id,
+ login_session_id=login_session_id,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=LoginSession,
+ )
+
+ def cancel(
+ self,
+ login_session_id: str,
+ *,
+ bridge_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> LoginSessionCancelResponse:
+ """
+ Cancel a temporary bridge login session.
+
+ Args:
+ bridge_id: Bridge ID.
+
+ login_session_id: Temporary bridge login session ID.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not bridge_id:
+ raise ValueError(f"Expected a non-empty value for `bridge_id` but received {bridge_id!r}")
+ if not login_session_id:
+ raise ValueError(f"Expected a non-empty value for `login_session_id` but received {login_session_id!r}")
+ return self._delete(
+ path_template(
+ "/v1/bridges/{bridge_id}/login-sessions/{login_session_id}",
+ bridge_id=bridge_id,
+ login_session_id=login_session_id,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=LoginSessionCancelResponse,
+ )
+
+
+class AsyncLoginSessionsResource(AsyncAPIResource):
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+
+ @cached_property
+ def steps(self) -> AsyncStepsResource:
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+ return AsyncStepsResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncLoginSessionsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncLoginSessionsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncLoginSessionsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#with_streaming_response
+ """
+ return AsyncLoginSessionsResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ bridge_id: str,
+ *,
+ account_id: str | Omit = omit,
+ flow_id: str | Omit = omit,
+ login_id: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> LoginSession:
+ """
+ Start a temporary bridge login session to connect a new chat account or
+ reconnect an existing bridge login. Omit loginID and accountID to connect a new
+ account.
+
+ Args:
+ bridge_id: Bridge ID.
+
+ account_id: Existing chat account ID to reconnect. Omit to connect a new account.
+
+ flow_id: Optional flow ID returned by the list login flows endpoint. If omitted, Beeper
+ chooses the default flow.
+
+ login_id: Existing bridge login ID to reconnect. Omit to connect a new account.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not bridge_id:
+ raise ValueError(f"Expected a non-empty value for `bridge_id` but received {bridge_id!r}")
+ return await self._post(
+ path_template("/v1/bridges/{bridge_id}/login-sessions", bridge_id=bridge_id),
+ body=await async_maybe_transform(
+ {
+ "account_id": account_id,
+ "flow_id": flow_id,
+ "login_id": login_id,
+ },
+ login_session_create_params.LoginSessionCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=LoginSession,
+ )
+
+ async def retrieve(
+ self,
+ login_session_id: str,
+ *,
+ bridge_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> LoginSession:
+ """
+ Get the current state of a temporary bridge login session.
+
+ Args:
+ bridge_id: Bridge ID.
+
+ login_session_id: Temporary bridge login session ID.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not bridge_id:
+ raise ValueError(f"Expected a non-empty value for `bridge_id` but received {bridge_id!r}")
+ if not login_session_id:
+ raise ValueError(f"Expected a non-empty value for `login_session_id` but received {login_session_id!r}")
+ return await self._get(
+ path_template(
+ "/v1/bridges/{bridge_id}/login-sessions/{login_session_id}",
+ bridge_id=bridge_id,
+ login_session_id=login_session_id,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=LoginSession,
+ )
+
+ async def cancel(
+ self,
+ login_session_id: str,
+ *,
+ bridge_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> LoginSessionCancelResponse:
+ """
+ Cancel a temporary bridge login session.
+
+ Args:
+ bridge_id: Bridge ID.
+
+ login_session_id: Temporary bridge login session ID.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not bridge_id:
+ raise ValueError(f"Expected a non-empty value for `bridge_id` but received {bridge_id!r}")
+ if not login_session_id:
+ raise ValueError(f"Expected a non-empty value for `login_session_id` but received {login_session_id!r}")
+ return await self._delete(
+ path_template(
+ "/v1/bridges/{bridge_id}/login-sessions/{login_session_id}",
+ bridge_id=bridge_id,
+ login_session_id=login_session_id,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=LoginSessionCancelResponse,
+ )
+
+
+class LoginSessionsResourceWithRawResponse:
+ def __init__(self, login_sessions: LoginSessionsResource) -> None:
+ self._login_sessions = login_sessions
+
+ self.create = to_raw_response_wrapper(
+ login_sessions.create,
+ )
+ self.retrieve = to_raw_response_wrapper(
+ login_sessions.retrieve,
+ )
+ self.cancel = to_raw_response_wrapper(
+ login_sessions.cancel,
+ )
+
+ @cached_property
+ def steps(self) -> StepsResourceWithRawResponse:
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+ return StepsResourceWithRawResponse(self._login_sessions.steps)
+
+
+class AsyncLoginSessionsResourceWithRawResponse:
+ def __init__(self, login_sessions: AsyncLoginSessionsResource) -> None:
+ self._login_sessions = login_sessions
+
+ self.create = async_to_raw_response_wrapper(
+ login_sessions.create,
+ )
+ self.retrieve = async_to_raw_response_wrapper(
+ login_sessions.retrieve,
+ )
+ self.cancel = async_to_raw_response_wrapper(
+ login_sessions.cancel,
+ )
+
+ @cached_property
+ def steps(self) -> AsyncStepsResourceWithRawResponse:
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+ return AsyncStepsResourceWithRawResponse(self._login_sessions.steps)
+
+
+class LoginSessionsResourceWithStreamingResponse:
+ def __init__(self, login_sessions: LoginSessionsResource) -> None:
+ self._login_sessions = login_sessions
+
+ self.create = to_streamed_response_wrapper(
+ login_sessions.create,
+ )
+ self.retrieve = to_streamed_response_wrapper(
+ login_sessions.retrieve,
+ )
+ self.cancel = to_streamed_response_wrapper(
+ login_sessions.cancel,
+ )
+
+ @cached_property
+ def steps(self) -> StepsResourceWithStreamingResponse:
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+ return StepsResourceWithStreamingResponse(self._login_sessions.steps)
+
+
+class AsyncLoginSessionsResourceWithStreamingResponse:
+ def __init__(self, login_sessions: AsyncLoginSessionsResource) -> None:
+ self._login_sessions = login_sessions
+
+ self.create = async_to_streamed_response_wrapper(
+ login_sessions.create,
+ )
+ self.retrieve = async_to_streamed_response_wrapper(
+ login_sessions.retrieve,
+ )
+ self.cancel = async_to_streamed_response_wrapper(
+ login_sessions.cancel,
+ )
+
+ @cached_property
+ def steps(self) -> AsyncStepsResourceWithStreamingResponse:
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+ return AsyncStepsResourceWithStreamingResponse(self._login_sessions.steps)
diff --git a/src/beeper_desktop_api/resources/bridges/login_sessions/steps.py b/src/beeper_desktop_api/resources/bridges/login_sessions/steps.py
new file mode 100644
index 0000000..6bc590e
--- /dev/null
+++ b/src/beeper_desktop_api/resources/bridges/login_sessions/steps.py
@@ -0,0 +1,250 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict
+from typing_extensions import Literal
+
+import httpx
+
+from ...._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from ...._utils import path_template, maybe_transform, async_maybe_transform
+from ...._compat import cached_property
+from ...._resource import SyncAPIResource, AsyncAPIResource
+from ...._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ...._base_client import make_request_options
+from ....types.login_session import LoginSession
+from ....types.bridges.login_sessions import step_submit_params
+
+__all__ = ["StepsResource", "AsyncStepsResource"]
+
+
+class StepsResource(SyncAPIResource):
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> StepsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#accessing-raw-response-data-eg-headers
+ """
+ return StepsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> StepsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#with_streaming_response
+ """
+ return StepsResourceWithStreamingResponse(self)
+
+ def submit(
+ self,
+ step_id: str,
+ *,
+ bridge_id: str,
+ login_session_id: str,
+ type: Literal["user_input", "cookies", "display_and_wait"],
+ fields: Dict[str, str] | Omit = omit,
+ last_url: str | Omit = omit,
+ source: Literal["api", "webview", "browser_extension"] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> LoginSession:
+ """
+ Submit input for the current step of a bridge login session.
+
+ Args:
+ bridge_id: Bridge ID.
+
+ login_session_id: Temporary bridge login session ID.
+
+ step_id: Current bridge login session step ID.
+
+ fields: Field values keyed by the field IDs from the current step.
+
+ last_url: Last browser URL reached during a cookies step, if available.
+
+ source: How the step was completed. Omit unless the client needs to distinguish an
+ embedded webview or browser extension.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not bridge_id:
+ raise ValueError(f"Expected a non-empty value for `bridge_id` but received {bridge_id!r}")
+ if not login_session_id:
+ raise ValueError(f"Expected a non-empty value for `login_session_id` but received {login_session_id!r}")
+ if not step_id:
+ raise ValueError(f"Expected a non-empty value for `step_id` but received {step_id!r}")
+ return self._post(
+ path_template(
+ "/v1/bridges/{bridge_id}/login-sessions/{login_session_id}/steps/{step_id}",
+ bridge_id=bridge_id,
+ login_session_id=login_session_id,
+ step_id=step_id,
+ ),
+ body=maybe_transform(
+ {
+ "type": type,
+ "fields": fields,
+ "last_url": last_url,
+ "source": source,
+ },
+ step_submit_params.StepSubmitParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=LoginSession,
+ )
+
+
+class AsyncStepsResource(AsyncAPIResource):
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> AsyncStepsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncStepsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncStepsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#with_streaming_response
+ """
+ return AsyncStepsResourceWithStreamingResponse(self)
+
+ async def submit(
+ self,
+ step_id: str,
+ *,
+ bridge_id: str,
+ login_session_id: str,
+ type: Literal["user_input", "cookies", "display_and_wait"],
+ fields: Dict[str, str] | Omit = omit,
+ last_url: str | Omit = omit,
+ source: Literal["api", "webview", "browser_extension"] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> LoginSession:
+ """
+ Submit input for the current step of a bridge login session.
+
+ Args:
+ bridge_id: Bridge ID.
+
+ login_session_id: Temporary bridge login session ID.
+
+ step_id: Current bridge login session step ID.
+
+ fields: Field values keyed by the field IDs from the current step.
+
+ last_url: Last browser URL reached during a cookies step, if available.
+
+ source: How the step was completed. Omit unless the client needs to distinguish an
+ embedded webview or browser extension.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not bridge_id:
+ raise ValueError(f"Expected a non-empty value for `bridge_id` but received {bridge_id!r}")
+ if not login_session_id:
+ raise ValueError(f"Expected a non-empty value for `login_session_id` but received {login_session_id!r}")
+ if not step_id:
+ raise ValueError(f"Expected a non-empty value for `step_id` but received {step_id!r}")
+ return await self._post(
+ path_template(
+ "/v1/bridges/{bridge_id}/login-sessions/{login_session_id}/steps/{step_id}",
+ bridge_id=bridge_id,
+ login_session_id=login_session_id,
+ step_id=step_id,
+ ),
+ body=await async_maybe_transform(
+ {
+ "type": type,
+ "fields": fields,
+ "last_url": last_url,
+ "source": source,
+ },
+ step_submit_params.StepSubmitParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=LoginSession,
+ )
+
+
+class StepsResourceWithRawResponse:
+ def __init__(self, steps: StepsResource) -> None:
+ self._steps = steps
+
+ self.submit = to_raw_response_wrapper(
+ steps.submit,
+ )
+
+
+class AsyncStepsResourceWithRawResponse:
+ def __init__(self, steps: AsyncStepsResource) -> None:
+ self._steps = steps
+
+ self.submit = async_to_raw_response_wrapper(
+ steps.submit,
+ )
+
+
+class StepsResourceWithStreamingResponse:
+ def __init__(self, steps: StepsResource) -> None:
+ self._steps = steps
+
+ self.submit = to_streamed_response_wrapper(
+ steps.submit,
+ )
+
+
+class AsyncStepsResourceWithStreamingResponse:
+ def __init__(self, steps: AsyncStepsResource) -> None:
+ self._steps = steps
+
+ self.submit = async_to_streamed_response_wrapper(
+ steps.submit,
+ )
diff --git a/src/beeper_desktop_api/resources/bridges/logins.py b/src/beeper_desktop_api/resources/bridges/logins.py
new file mode 100644
index 0000000..f255b94
--- /dev/null
+++ b/src/beeper_desktop_api/resources/bridges/logins.py
@@ -0,0 +1,379 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal
+
+import httpx
+
+from ..._types import Body, Query, Headers, NotGiven, not_given
+from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.bridges import login_remove_params
+from ...types.bridge_login import BridgeLogin
+from ...types.bridges.login_list_response import LoginListResponse
+from ...types.bridges.login_remove_response import LoginRemoveResponse
+
+__all__ = ["LoginsResource", "AsyncLoginsResource"]
+
+
+class LoginsResource(SyncAPIResource):
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> LoginsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#accessing-raw-response-data-eg-headers
+ """
+ return LoginsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> LoginsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#with_streaming_response
+ """
+ return LoginsResourceWithStreamingResponse(self)
+
+ def retrieve(
+ self,
+ login_id: str,
+ *,
+ bridge_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> BridgeLogin:
+ """
+ Get one bridge login.
+
+ Args:
+ bridge_id: Bridge ID.
+
+ login_id: Bridge login ID.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not bridge_id:
+ raise ValueError(f"Expected a non-empty value for `bridge_id` but received {bridge_id!r}")
+ if not login_id:
+ raise ValueError(f"Expected a non-empty value for `login_id` but received {login_id!r}")
+ return self._get(
+ path_template("/v1/bridges/{bridge_id}/logins/{login_id}", bridge_id=bridge_id, login_id=login_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=BridgeLogin,
+ )
+
+ def list(
+ self,
+ bridge_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> LoginListResponse:
+ """List bridge logins.
+
+ A bridge login is a signed-in identity for a bridge and can
+ contain one or more chat accounts.
+
+ Args:
+ bridge_id: Bridge ID.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not bridge_id:
+ raise ValueError(f"Expected a non-empty value for `bridge_id` but received {bridge_id!r}")
+ return self._get(
+ path_template("/v1/bridges/{bridge_id}/logins", bridge_id=bridge_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=LoginListResponse,
+ )
+
+ def remove(
+ self,
+ login_id: str,
+ *,
+ bridge_id: str,
+ scope: Literal["current-device", "all-devices"],
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> LoginRemoveResponse:
+ """
+ Remove a bridge login from this device or, when supported by the bridge, from
+ all devices.
+
+ Args:
+ bridge_id: Bridge ID.
+
+ login_id: Bridge login ID.
+
+ scope: Where this bridge login should be removed.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not bridge_id:
+ raise ValueError(f"Expected a non-empty value for `bridge_id` but received {bridge_id!r}")
+ if not login_id:
+ raise ValueError(f"Expected a non-empty value for `login_id` but received {login_id!r}")
+ return self._post(
+ path_template("/v1/bridges/{bridge_id}/logins/{login_id}/remove", bridge_id=bridge_id, login_id=login_id),
+ body=maybe_transform({"scope": scope}, login_remove_params.LoginRemoveParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=LoginRemoveResponse,
+ )
+
+
+class AsyncLoginsResource(AsyncAPIResource):
+ """
+ Available bridges, bridge logins, login sessions for connect and reconnect flows, and advanced network capabilities.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> AsyncLoginsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncLoginsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncLoginsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#with_streaming_response
+ """
+ return AsyncLoginsResourceWithStreamingResponse(self)
+
+ async def retrieve(
+ self,
+ login_id: str,
+ *,
+ bridge_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> BridgeLogin:
+ """
+ Get one bridge login.
+
+ Args:
+ bridge_id: Bridge ID.
+
+ login_id: Bridge login ID.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not bridge_id:
+ raise ValueError(f"Expected a non-empty value for `bridge_id` but received {bridge_id!r}")
+ if not login_id:
+ raise ValueError(f"Expected a non-empty value for `login_id` but received {login_id!r}")
+ return await self._get(
+ path_template("/v1/bridges/{bridge_id}/logins/{login_id}", bridge_id=bridge_id, login_id=login_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=BridgeLogin,
+ )
+
+ async def list(
+ self,
+ bridge_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> LoginListResponse:
+ """List bridge logins.
+
+ A bridge login is a signed-in identity for a bridge and can
+ contain one or more chat accounts.
+
+ Args:
+ bridge_id: Bridge ID.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not bridge_id:
+ raise ValueError(f"Expected a non-empty value for `bridge_id` but received {bridge_id!r}")
+ return await self._get(
+ path_template("/v1/bridges/{bridge_id}/logins", bridge_id=bridge_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=LoginListResponse,
+ )
+
+ async def remove(
+ self,
+ login_id: str,
+ *,
+ bridge_id: str,
+ scope: Literal["current-device", "all-devices"],
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> LoginRemoveResponse:
+ """
+ Remove a bridge login from this device or, when supported by the bridge, from
+ all devices.
+
+ Args:
+ bridge_id: Bridge ID.
+
+ login_id: Bridge login ID.
+
+ scope: Where this bridge login should be removed.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not bridge_id:
+ raise ValueError(f"Expected a non-empty value for `bridge_id` but received {bridge_id!r}")
+ if not login_id:
+ raise ValueError(f"Expected a non-empty value for `login_id` but received {login_id!r}")
+ return await self._post(
+ path_template("/v1/bridges/{bridge_id}/logins/{login_id}/remove", bridge_id=bridge_id, login_id=login_id),
+ body=await async_maybe_transform({"scope": scope}, login_remove_params.LoginRemoveParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=LoginRemoveResponse,
+ )
+
+
+class LoginsResourceWithRawResponse:
+ def __init__(self, logins: LoginsResource) -> None:
+ self._logins = logins
+
+ self.retrieve = to_raw_response_wrapper(
+ logins.retrieve,
+ )
+ self.list = to_raw_response_wrapper(
+ logins.list,
+ )
+ self.remove = to_raw_response_wrapper(
+ logins.remove,
+ )
+
+
+class AsyncLoginsResourceWithRawResponse:
+ def __init__(self, logins: AsyncLoginsResource) -> None:
+ self._logins = logins
+
+ self.retrieve = async_to_raw_response_wrapper(
+ logins.retrieve,
+ )
+ self.list = async_to_raw_response_wrapper(
+ logins.list,
+ )
+ self.remove = async_to_raw_response_wrapper(
+ logins.remove,
+ )
+
+
+class LoginsResourceWithStreamingResponse:
+ def __init__(self, logins: LoginsResource) -> None:
+ self._logins = logins
+
+ self.retrieve = to_streamed_response_wrapper(
+ logins.retrieve,
+ )
+ self.list = to_streamed_response_wrapper(
+ logins.list,
+ )
+ self.remove = to_streamed_response_wrapper(
+ logins.remove,
+ )
+
+
+class AsyncLoginsResourceWithStreamingResponse:
+ def __init__(self, logins: AsyncLoginsResource) -> None:
+ self._logins = logins
+
+ self.retrieve = async_to_streamed_response_wrapper(
+ logins.retrieve,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ logins.list,
+ )
+ self.remove = async_to_streamed_response_wrapper(
+ logins.remove,
+ )
diff --git a/src/beeper_desktop_api/resources/chats/chats.py b/src/beeper_desktop_api/resources/chats/chats.py
index 38775e0..ebcbc18 100644
--- a/src/beeper_desktop_api/resources/chats/chats.py
+++ b/src/beeper_desktop_api/resources/chats/chats.py
@@ -157,11 +157,11 @@ def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Chat:
"""
- Retrieve chat details including metadata, participants, and latest message
+ Retrieve chat details, including metadata, participants, and the latest message.
Args:
- chat_id: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ chat_id: Chat ID. Input routes also accept the local chat ID from this installation when
+ available.
max_participant_count: Maximum number of participants to return. Use -1 for all; otherwise 0-500.
Defaults to 100. List and search endpoints return up to 20 participants per
@@ -213,13 +213,13 @@ def update(
) -> Chat:
"""Update supported chat fields.
- Non-empty draft objects are accepted only when the
+ Non-empty drafts are accepted only when the
current draft is empty. Send draft=null to clear the draft before setting new
draft text or attachments.
Args:
- chat_id: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ chat_id: Chat ID. Input routes also accept the local chat ID from this installation when
+ available.
description: Group chat description/topic. Support depends on the chat account and chat
permissions.
@@ -281,6 +281,7 @@ def list(
account_ids: SequenceNotStr[str] | Omit = omit,
cursor: str | Omit = omit,
direction: Literal["after", "before"] | Omit = omit,
+ limit: int | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -301,6 +302,8 @@ def list(
direction: Pagination direction used with 'cursor': 'before' fetches older results, 'after'
fetches newer results. Defaults to 'before' when only 'cursor' is provided.
+ limit: Set the maximum number of chats to retrieve. Valid range: 1-200, default is 25
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -322,6 +325,7 @@ def list(
"account_ids": account_ids,
"cursor": cursor,
"direction": direction,
+ "limit": limit,
},
chat_list_params.ChatListParams,
),
@@ -343,12 +347,12 @@ def archive(
) -> None:
"""Archive or unarchive a chat.
- Set archived=true to move to archive,
- archived=false to move back to inbox
+ Set archived=true to move it to Archive, or
+ archived=false to move it back to the inbox.
Args:
- chat_id: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ chat_id: Chat ID. Input routes also accept the local chat ID from this installation when
+ available.
archived: True to archive, false to unarchive
@@ -388,8 +392,8 @@ def mark_read(
Mark a chat as read, optionally through a specific message ID.
Args:
- chat_id: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ chat_id: Chat ID. Input routes also accept the local chat ID from this installation when
+ available.
message_id: Optional message ID to mark read through.
@@ -428,8 +432,8 @@ def mark_unread(
Mark a chat as unread, optionally from a specific message ID.
Args:
- chat_id: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ chat_id: Chat ID. Input routes also accept the local chat ID from this installation when
+ available.
message_id: Optional message ID to mark unread from.
@@ -464,12 +468,13 @@ def notify_anyway(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Chat:
"""
- Force a delivery notification when supported by the underlying network.
- Currently intended for iMessage on macOS; unsupported networks return an error.
+ Send a notification despite the recipient focus state when the network supports
+ it. Currently intended for iMessage on macOS; unsupported networks return an
+ error.
Args:
- chat_id: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ chat_id: Chat ID. Input routes also accept the local chat ID from this installation when
+ available.
extra_headers: Send extra headers
@@ -483,6 +488,7 @@ def notify_anyway(
raise ValueError(f"Expected a non-empty value for `chat_id` but received {chat_id!r}")
return self._post(
path_template("/v1/chats/{chat_id}/notify-anyway", chat_id=chat_id),
+ body={},
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
@@ -497,6 +503,7 @@ def search(
direction: Literal["after", "before"] | Omit = omit,
inbox: Literal["primary", "low-priority", "archive"] | Omit = omit,
include_muted: Optional[bool] | Omit = omit,
+ label_id: str | Omit = omit,
last_activity_after: Union[str, datetime] | Omit = omit,
last_activity_before: Union[str, datetime] | Omit = omit,
limit: int | Omit = omit,
@@ -515,30 +522,30 @@ def search(
Search chats by title, network, or participant names.
Args:
- account_ids: Provide an array of account IDs to filter chats from specific messaging accounts
- only
+ account_ids: Limit results to specific chat accounts.
cursor: Opaque pagination cursor; do not inspect. Use together with 'direction'.
direction: Pagination direction used with 'cursor': 'before' fetches older results, 'after'
fetches newer results. Defaults to 'before' when only 'cursor' is provided.
- inbox: Filter by inbox type: "primary" (non-archived, non-low-priority),
- "low-priority", or "archive". If not specified, shows all chats.
+ inbox: Filter by inbox type: "primary" (the chats the Beeper inbox shows: non-archived,
+ non-low-priority, honoring inbox visibility rules and labels), "low-priority",
+ or "archive". If not specified, shows all chats.
include_muted: Include chats marked as Muted by the user, which are usually less important.
Default: true. Set to false if the user wants a more refined search.
- last_activity_after: Provide an ISO datetime string to only retrieve chats with last activity after
- this time
+ label_id: Only include chats that carry this label. Label IDs come from GET /v1/labels.
+
+ last_activity_after: Only include chats with last activity after this ISO 8601 datetime.
- last_activity_before: Provide an ISO datetime string to only retrieve chats with last activity before
- this time
+ last_activity_before: Only include chats with last activity before this ISO 8601 datetime.
limit: Set the maximum number of chats to retrieve. Valid range: 1-200, default is 50
- query: Literal token search (non-semantic). Use single words users type (e.g.,
- "dinner"). When multiple words provided, ALL must match. Case-insensitive.
+ query: Literal chat search. Use words the user typed, such as "dinner". When multiple
+ words are provided, all must match. Case-insensitive.
scope: Search scope: 'titles' matches title + network; 'participants' matches
participant names.
@@ -571,6 +578,7 @@ def search(
"direction": direction,
"inbox": inbox,
"include_muted": include_muted,
+ "label_id": label_id,
"last_activity_after": last_activity_after,
"last_activity_before": last_activity_before,
"limit": limit,
@@ -602,12 +610,12 @@ def start(
"""Resolve a user/contact and open a direct chat.
Reuses and returns an existing
- direct chat when one is found. Available in Beeper Desktop v4.2.808+.
+ direct chat when one is found. Available in Beeper v4.2.808+.
Args:
account_id: Account to create or start the chat on.
- user: Merged user-like contact payload used to resolve the best identifier.
+ user: Contact-like user payload used to resolve the best identifier.
allow_invite: Whether invite-based DM creation is allowed when required by the platform.
@@ -741,11 +749,11 @@ async def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Chat:
"""
- Retrieve chat details including metadata, participants, and latest message
+ Retrieve chat details, including metadata, participants, and the latest message.
Args:
- chat_id: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ chat_id: Chat ID. Input routes also accept the local chat ID from this installation when
+ available.
max_participant_count: Maximum number of participants to return. Use -1 for all; otherwise 0-500.
Defaults to 100. List and search endpoints return up to 20 participants per
@@ -797,13 +805,13 @@ async def update(
) -> Chat:
"""Update supported chat fields.
- Non-empty draft objects are accepted only when the
+ Non-empty drafts are accepted only when the
current draft is empty. Send draft=null to clear the draft before setting new
draft text or attachments.
Args:
- chat_id: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ chat_id: Chat ID. Input routes also accept the local chat ID from this installation when
+ available.
description: Group chat description/topic. Support depends on the chat account and chat
permissions.
@@ -865,6 +873,7 @@ def list(
account_ids: SequenceNotStr[str] | Omit = omit,
cursor: str | Omit = omit,
direction: Literal["after", "before"] | Omit = omit,
+ limit: int | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -885,6 +894,8 @@ def list(
direction: Pagination direction used with 'cursor': 'before' fetches older results, 'after'
fetches newer results. Defaults to 'before' when only 'cursor' is provided.
+ limit: Set the maximum number of chats to retrieve. Valid range: 1-200, default is 25
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -906,6 +917,7 @@ def list(
"account_ids": account_ids,
"cursor": cursor,
"direction": direction,
+ "limit": limit,
},
chat_list_params.ChatListParams,
),
@@ -927,12 +939,12 @@ async def archive(
) -> None:
"""Archive or unarchive a chat.
- Set archived=true to move to archive,
- archived=false to move back to inbox
+ Set archived=true to move it to Archive, or
+ archived=false to move it back to the inbox.
Args:
- chat_id: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ chat_id: Chat ID. Input routes also accept the local chat ID from this installation when
+ available.
archived: True to archive, false to unarchive
@@ -972,8 +984,8 @@ async def mark_read(
Mark a chat as read, optionally through a specific message ID.
Args:
- chat_id: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ chat_id: Chat ID. Input routes also accept the local chat ID from this installation when
+ available.
message_id: Optional message ID to mark read through.
@@ -1012,8 +1024,8 @@ async def mark_unread(
Mark a chat as unread, optionally from a specific message ID.
Args:
- chat_id: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ chat_id: Chat ID. Input routes also accept the local chat ID from this installation when
+ available.
message_id: Optional message ID to mark unread from.
@@ -1048,12 +1060,13 @@ async def notify_anyway(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Chat:
"""
- Force a delivery notification when supported by the underlying network.
- Currently intended for iMessage on macOS; unsupported networks return an error.
+ Send a notification despite the recipient focus state when the network supports
+ it. Currently intended for iMessage on macOS; unsupported networks return an
+ error.
Args:
- chat_id: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ chat_id: Chat ID. Input routes also accept the local chat ID from this installation when
+ available.
extra_headers: Send extra headers
@@ -1067,6 +1080,7 @@ async def notify_anyway(
raise ValueError(f"Expected a non-empty value for `chat_id` but received {chat_id!r}")
return await self._post(
path_template("/v1/chats/{chat_id}/notify-anyway", chat_id=chat_id),
+ body={},
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
@@ -1081,6 +1095,7 @@ def search(
direction: Literal["after", "before"] | Omit = omit,
inbox: Literal["primary", "low-priority", "archive"] | Omit = omit,
include_muted: Optional[bool] | Omit = omit,
+ label_id: str | Omit = omit,
last_activity_after: Union[str, datetime] | Omit = omit,
last_activity_before: Union[str, datetime] | Omit = omit,
limit: int | Omit = omit,
@@ -1099,30 +1114,30 @@ def search(
Search chats by title, network, or participant names.
Args:
- account_ids: Provide an array of account IDs to filter chats from specific messaging accounts
- only
+ account_ids: Limit results to specific chat accounts.
cursor: Opaque pagination cursor; do not inspect. Use together with 'direction'.
direction: Pagination direction used with 'cursor': 'before' fetches older results, 'after'
fetches newer results. Defaults to 'before' when only 'cursor' is provided.
- inbox: Filter by inbox type: "primary" (non-archived, non-low-priority),
- "low-priority", or "archive". If not specified, shows all chats.
+ inbox: Filter by inbox type: "primary" (the chats the Beeper inbox shows: non-archived,
+ non-low-priority, honoring inbox visibility rules and labels), "low-priority",
+ or "archive". If not specified, shows all chats.
include_muted: Include chats marked as Muted by the user, which are usually less important.
Default: true. Set to false if the user wants a more refined search.
- last_activity_after: Provide an ISO datetime string to only retrieve chats with last activity after
- this time
+ label_id: Only include chats that carry this label. Label IDs come from GET /v1/labels.
+
+ last_activity_after: Only include chats with last activity after this ISO 8601 datetime.
- last_activity_before: Provide an ISO datetime string to only retrieve chats with last activity before
- this time
+ last_activity_before: Only include chats with last activity before this ISO 8601 datetime.
limit: Set the maximum number of chats to retrieve. Valid range: 1-200, default is 50
- query: Literal token search (non-semantic). Use single words users type (e.g.,
- "dinner"). When multiple words provided, ALL must match. Case-insensitive.
+ query: Literal chat search. Use words the user typed, such as "dinner". When multiple
+ words are provided, all must match. Case-insensitive.
scope: Search scope: 'titles' matches title + network; 'participants' matches
participant names.
@@ -1155,6 +1170,7 @@ def search(
"direction": direction,
"inbox": inbox,
"include_muted": include_muted,
+ "label_id": label_id,
"last_activity_after": last_activity_after,
"last_activity_before": last_activity_before,
"limit": limit,
@@ -1186,12 +1202,12 @@ async def start(
"""Resolve a user/contact and open a direct chat.
Reuses and returns an existing
- direct chat when one is found. Available in Beeper Desktop v4.2.808+.
+ direct chat when one is found. Available in Beeper v4.2.808+.
Args:
account_id: Account to create or start the chat on.
- user: Merged user-like contact payload used to resolve the best identifier.
+ user: Contact-like user payload used to resolve the best identifier.
allow_invite: Whether invite-based DM creation is allowed when required by the platform.
diff --git a/src/beeper_desktop_api/resources/chats/messages/reactions.py b/src/beeper_desktop_api/resources/chats/messages/reactions.py
index 974d9f6..c9199da 100644
--- a/src/beeper_desktop_api/resources/chats/messages/reactions.py
+++ b/src/beeper_desktop_api/resources/chats/messages/reactions.py
@@ -61,8 +61,8 @@ def delete(
Remove the reaction added by the authenticated user from an existing message.
Args:
- chat_id: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ chat_id: Chat ID. Input routes also accept the local chat ID from this installation when
+ available.
message_id: Message ID.
@@ -114,8 +114,8 @@ def add(
Args:
chat_id: Chat ID.
- Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ Input routes also accept the local chat ID from this installation when
+ available.
message_id: Message ID.
@@ -192,8 +192,8 @@ async def delete(
Remove the reaction added by the authenticated user from an existing message.
Args:
- chat_id: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ chat_id: Chat ID. Input routes also accept the local chat ID from this installation when
+ available.
message_id: Message ID.
@@ -245,8 +245,8 @@ async def add(
Args:
chat_id: Chat ID.
- Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ Input routes also accept the local chat ID from this installation when
+ available.
message_id: Message ID.
diff --git a/src/beeper_desktop_api/resources/chats/reminders.py b/src/beeper_desktop_api/resources/chats/reminders.py
index ef502a6..fb02508 100644
--- a/src/beeper_desktop_api/resources/chats/reminders.py
+++ b/src/beeper_desktop_api/resources/chats/reminders.py
@@ -54,13 +54,13 @@ def create(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Set a reminder for a chat at a specific time
+ """Set a reminder for a chat at a specific time.
Args:
chat_id: Chat ID.
- Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ Input routes also accept the local chat ID from this installation when
+ available.
reminder: Reminder configuration
@@ -95,13 +95,13 @@ def delete(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Clear an existing reminder from a chat
+ """Clear an existing reminder from a chat.
Args:
chat_id: Chat ID.
- Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ Input routes also accept the local chat ID from this installation when
+ available.
extra_headers: Send extra headers
@@ -157,13 +157,13 @@ async def create(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Set a reminder for a chat at a specific time
+ """Set a reminder for a chat at a specific time.
Args:
chat_id: Chat ID.
- Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ Input routes also accept the local chat ID from this installation when
+ available.
reminder: Reminder configuration
@@ -198,13 +198,13 @@ async def delete(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Clear an existing reminder from a chat
+ """Clear an existing reminder from a chat.
Args:
chat_id: Chat ID.
- Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ Input routes also accept the local chat ID from this installation when
+ available.
extra_headers: Send extra headers
diff --git a/src/beeper_desktop_api/resources/info.py b/src/beeper_desktop_api/resources/info.py
index d7eaf8d..c642841 100644
--- a/src/beeper_desktop_api/resources/info.py
+++ b/src/beeper_desktop_api/resources/info.py
@@ -56,7 +56,7 @@ def retrieve(
) -> InfoRetrieveResponse:
"""
Returns app, platform, server, endpoint discovery, OAuth, and WebSocket metadata
- for this Beeper Desktop instance.
+ for this Beeper Client API server.
"""
return self._get(
"/v1/info",
@@ -108,7 +108,7 @@ async def retrieve(
) -> InfoRetrieveResponse:
"""
Returns app, platform, server, endpoint discovery, OAuth, and WebSocket metadata
- for this Beeper Desktop instance.
+ for this Beeper Client API server.
"""
return await self._get(
"/v1/info",
diff --git a/src/beeper_desktop_api/resources/labels.py b/src/beeper_desktop_api/resources/labels.py
new file mode 100644
index 0000000..c1b4228
--- /dev/null
+++ b/src/beeper_desktop_api/resources/labels.py
@@ -0,0 +1,147 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from .._types import Body, Query, Headers, NotGiven, not_given
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from .._base_client import make_request_options
+from ..types.label_list_response import LabelListResponse
+
+__all__ = ["LabelsResource", "AsyncLabelsResource"]
+
+
+class LabelsResource(SyncAPIResource):
+ """User-created labels that organize chats"""
+
+ @cached_property
+ def with_raw_response(self) -> LabelsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#accessing-raw-response-data-eg-headers
+ """
+ return LabelsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> LabelsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#with_streaming_response
+ """
+ return LabelsResourceWithStreamingResponse(self)
+
+ def list(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> LabelListResponse:
+ """List the labels the user has created for organizing chats.
+
+ Filter chats by label
+ via GET /v1/chats/search with labelID.
+ """
+ return self._get(
+ "/v1/labels",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=LabelListResponse,
+ )
+
+
+class AsyncLabelsResource(AsyncAPIResource):
+ """User-created labels that organize chats"""
+
+ @cached_property
+ def with_raw_response(self) -> AsyncLabelsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncLabelsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncLabelsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/beeper/desktop-api-python#with_streaming_response
+ """
+ return AsyncLabelsResourceWithStreamingResponse(self)
+
+ async def list(
+ self,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> LabelListResponse:
+ """List the labels the user has created for organizing chats.
+
+ Filter chats by label
+ via GET /v1/chats/search with labelID.
+ """
+ return await self._get(
+ "/v1/labels",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=LabelListResponse,
+ )
+
+
+class LabelsResourceWithRawResponse:
+ def __init__(self, labels: LabelsResource) -> None:
+ self._labels = labels
+
+ self.list = to_raw_response_wrapper(
+ labels.list,
+ )
+
+
+class AsyncLabelsResourceWithRawResponse:
+ def __init__(self, labels: AsyncLabelsResource) -> None:
+ self._labels = labels
+
+ self.list = async_to_raw_response_wrapper(
+ labels.list,
+ )
+
+
+class LabelsResourceWithStreamingResponse:
+ def __init__(self, labels: LabelsResource) -> None:
+ self._labels = labels
+
+ self.list = to_streamed_response_wrapper(
+ labels.list,
+ )
+
+
+class AsyncLabelsResourceWithStreamingResponse:
+ def __init__(self, labels: AsyncLabelsResource) -> None:
+ self._labels = labels
+
+ self.list = async_to_streamed_response_wrapper(
+ labels.list,
+ )
diff --git a/src/beeper_desktop_api/resources/messages.py b/src/beeper_desktop_api/resources/messages.py
index 290c923..7c4e7e6 100644
--- a/src/beeper_desktop_api/resources/messages.py
+++ b/src/beeper_desktop_api/resources/messages.py
@@ -70,11 +70,11 @@ def retrieve(
) -> Message:
"""
Retrieve a message by final message ID, pendingMessageID, or Matrix event ID.
- Chat ID may be a Beeper chat ID or local chat ID.
+ chatID may be a Beeper chat ID or a local chat ID.
Args:
- chat_id: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ chat_id: Chat ID. Input routes also accept the local chat ID from this installation when
+ available.
message_id: Message ID.
@@ -117,8 +117,8 @@ def update(
be edited.
Args:
- chat_id: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ chat_id: Chat ID. Input routes also accept the local chat ID from this installation when
+ available.
message_id: Message ID.
@@ -163,8 +163,8 @@ def list(
Sorted by timestamp.
Args:
- chat_id: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ chat_id: Chat ID. Input routes also accept the local chat ID from this installation when
+ available.
cursor: Opaque pagination cursor; do not inspect. Use together with 'direction'.
@@ -219,8 +219,8 @@ def delete(
because messages cannot be deleted while sending.
Args:
- chat_id: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ chat_id: Chat ID. Input routes also accept the local chat ID from this installation when
+ available.
message_id: Message ID.
@@ -307,10 +307,10 @@ def search(
media_types: Filter messages by media types. Use ['any'] for any media type, or specify exact
types like ['video', 'image']. Omit for no media filtering.
- query: Literal word search (non-semantic). Finds messages containing these EXACT words
- in any order. Use single words users actually type, not concepts or phrases.
- Example: use "dinner" not "dinner plans", use "sick" not "health issues". If
- omitted, returns results filtered only by other parameters.
+ query: Literal word search. Finds messages containing these words in any order. Use
+ words the user actually typed, not inferred concepts. Example: use "dinner"
+ rather than "dinner plans". If omitted, returns results filtered only by the
+ other parameters.
sender: Filter by sender: 'me' (messages sent by the authenticated user), 'others'
(messages sent by others), or a specific user ID string (user.id).
@@ -373,15 +373,15 @@ def send(
Returns a pending message ID.
Args:
- chat_id: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ chat_id: Chat ID. Input routes also accept the local chat ID from this installation when
+ available.
attachment: Single attachment to send with the message
reply_to_message_id: Provide a message ID to send this as a reply to an existing message
- text: Draft text. Plain text and Markdown are converted to Matrix HTML with the same
- rules used by send and edit.
+ text: Draft text. Plain text and Markdown are converted to Beeper rich text with the
+ same rules used by send and edit.
extra_headers: Send extra headers
@@ -446,11 +446,11 @@ async def retrieve(
) -> Message:
"""
Retrieve a message by final message ID, pendingMessageID, or Matrix event ID.
- Chat ID may be a Beeper chat ID or local chat ID.
+ chatID may be a Beeper chat ID or a local chat ID.
Args:
- chat_id: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ chat_id: Chat ID. Input routes also accept the local chat ID from this installation when
+ available.
message_id: Message ID.
@@ -493,8 +493,8 @@ async def update(
be edited.
Args:
- chat_id: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ chat_id: Chat ID. Input routes also accept the local chat ID from this installation when
+ available.
message_id: Message ID.
@@ -539,8 +539,8 @@ def list(
Sorted by timestamp.
Args:
- chat_id: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ chat_id: Chat ID. Input routes also accept the local chat ID from this installation when
+ available.
cursor: Opaque pagination cursor; do not inspect. Use together with 'direction'.
@@ -595,8 +595,8 @@ async def delete(
because messages cannot be deleted while sending.
Args:
- chat_id: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ chat_id: Chat ID. Input routes also accept the local chat ID from this installation when
+ available.
message_id: Message ID.
@@ -685,10 +685,10 @@ def search(
media_types: Filter messages by media types. Use ['any'] for any media type, or specify exact
types like ['video', 'image']. Omit for no media filtering.
- query: Literal word search (non-semantic). Finds messages containing these EXACT words
- in any order. Use single words users actually type, not concepts or phrases.
- Example: use "dinner" not "dinner plans", use "sick" not "health issues". If
- omitted, returns results filtered only by other parameters.
+ query: Literal word search. Finds messages containing these words in any order. Use
+ words the user actually typed, not inferred concepts. Example: use "dinner"
+ rather than "dinner plans". If omitted, returns results filtered only by the
+ other parameters.
sender: Filter by sender: 'me' (messages sent by the authenticated user), 'others'
(messages sent by others), or a specific user ID string (user.id).
@@ -751,15 +751,15 @@ async def send(
Returns a pending message ID.
Args:
- chat_id: Chat ID. Input routes also accept the local chat ID from this Beeper Desktop
- installation when available.
+ chat_id: Chat ID. Input routes also accept the local chat ID from this installation when
+ available.
attachment: Single attachment to send with the message
reply_to_message_id: Provide a message ID to send this as a reply to an existing message
- text: Draft text. Plain text and Markdown are converted to Matrix HTML with the same
- rules used by send and edit.
+ text: Draft text. Plain text and Markdown are converted to Beeper rich text with the
+ same rules used by send and edit.
extra_headers: Send extra headers
diff --git a/src/beeper_desktop_api/types/__init__.py b/src/beeper_desktop_api/types/__init__.py
index 78ec780..eac40e2 100644
--- a/src/beeper_desktop_api/types/__init__.py
+++ b/src/beeper_desktop_api/types/__init__.py
@@ -3,12 +3,35 @@
from __future__ import annotations
from .chat import Chat as Chat
-from .shared import User as User, Error as Error, Message as Message, Reaction as Reaction, Attachment as Attachment
+from .label import Label as Label
+from .bridge import Bridge as Bridge
+from .shared import (
+ User as User,
+ Error as Error,
+ Message as Message,
+ APIError as APIError,
+ Reaction as Reaction,
+ ChatDraft as ChatDraft,
+ Attachment as Attachment,
+ SendStatus as SendStatus,
+ LinkPreview as LinkPreview,
+ DraftAttachment as DraftAttachment,
+ AppStateSnapshot as AppStateSnapshot,
+ ChatCapabilities as ChatCapabilities,
+ ChatStateCapabilities as ChatStateCapabilities,
+ AttachmentCapabilities as AttachmentCapabilities,
+)
from .account import Account as Account
+from .login_flow import LoginFlow as LoginFlow
+from .bridge_login import BridgeLogin as BridgeLogin
+from .cookie_field import CookieField as CookieField
+from .login_session import LoginSession as LoginSession
+from .account_bridge import AccountBridge as AccountBridge
from .focus_response import FocusResponse as FocusResponse
from .search_response import SearchResponse as SearchResponse
from .chat_list_params import ChatListParams as ChatListParams
from .chat_start_params import ChatStartParams as ChatStartParams
+from .login_input_field import LoginInputField as LoginInputField
from .asset_serve_params import AssetServeParams as AssetServeParams
from .chat_create_params import ChatCreateParams as ChatCreateParams
from .chat_list_response import ChatListResponse as ChatListResponse
@@ -18,8 +41,10 @@
from .chat_archive_params import ChatArchiveParams as ChatArchiveParams
from .chat_start_response import ChatStartResponse as ChatStartResponse
from .client_focus_params import ClientFocusParams as ClientFocusParams
+from .label_list_response import LabelListResponse as LabelListResponse
from .message_list_params import MessageListParams as MessageListParams
from .message_send_params import MessageSendParams as MessageSendParams
+from .bridge_list_response import BridgeListResponse as BridgeListResponse
from .chat_create_response import ChatCreateResponse as ChatCreateResponse
from .chat_retrieve_params import ChatRetrieveParams as ChatRetrieveParams
from .client_search_params import ClientSearchParams as ClientSearchParams
@@ -31,9 +56,16 @@
from .message_search_params import MessageSearchParams as MessageSearchParams
from .message_send_response import MessageSendResponse as MessageSendResponse
from .message_update_params import MessageUpdateParams as MessageUpdateParams
+from .group_field_capability import GroupFieldCapability as GroupFieldCapability
from .info_retrieve_response import InfoRetrieveResponse as InfoRetrieveResponse
from .asset_download_response import AssetDownloadResponse as AssetDownloadResponse
from .chat_mark_unread_params import ChatMarkUnreadParams as ChatMarkUnreadParams
+from .group_type_capabilities import GroupTypeCapabilities as GroupTypeCapabilities
from .message_update_response import MessageUpdateResponse as MessageUpdateResponse
+from .bridge_retrieve_response import BridgeRetrieveResponse as BridgeRetrieveResponse
+from .account_retrieve_response import AccountRetrieveResponse as AccountRetrieveResponse
+from .provisioning_capabilities import ProvisioningCapabilities as ProvisioningCapabilities
from .asset_upload_base64_params import AssetUploadBase64Params as AssetUploadBase64Params
from .asset_upload_base64_response import AssetUploadBase64Response as AssetUploadBase64Response
+from .disappearing_timer_capability import DisappearingTimerCapability as DisappearingTimerCapability
+from .resolve_identifier_capabilities import ResolveIdentifierCapabilities as ResolveIdentifierCapabilities
diff --git a/src/beeper_desktop_api/types/account.py b/src/beeper_desktop_api/types/account.py
index fdc1a4b..a469ec4 100644
--- a/src/beeper_desktop_api/types/account.py
+++ b/src/beeper_desktop_api/types/account.py
@@ -1,35 +1,15 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import Optional
+from typing import Dict, Optional
from typing_extensions import Literal
from pydantic import Field as FieldInfo
from .._models import BaseModel
from .shared.user import User
+from .account_bridge import AccountBridge
-__all__ = ["Account", "Bridge"]
-
-
-class Bridge(BaseModel):
- """Bridge metadata for the account. Available in Beeper Desktop v4.2.785+."""
-
- id: str
- """Bridge instance identifier.
-
- Matrix and cloud bridges often use the bridge type (for example matrix or
- discordgo); local bridges use a local bridge ID (for example local-whatsapp).
- Available in Beeper Desktop v4.2.785+.
- """
-
- provider: Literal["cloud", "self-hosted", "local", "platform-sdk"]
- """Bridge provider for the account. Available in Beeper Desktop v4.2.785+."""
-
- type: str
- """Bridge type, such as matrix, discordgo, slackgo, whatsapp, telegram, or twitter.
-
- Available in Beeper Desktop v4.2.785+.
- """
+__all__ = ["Account"]
class Account(BaseModel):
@@ -43,14 +23,38 @@ class Account(BaseModel):
workspace-scoped cloud bridges, and local-whatsapp*ba*... for local bridges.
"""
- bridge: Bridge
+ bridge: AccountBridge
"""Bridge metadata for the account. Available in Beeper Desktop v4.2.785+."""
+ status: Literal[
+ "connected",
+ "connecting",
+ "backfilling",
+ "connection_required",
+ "reconnect_required",
+ "attention_required",
+ "disconnected",
+ "disabled",
+ ]
+ """Current connection status for this account."""
+
user: User
"""User the account belongs to."""
+ capabilities: Optional[Dict[str, Optional[object]]] = None
+ """Runtime chat/message capabilities for this connected account, when available."""
+
+ login_id: Optional[str] = FieldInfo(alias="loginID", default=None)
+ """Bridge login ID for this account, when known.
+
+ One bridge login can contain multiple chat accounts.
+ """
+
network: Optional[str] = None
"""Human-friendly network name for the account.
Omitted when the network is unknown.
"""
+
+ status_text: Optional[str] = FieldInfo(alias="statusText", default=None)
+ """Human-friendly account status text."""
diff --git a/src/beeper_desktop_api/types/account_bridge.py b/src/beeper_desktop_api/types/account_bridge.py
new file mode 100644
index 0000000..d507424
--- /dev/null
+++ b/src/beeper_desktop_api/types/account_bridge.py
@@ -0,0 +1,31 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal
+
+from .._models import BaseModel
+
+__all__ = ["AccountBridge"]
+
+
+class AccountBridge(BaseModel):
+ """Bridge metadata for the account. Available in Beeper Desktop v4.2.785+."""
+
+ id: str
+ """Bridge identifier.
+
+ Beeper Cloud accounts often use the network type (for example matrix or
+ discordgo); on-device accounts use a local bridge ID (for example
+ local-whatsapp). Available in Beeper Desktop v4.2.785+.
+ """
+
+ provider: Literal["cloud", "self-hosted", "local", "platform-sdk"]
+ """Where this account runs: on this device or in Beeper Cloud.
+
+ Available in Beeper Desktop v4.2.785+.
+ """
+
+ type: str
+ """Bridge type, such as matrix, discordgo, slackgo, whatsapp, telegram, or twitter.
+
+ Available in Beeper Desktop v4.2.785+.
+ """
diff --git a/src/beeper_desktop_api/types/account_retrieve_response.py b/src/beeper_desktop_api/types/account_retrieve_response.py
new file mode 100644
index 0000000..20b59e8
--- /dev/null
+++ b/src/beeper_desktop_api/types/account_retrieve_response.py
@@ -0,0 +1,60 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.user import User
+from .account_bridge import AccountBridge
+
+__all__ = ["AccountRetrieveResponse"]
+
+
+class AccountRetrieveResponse(BaseModel):
+ """A chat account added to Beeper."""
+
+ account_id: str = FieldInfo(alias="accountID")
+ """Chat account added to Beeper.
+
+ Use this to route account-scoped actions. Examples include matrix for
+ Beeper/Matrix, discordgo for a cloud bridge, slackgo.TEAM-USER for
+ workspace-scoped cloud bridges, and local-whatsapp*ba*... for local bridges.
+ """
+
+ bridge: AccountBridge
+ """Bridge metadata for the account. Available in Beeper Desktop v4.2.785+."""
+
+ status: Literal[
+ "connected",
+ "connecting",
+ "backfilling",
+ "connection_required",
+ "reconnect_required",
+ "attention_required",
+ "disconnected",
+ "disabled",
+ ]
+ """Current connection status for this account."""
+
+ user: User
+ """User the account belongs to."""
+
+ capabilities: Optional[Dict[str, Optional[object]]] = None
+ """Runtime chat/message capabilities for this connected account, when available."""
+
+ login_id: Optional[str] = FieldInfo(alias="loginID", default=None)
+ """Bridge login ID for this account, when known.
+
+ One bridge login can contain multiple chat accounts.
+ """
+
+ network: Optional[str] = None
+ """Human-friendly network name for the account.
+
+ Omitted when the network is unknown.
+ """
+
+ status_text: Optional[str] = FieldInfo(alias="statusText", default=None)
+ """Human-friendly account status text."""
diff --git a/src/beeper_desktop_api/types/accounts/contact_list_params.py b/src/beeper_desktop_api/types/accounts/contact_list_params.py
index e452ddb..983aea0 100644
--- a/src/beeper_desktop_api/types/accounts/contact_list_params.py
+++ b/src/beeper_desktop_api/types/accounts/contact_list_params.py
@@ -21,4 +21,4 @@ class ContactListParams(TypedDict, total=False):
"""Maximum contacts to return per page."""
query: str
- """Optional search query for blended contact lookup."""
+ """Optional search query for contact lookup."""
diff --git a/src/beeper_desktop_api/types/accounts/contact_search_params.py b/src/beeper_desktop_api/types/accounts/contact_search_params.py
index f9063e0..849694d 100644
--- a/src/beeper_desktop_api/types/accounts/contact_search_params.py
+++ b/src/beeper_desktop_api/types/accounts/contact_search_params.py
@@ -9,4 +9,10 @@
class ContactSearchParams(TypedDict, total=False):
query: Required[str]
- """Text to search users by. Network-specific behavior."""
+ """Text to search contacts by.
+
+ A phone number, email address, or username written with a leading @ is
+ additionally looked up as an exact identifier on the network; any other text,
+ such as a bare handle or a person or business name, searches existing contacts
+ only. Matching behavior depends on the network.
+ """
diff --git a/src/beeper_desktop_api/types/app/__init__.py b/src/beeper_desktop_api/types/app/__init__.py
new file mode 100644
index 0000000..d6c4b97
--- /dev/null
+++ b/src/beeper_desktop_api/types/app/__init__.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .setup_email_params import SetupEmailParams as SetupEmailParams
+from .setup_start_response import SetupStartResponse as SetupStartResponse
+from .setup_register_params import SetupRegisterParams as SetupRegisterParams
+from .setup_response_params import SetupResponseParams as SetupResponseParams
+from .setup_register_response import SetupRegisterResponse as SetupRegisterResponse
+from .setup_response_response import SetupResponseResponse as SetupResponseResponse
+from .setup_retrieve_response import SetupRetrieveResponse as SetupRetrieveResponse
diff --git a/src/beeper_desktop_api/types/app/setup/__init__.py b/src/beeper_desktop_api/types/app/setup/__init__.py
new file mode 100644
index 0000000..4b7fcd2
--- /dev/null
+++ b/src/beeper_desktop_api/types/app/setup/__init__.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .recovery_key_verify_params import RecoveryKeyVerifyParams as RecoveryKeyVerifyParams
+from .verification_cancel_params import VerificationCancelParams as VerificationCancelParams
+from .verification_create_params import VerificationCreateParams as VerificationCreateParams
+from .verification_list_response import VerificationListResponse as VerificationListResponse
+from .recovery_key_verify_response import RecoveryKeyVerifyResponse as RecoveryKeyVerifyResponse
+from .verification_accept_response import VerificationAcceptResponse as VerificationAcceptResponse
+from .verification_cancel_response import VerificationCancelResponse as VerificationCancelResponse
+from .verification_create_response import VerificationCreateResponse as VerificationCreateResponse
+from .verification_retrieve_response import VerificationRetrieveResponse as VerificationRetrieveResponse
diff --git a/src/beeper_desktop_api/types/app/setup/recovery_key/__init__.py b/src/beeper_desktop_api/types/app/setup/recovery_key/__init__.py
new file mode 100644
index 0000000..0299c46
--- /dev/null
+++ b/src/beeper_desktop_api/types/app/setup/recovery_key/__init__.py
@@ -0,0 +1,8 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .reset_create_params import ResetCreateParams as ResetCreateParams
+from .reset_confirm_params import ResetConfirmParams as ResetConfirmParams
+from .reset_create_response import ResetCreateResponse as ResetCreateResponse
+from .reset_confirm_response import ResetConfirmResponse as ResetConfirmResponse
diff --git a/src/beeper_desktop_api/types/app/setup/recovery_key/reset_confirm_params.py b/src/beeper_desktop_api/types/app/setup/recovery_key/reset_confirm_params.py
new file mode 100644
index 0000000..e3cbb27
--- /dev/null
+++ b/src/beeper_desktop_api/types/app/setup/recovery_key/reset_confirm_params.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from ....._utils import PropertyInfo
+
+__all__ = ["ResetConfirmParams"]
+
+
+class ResetConfirmParams(TypedDict, total=False):
+ recovery_key: Required[Annotated[str, PropertyInfo(alias="recoveryKey")]]
+ """New recovery key returned by the reset step."""
diff --git a/src/beeper_desktop_api/types/app/setup/recovery_key/reset_confirm_response.py b/src/beeper_desktop_api/types/app/setup/recovery_key/reset_confirm_response.py
new file mode 100644
index 0000000..a5622ad
--- /dev/null
+++ b/src/beeper_desktop_api/types/app/setup/recovery_key/reset_confirm_response.py
@@ -0,0 +1,192 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ....._models import BaseModel
+
+__all__ = [
+ "ResetConfirmResponse",
+ "Session",
+ "SessionE2EE",
+ "SessionE2EESecrets",
+ "SessionMatrix",
+ "SessionVerification",
+ "SessionVerificationError",
+ "SessionVerificationOtherDevice",
+ "SessionVerificationQR",
+ "SessionVerificationSAS",
+]
+
+
+class SessionE2EESecrets(BaseModel):
+ """Encrypted messaging keys available on this device."""
+
+ master_key: bool = FieldInfo(alias="masterKey")
+ """Whether the account identity key is available."""
+
+ megolm_backup_key: bool = FieldInfo(alias="megolmBackupKey")
+ """Whether the encrypted message backup key is available."""
+
+ recovery_key: bool = FieldInfo(alias="recoveryKey")
+ """Whether a recovery key is available."""
+
+ self_signing_key: bool = FieldInfo(alias="selfSigningKey")
+ """Whether the device trust key is available."""
+
+ user_signing_key: bool = FieldInfo(alias="userSigningKey")
+ """Whether the user trust key is available."""
+
+
+class SessionE2EE(BaseModel):
+ """Encrypted messaging setup status."""
+
+ cross_signing: bool = FieldInfo(alias="crossSigning")
+ """Whether this account can verify trusted devices."""
+
+ first_sync_done: bool = FieldInfo(alias="firstSyncDone")
+ """Whether the first encrypted message sync is complete."""
+
+ has_backed_up_recovery_key: bool = FieldInfo(alias="hasBackedUpRecoveryKey")
+ """Whether the user confirmed that they saved their recovery key."""
+
+ initialized: bool
+ """Whether encrypted messaging setup has started."""
+
+ key_backup: bool = FieldInfo(alias="keyBackup")
+ """Whether encrypted message backup is available."""
+
+ secrets: SessionE2EESecrets
+ """Encrypted messaging keys available on this device."""
+
+ secret_storage: bool = FieldInfo(alias="secretStorage")
+ """Whether secure key storage is available."""
+
+ verified: bool
+ """Whether this device is trusted for encrypted messages."""
+
+ recovery_key_generated_at: Optional[float] = FieldInfo(alias="recoveryKeyGeneratedAt", default=None)
+ """Unix timestamp for when the recovery key was created."""
+
+
+class SessionMatrix(BaseModel):
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ device_id: str = FieldInfo(alias="deviceID")
+ """Current device ID."""
+
+ homeserver: str
+ """Beeper homeserver URL for this account."""
+
+ user_id: str = FieldInfo(alias="userID")
+ """Signed-in Beeper user ID."""
+
+
+class SessionVerificationError(BaseModel):
+ """Verification error details, if verification stopped."""
+
+ code: str
+ """Verification error code."""
+
+ reason: str
+ """User-facing verification error message."""
+
+
+class SessionVerificationOtherDevice(BaseModel):
+ """Other device participating in verification."""
+
+ id: str
+ """Other device ID."""
+
+ name: Optional[str] = None
+ """Other device display name, if known."""
+
+
+class SessionVerificationQR(BaseModel):
+ """QR verification data."""
+
+ data: str
+ """QR code payload to display for verification."""
+
+
+class SessionVerificationSAS(BaseModel):
+ """Emoji or number comparison data for verification."""
+
+ emojis: str
+ """Emoji sequence to compare on both devices."""
+
+ decimals: Optional[str] = None
+ """Number sequence to compare on both devices."""
+
+
+class SessionVerification(BaseModel):
+ """Trusted device verification progress."""
+
+ id: str
+ """Verification ID to pass in verification action paths."""
+
+ available_actions: List[Literal["accept", "cancel", "qr.confirmScanned", "sas.start", "sas.confirm"]] = FieldInfo(
+ alias="availableActions"
+ )
+ """Verification actions that are valid for the current state."""
+
+ direction: Literal["incoming", "outgoing"]
+ """Whether this device started or received the verification."""
+
+ methods: List[Literal["qr", "sas"]]
+ """Verification methods supported for this transaction."""
+
+ purpose: Literal["login", "device"]
+ """Why this verification exists."""
+
+ state: Literal["requested", "ready", "sas_ready", "qr_scanned", "done", "cancelled", "error"]
+ """Current trusted-device verification state."""
+
+ error: Optional[SessionVerificationError] = None
+ """Verification error details, if verification stopped."""
+
+ other_device: Optional[SessionVerificationOtherDevice] = FieldInfo(alias="otherDevice", default=None)
+ """Other device participating in verification."""
+
+ other_user_id: Optional[str] = FieldInfo(alias="otherUserID", default=None)
+ """Other Beeper user participating in verification."""
+
+ qr: Optional[SessionVerificationQR] = None
+ """QR verification data."""
+
+ sas: Optional[SessionVerificationSAS] = None
+ """Emoji or number comparison data for verification."""
+
+
+class Session(BaseModel):
+ """Current app sign-in and encrypted messaging setup state."""
+
+ e2ee: SessionE2EE
+ """Encrypted messaging setup status."""
+
+ state: Literal[
+ "needs-login",
+ "initializing",
+ "needs-cross-signing-setup",
+ "needs-verification",
+ "needs-secrets",
+ "needs-first-sync",
+ "ready",
+ ]
+ """
+ Current sign-in and encrypted messaging setup state for Beeper Desktop or Beeper
+ Server.
+ """
+
+ matrix: Optional[SessionMatrix] = None
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ verification: Optional[SessionVerification] = None
+ """Trusted device verification progress."""
+
+
+class ResetConfirmResponse(BaseModel):
+ session: Session
+ """Current app sign-in and encrypted messaging setup state."""
diff --git a/src/beeper_desktop_api/types/app/setup/recovery_key/reset_create_params.py b/src/beeper_desktop_api/types/app/setup/recovery_key/reset_create_params.py
new file mode 100644
index 0000000..c0817fe
--- /dev/null
+++ b/src/beeper_desktop_api/types/app/setup/recovery_key/reset_create_params.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from ....._utils import PropertyInfo
+
+__all__ = ["ResetCreateParams"]
+
+
+class ResetCreateParams(TypedDict, total=False):
+ existing_recovery_key: Annotated[str, PropertyInfo(alias="existingRecoveryKey")]
+ """Existing recovery key, if the user has it."""
diff --git a/src/beeper_desktop_api/types/app/setup/recovery_key/reset_create_response.py b/src/beeper_desktop_api/types/app/setup/recovery_key/reset_create_response.py
new file mode 100644
index 0000000..1474d5f
--- /dev/null
+++ b/src/beeper_desktop_api/types/app/setup/recovery_key/reset_create_response.py
@@ -0,0 +1,200 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ....._models import BaseModel
+
+__all__ = [
+ "ResetCreateResponse",
+ "Session",
+ "SessionE2EE",
+ "SessionE2EESecrets",
+ "SessionMatrix",
+ "SessionVerification",
+ "SessionVerificationError",
+ "SessionVerificationOtherDevice",
+ "SessionVerificationQR",
+ "SessionVerificationSAS",
+]
+
+
+class SessionE2EESecrets(BaseModel):
+ """Encrypted messaging keys available on this device."""
+
+ master_key: bool = FieldInfo(alias="masterKey")
+ """Whether the account identity key is available."""
+
+ megolm_backup_key: bool = FieldInfo(alias="megolmBackupKey")
+ """Whether the encrypted message backup key is available."""
+
+ recovery_key: bool = FieldInfo(alias="recoveryKey")
+ """Whether a recovery key is available."""
+
+ self_signing_key: bool = FieldInfo(alias="selfSigningKey")
+ """Whether the device trust key is available."""
+
+ user_signing_key: bool = FieldInfo(alias="userSigningKey")
+ """Whether the user trust key is available."""
+
+
+class SessionE2EE(BaseModel):
+ """Encrypted messaging setup status."""
+
+ cross_signing: bool = FieldInfo(alias="crossSigning")
+ """Whether this account can verify trusted devices."""
+
+ first_sync_done: bool = FieldInfo(alias="firstSyncDone")
+ """Whether the first encrypted message sync is complete."""
+
+ has_backed_up_recovery_key: bool = FieldInfo(alias="hasBackedUpRecoveryKey")
+ """Whether the user confirmed that they saved their recovery key."""
+
+ initialized: bool
+ """Whether encrypted messaging setup has started."""
+
+ key_backup: bool = FieldInfo(alias="keyBackup")
+ """Whether encrypted message backup is available."""
+
+ secrets: SessionE2EESecrets
+ """Encrypted messaging keys available on this device."""
+
+ secret_storage: bool = FieldInfo(alias="secretStorage")
+ """Whether secure key storage is available."""
+
+ verified: bool
+ """Whether this device is trusted for encrypted messages."""
+
+ recovery_key_generated_at: Optional[float] = FieldInfo(alias="recoveryKeyGeneratedAt", default=None)
+ """Unix timestamp for when the recovery key was created."""
+
+
+class SessionMatrix(BaseModel):
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ device_id: str = FieldInfo(alias="deviceID")
+ """Current device ID."""
+
+ homeserver: str
+ """Beeper homeserver URL for this account."""
+
+ user_id: str = FieldInfo(alias="userID")
+ """Signed-in Beeper user ID."""
+
+
+class SessionVerificationError(BaseModel):
+ """Verification error details, if verification stopped."""
+
+ code: str
+ """Verification error code."""
+
+ reason: str
+ """User-facing verification error message."""
+
+
+class SessionVerificationOtherDevice(BaseModel):
+ """Other device participating in verification."""
+
+ id: str
+ """Other device ID."""
+
+ name: Optional[str] = None
+ """Other device display name, if known."""
+
+
+class SessionVerificationQR(BaseModel):
+ """QR verification data."""
+
+ data: str
+ """QR code payload to display for verification."""
+
+
+class SessionVerificationSAS(BaseModel):
+ """Emoji or number comparison data for verification."""
+
+ emojis: str
+ """Emoji sequence to compare on both devices."""
+
+ decimals: Optional[str] = None
+ """Number sequence to compare on both devices."""
+
+
+class SessionVerification(BaseModel):
+ """Trusted device verification progress."""
+
+ id: str
+ """Verification ID to pass in verification action paths."""
+
+ available_actions: List[Literal["accept", "cancel", "qr.confirmScanned", "sas.start", "sas.confirm"]] = FieldInfo(
+ alias="availableActions"
+ )
+ """Verification actions that are valid for the current state."""
+
+ direction: Literal["incoming", "outgoing"]
+ """Whether this device started or received the verification."""
+
+ methods: List[Literal["qr", "sas"]]
+ """Verification methods supported for this transaction."""
+
+ purpose: Literal["login", "device"]
+ """Why this verification exists."""
+
+ state: Literal["requested", "ready", "sas_ready", "qr_scanned", "done", "cancelled", "error"]
+ """Current trusted-device verification state."""
+
+ error: Optional[SessionVerificationError] = None
+ """Verification error details, if verification stopped."""
+
+ other_device: Optional[SessionVerificationOtherDevice] = FieldInfo(alias="otherDevice", default=None)
+ """Other device participating in verification."""
+
+ other_user_id: Optional[str] = FieldInfo(alias="otherUserID", default=None)
+ """Other Beeper user participating in verification."""
+
+ qr: Optional[SessionVerificationQR] = None
+ """QR verification data."""
+
+ sas: Optional[SessionVerificationSAS] = None
+ """Emoji or number comparison data for verification."""
+
+
+class Session(BaseModel):
+ """
+ Current app sign-in and encrypted messaging setup state after creating the new recovery key.
+ """
+
+ e2ee: SessionE2EE
+ """Encrypted messaging setup status."""
+
+ state: Literal[
+ "needs-login",
+ "initializing",
+ "needs-cross-signing-setup",
+ "needs-verification",
+ "needs-secrets",
+ "needs-first-sync",
+ "ready",
+ ]
+ """
+ Current sign-in and encrypted messaging setup state for Beeper Desktop or Beeper
+ Server.
+ """
+
+ matrix: Optional[SessionMatrix] = None
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ verification: Optional[SessionVerification] = None
+ """Trusted device verification progress."""
+
+
+class ResetCreateResponse(BaseModel):
+ recovery_key: str = FieldInfo(alias="recoveryKey")
+ """New recovery key. Show it once and ask the user to save it."""
+
+ session: Session
+ """
+ Current app sign-in and encrypted messaging setup state after creating the new
+ recovery key.
+ """
diff --git a/src/beeper_desktop_api/types/app/setup/recovery_key_verify_params.py b/src/beeper_desktop_api/types/app/setup/recovery_key_verify_params.py
new file mode 100644
index 0000000..2264fca
--- /dev/null
+++ b/src/beeper_desktop_api/types/app/setup/recovery_key_verify_params.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from ...._utils import PropertyInfo
+
+__all__ = ["RecoveryKeyVerifyParams"]
+
+
+class RecoveryKeyVerifyParams(TypedDict, total=False):
+ recovery_key: Required[Annotated[str, PropertyInfo(alias="recoveryKey")]]
+ """Recovery key saved by the user."""
diff --git a/src/beeper_desktop_api/types/app/setup/recovery_key_verify_response.py b/src/beeper_desktop_api/types/app/setup/recovery_key_verify_response.py
new file mode 100644
index 0000000..91e61b1
--- /dev/null
+++ b/src/beeper_desktop_api/types/app/setup/recovery_key_verify_response.py
@@ -0,0 +1,192 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+
+__all__ = [
+ "RecoveryKeyVerifyResponse",
+ "Session",
+ "SessionE2EE",
+ "SessionE2EESecrets",
+ "SessionMatrix",
+ "SessionVerification",
+ "SessionVerificationError",
+ "SessionVerificationOtherDevice",
+ "SessionVerificationQR",
+ "SessionVerificationSAS",
+]
+
+
+class SessionE2EESecrets(BaseModel):
+ """Encrypted messaging keys available on this device."""
+
+ master_key: bool = FieldInfo(alias="masterKey")
+ """Whether the account identity key is available."""
+
+ megolm_backup_key: bool = FieldInfo(alias="megolmBackupKey")
+ """Whether the encrypted message backup key is available."""
+
+ recovery_key: bool = FieldInfo(alias="recoveryKey")
+ """Whether a recovery key is available."""
+
+ self_signing_key: bool = FieldInfo(alias="selfSigningKey")
+ """Whether the device trust key is available."""
+
+ user_signing_key: bool = FieldInfo(alias="userSigningKey")
+ """Whether the user trust key is available."""
+
+
+class SessionE2EE(BaseModel):
+ """Encrypted messaging setup status."""
+
+ cross_signing: bool = FieldInfo(alias="crossSigning")
+ """Whether this account can verify trusted devices."""
+
+ first_sync_done: bool = FieldInfo(alias="firstSyncDone")
+ """Whether the first encrypted message sync is complete."""
+
+ has_backed_up_recovery_key: bool = FieldInfo(alias="hasBackedUpRecoveryKey")
+ """Whether the user confirmed that they saved their recovery key."""
+
+ initialized: bool
+ """Whether encrypted messaging setup has started."""
+
+ key_backup: bool = FieldInfo(alias="keyBackup")
+ """Whether encrypted message backup is available."""
+
+ secrets: SessionE2EESecrets
+ """Encrypted messaging keys available on this device."""
+
+ secret_storage: bool = FieldInfo(alias="secretStorage")
+ """Whether secure key storage is available."""
+
+ verified: bool
+ """Whether this device is trusted for encrypted messages."""
+
+ recovery_key_generated_at: Optional[float] = FieldInfo(alias="recoveryKeyGeneratedAt", default=None)
+ """Unix timestamp for when the recovery key was created."""
+
+
+class SessionMatrix(BaseModel):
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ device_id: str = FieldInfo(alias="deviceID")
+ """Current device ID."""
+
+ homeserver: str
+ """Beeper homeserver URL for this account."""
+
+ user_id: str = FieldInfo(alias="userID")
+ """Signed-in Beeper user ID."""
+
+
+class SessionVerificationError(BaseModel):
+ """Verification error details, if verification stopped."""
+
+ code: str
+ """Verification error code."""
+
+ reason: str
+ """User-facing verification error message."""
+
+
+class SessionVerificationOtherDevice(BaseModel):
+ """Other device participating in verification."""
+
+ id: str
+ """Other device ID."""
+
+ name: Optional[str] = None
+ """Other device display name, if known."""
+
+
+class SessionVerificationQR(BaseModel):
+ """QR verification data."""
+
+ data: str
+ """QR code payload to display for verification."""
+
+
+class SessionVerificationSAS(BaseModel):
+ """Emoji or number comparison data for verification."""
+
+ emojis: str
+ """Emoji sequence to compare on both devices."""
+
+ decimals: Optional[str] = None
+ """Number sequence to compare on both devices."""
+
+
+class SessionVerification(BaseModel):
+ """Trusted device verification progress."""
+
+ id: str
+ """Verification ID to pass in verification action paths."""
+
+ available_actions: List[Literal["accept", "cancel", "qr.confirmScanned", "sas.start", "sas.confirm"]] = FieldInfo(
+ alias="availableActions"
+ )
+ """Verification actions that are valid for the current state."""
+
+ direction: Literal["incoming", "outgoing"]
+ """Whether this device started or received the verification."""
+
+ methods: List[Literal["qr", "sas"]]
+ """Verification methods supported for this transaction."""
+
+ purpose: Literal["login", "device"]
+ """Why this verification exists."""
+
+ state: Literal["requested", "ready", "sas_ready", "qr_scanned", "done", "cancelled", "error"]
+ """Current trusted-device verification state."""
+
+ error: Optional[SessionVerificationError] = None
+ """Verification error details, if verification stopped."""
+
+ other_device: Optional[SessionVerificationOtherDevice] = FieldInfo(alias="otherDevice", default=None)
+ """Other device participating in verification."""
+
+ other_user_id: Optional[str] = FieldInfo(alias="otherUserID", default=None)
+ """Other Beeper user participating in verification."""
+
+ qr: Optional[SessionVerificationQR] = None
+ """QR verification data."""
+
+ sas: Optional[SessionVerificationSAS] = None
+ """Emoji or number comparison data for verification."""
+
+
+class Session(BaseModel):
+ """Current app sign-in and encrypted messaging setup state."""
+
+ e2ee: SessionE2EE
+ """Encrypted messaging setup status."""
+
+ state: Literal[
+ "needs-login",
+ "initializing",
+ "needs-cross-signing-setup",
+ "needs-verification",
+ "needs-secrets",
+ "needs-first-sync",
+ "ready",
+ ]
+ """
+ Current sign-in and encrypted messaging setup state for Beeper Desktop or Beeper
+ Server.
+ """
+
+ matrix: Optional[SessionMatrix] = None
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ verification: Optional[SessionVerification] = None
+ """Trusted device verification progress."""
+
+
+class RecoveryKeyVerifyResponse(BaseModel):
+ session: Session
+ """Current app sign-in and encrypted messaging setup state."""
diff --git a/src/beeper_desktop_api/types/app/setup/verification_accept_response.py b/src/beeper_desktop_api/types/app/setup/verification_accept_response.py
new file mode 100644
index 0000000..ef832f5
--- /dev/null
+++ b/src/beeper_desktop_api/types/app/setup/verification_accept_response.py
@@ -0,0 +1,276 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+
+__all__ = [
+ "VerificationAcceptResponse",
+ "Session",
+ "SessionE2EE",
+ "SessionE2EESecrets",
+ "SessionMatrix",
+ "SessionVerification",
+ "SessionVerificationError",
+ "SessionVerificationOtherDevice",
+ "SessionVerificationQR",
+ "SessionVerificationSAS",
+ "Verification",
+ "VerificationError",
+ "VerificationOtherDevice",
+ "VerificationQR",
+ "VerificationSAS",
+]
+
+
+class SessionE2EESecrets(BaseModel):
+ """Encrypted messaging keys available on this device."""
+
+ master_key: bool = FieldInfo(alias="masterKey")
+ """Whether the account identity key is available."""
+
+ megolm_backup_key: bool = FieldInfo(alias="megolmBackupKey")
+ """Whether the encrypted message backup key is available."""
+
+ recovery_key: bool = FieldInfo(alias="recoveryKey")
+ """Whether a recovery key is available."""
+
+ self_signing_key: bool = FieldInfo(alias="selfSigningKey")
+ """Whether the device trust key is available."""
+
+ user_signing_key: bool = FieldInfo(alias="userSigningKey")
+ """Whether the user trust key is available."""
+
+
+class SessionE2EE(BaseModel):
+ """Encrypted messaging setup status."""
+
+ cross_signing: bool = FieldInfo(alias="crossSigning")
+ """Whether this account can verify trusted devices."""
+
+ first_sync_done: bool = FieldInfo(alias="firstSyncDone")
+ """Whether the first encrypted message sync is complete."""
+
+ has_backed_up_recovery_key: bool = FieldInfo(alias="hasBackedUpRecoveryKey")
+ """Whether the user confirmed that they saved their recovery key."""
+
+ initialized: bool
+ """Whether encrypted messaging setup has started."""
+
+ key_backup: bool = FieldInfo(alias="keyBackup")
+ """Whether encrypted message backup is available."""
+
+ secrets: SessionE2EESecrets
+ """Encrypted messaging keys available on this device."""
+
+ secret_storage: bool = FieldInfo(alias="secretStorage")
+ """Whether secure key storage is available."""
+
+ verified: bool
+ """Whether this device is trusted for encrypted messages."""
+
+ recovery_key_generated_at: Optional[float] = FieldInfo(alias="recoveryKeyGeneratedAt", default=None)
+ """Unix timestamp for when the recovery key was created."""
+
+
+class SessionMatrix(BaseModel):
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ device_id: str = FieldInfo(alias="deviceID")
+ """Current device ID."""
+
+ homeserver: str
+ """Beeper homeserver URL for this account."""
+
+ user_id: str = FieldInfo(alias="userID")
+ """Signed-in Beeper user ID."""
+
+
+class SessionVerificationError(BaseModel):
+ """Verification error details, if verification stopped."""
+
+ code: str
+ """Verification error code."""
+
+ reason: str
+ """User-facing verification error message."""
+
+
+class SessionVerificationOtherDevice(BaseModel):
+ """Other device participating in verification."""
+
+ id: str
+ """Other device ID."""
+
+ name: Optional[str] = None
+ """Other device display name, if known."""
+
+
+class SessionVerificationQR(BaseModel):
+ """QR verification data."""
+
+ data: str
+ """QR code payload to display for verification."""
+
+
+class SessionVerificationSAS(BaseModel):
+ """Emoji or number comparison data for verification."""
+
+ emojis: str
+ """Emoji sequence to compare on both devices."""
+
+ decimals: Optional[str] = None
+ """Number sequence to compare on both devices."""
+
+
+class SessionVerification(BaseModel):
+ """Trusted device verification progress."""
+
+ id: str
+ """Verification ID to pass in verification action paths."""
+
+ available_actions: List[Literal["accept", "cancel", "qr.confirmScanned", "sas.start", "sas.confirm"]] = FieldInfo(
+ alias="availableActions"
+ )
+ """Verification actions that are valid for the current state."""
+
+ direction: Literal["incoming", "outgoing"]
+ """Whether this device started or received the verification."""
+
+ methods: List[Literal["qr", "sas"]]
+ """Verification methods supported for this transaction."""
+
+ purpose: Literal["login", "device"]
+ """Why this verification exists."""
+
+ state: Literal["requested", "ready", "sas_ready", "qr_scanned", "done", "cancelled", "error"]
+ """Current trusted-device verification state."""
+
+ error: Optional[SessionVerificationError] = None
+ """Verification error details, if verification stopped."""
+
+ other_device: Optional[SessionVerificationOtherDevice] = FieldInfo(alias="otherDevice", default=None)
+ """Other device participating in verification."""
+
+ other_user_id: Optional[str] = FieldInfo(alias="otherUserID", default=None)
+ """Other Beeper user participating in verification."""
+
+ qr: Optional[SessionVerificationQR] = None
+ """QR verification data."""
+
+ sas: Optional[SessionVerificationSAS] = None
+ """Emoji or number comparison data for verification."""
+
+
+class Session(BaseModel):
+ """Current app sign-in and encrypted messaging setup state."""
+
+ e2ee: SessionE2EE
+ """Encrypted messaging setup status."""
+
+ state: Literal[
+ "needs-login",
+ "initializing",
+ "needs-cross-signing-setup",
+ "needs-verification",
+ "needs-secrets",
+ "needs-first-sync",
+ "ready",
+ ]
+ """
+ Current sign-in and encrypted messaging setup state for Beeper Desktop or Beeper
+ Server.
+ """
+
+ matrix: Optional[SessionMatrix] = None
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ verification: Optional[SessionVerification] = None
+ """Trusted device verification progress."""
+
+
+class VerificationError(BaseModel):
+ """Verification error details, if verification stopped."""
+
+ code: str
+ """Verification error code."""
+
+ reason: str
+ """User-facing verification error message."""
+
+
+class VerificationOtherDevice(BaseModel):
+ """Other device participating in verification."""
+
+ id: str
+ """Other device ID."""
+
+ name: Optional[str] = None
+ """Other device display name, if known."""
+
+
+class VerificationQR(BaseModel):
+ """QR verification data."""
+
+ data: str
+ """QR code payload to display for verification."""
+
+
+class VerificationSAS(BaseModel):
+ """Emoji or number comparison data for verification."""
+
+ emojis: str
+ """Emoji sequence to compare on both devices."""
+
+ decimals: Optional[str] = None
+ """Number sequence to compare on both devices."""
+
+
+class Verification(BaseModel):
+ """Trusted device verification progress."""
+
+ id: str
+ """Verification ID to pass in verification action paths."""
+
+ available_actions: List[Literal["accept", "cancel", "qr.confirmScanned", "sas.start", "sas.confirm"]] = FieldInfo(
+ alias="availableActions"
+ )
+ """Verification actions that are valid for the current state."""
+
+ direction: Literal["incoming", "outgoing"]
+ """Whether this device started or received the verification."""
+
+ methods: List[Literal["qr", "sas"]]
+ """Verification methods supported for this transaction."""
+
+ purpose: Literal["login", "device"]
+ """Why this verification exists."""
+
+ state: Literal["requested", "ready", "sas_ready", "qr_scanned", "done", "cancelled", "error"]
+ """Current trusted-device verification state."""
+
+ error: Optional[VerificationError] = None
+ """Verification error details, if verification stopped."""
+
+ other_device: Optional[VerificationOtherDevice] = FieldInfo(alias="otherDevice", default=None)
+ """Other device participating in verification."""
+
+ other_user_id: Optional[str] = FieldInfo(alias="otherUserID", default=None)
+ """Other Beeper user participating in verification."""
+
+ qr: Optional[VerificationQR] = None
+ """QR verification data."""
+
+ sas: Optional[VerificationSAS] = None
+ """Emoji or number comparison data for verification."""
+
+
+class VerificationAcceptResponse(BaseModel):
+ session: Session
+ """Current app sign-in and encrypted messaging setup state."""
+
+ verification: Optional[Verification] = None
+ """Trusted device verification progress."""
diff --git a/src/beeper_desktop_api/types/app/setup/verification_cancel_params.py b/src/beeper_desktop_api/types/app/setup/verification_cancel_params.py
new file mode 100644
index 0000000..d954b97
--- /dev/null
+++ b/src/beeper_desktop_api/types/app/setup/verification_cancel_params.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["VerificationCancelParams"]
+
+
+class VerificationCancelParams(TypedDict, total=False):
+ code: str
+ """Optional cancellation code."""
+
+ reason: str
+ """Optional user-facing cancellation reason."""
diff --git a/src/beeper_desktop_api/types/app/setup/verification_cancel_response.py b/src/beeper_desktop_api/types/app/setup/verification_cancel_response.py
new file mode 100644
index 0000000..487341c
--- /dev/null
+++ b/src/beeper_desktop_api/types/app/setup/verification_cancel_response.py
@@ -0,0 +1,276 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+
+__all__ = [
+ "VerificationCancelResponse",
+ "Session",
+ "SessionE2EE",
+ "SessionE2EESecrets",
+ "SessionMatrix",
+ "SessionVerification",
+ "SessionVerificationError",
+ "SessionVerificationOtherDevice",
+ "SessionVerificationQR",
+ "SessionVerificationSAS",
+ "Verification",
+ "VerificationError",
+ "VerificationOtherDevice",
+ "VerificationQR",
+ "VerificationSAS",
+]
+
+
+class SessionE2EESecrets(BaseModel):
+ """Encrypted messaging keys available on this device."""
+
+ master_key: bool = FieldInfo(alias="masterKey")
+ """Whether the account identity key is available."""
+
+ megolm_backup_key: bool = FieldInfo(alias="megolmBackupKey")
+ """Whether the encrypted message backup key is available."""
+
+ recovery_key: bool = FieldInfo(alias="recoveryKey")
+ """Whether a recovery key is available."""
+
+ self_signing_key: bool = FieldInfo(alias="selfSigningKey")
+ """Whether the device trust key is available."""
+
+ user_signing_key: bool = FieldInfo(alias="userSigningKey")
+ """Whether the user trust key is available."""
+
+
+class SessionE2EE(BaseModel):
+ """Encrypted messaging setup status."""
+
+ cross_signing: bool = FieldInfo(alias="crossSigning")
+ """Whether this account can verify trusted devices."""
+
+ first_sync_done: bool = FieldInfo(alias="firstSyncDone")
+ """Whether the first encrypted message sync is complete."""
+
+ has_backed_up_recovery_key: bool = FieldInfo(alias="hasBackedUpRecoveryKey")
+ """Whether the user confirmed that they saved their recovery key."""
+
+ initialized: bool
+ """Whether encrypted messaging setup has started."""
+
+ key_backup: bool = FieldInfo(alias="keyBackup")
+ """Whether encrypted message backup is available."""
+
+ secrets: SessionE2EESecrets
+ """Encrypted messaging keys available on this device."""
+
+ secret_storage: bool = FieldInfo(alias="secretStorage")
+ """Whether secure key storage is available."""
+
+ verified: bool
+ """Whether this device is trusted for encrypted messages."""
+
+ recovery_key_generated_at: Optional[float] = FieldInfo(alias="recoveryKeyGeneratedAt", default=None)
+ """Unix timestamp for when the recovery key was created."""
+
+
+class SessionMatrix(BaseModel):
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ device_id: str = FieldInfo(alias="deviceID")
+ """Current device ID."""
+
+ homeserver: str
+ """Beeper homeserver URL for this account."""
+
+ user_id: str = FieldInfo(alias="userID")
+ """Signed-in Beeper user ID."""
+
+
+class SessionVerificationError(BaseModel):
+ """Verification error details, if verification stopped."""
+
+ code: str
+ """Verification error code."""
+
+ reason: str
+ """User-facing verification error message."""
+
+
+class SessionVerificationOtherDevice(BaseModel):
+ """Other device participating in verification."""
+
+ id: str
+ """Other device ID."""
+
+ name: Optional[str] = None
+ """Other device display name, if known."""
+
+
+class SessionVerificationQR(BaseModel):
+ """QR verification data."""
+
+ data: str
+ """QR code payload to display for verification."""
+
+
+class SessionVerificationSAS(BaseModel):
+ """Emoji or number comparison data for verification."""
+
+ emojis: str
+ """Emoji sequence to compare on both devices."""
+
+ decimals: Optional[str] = None
+ """Number sequence to compare on both devices."""
+
+
+class SessionVerification(BaseModel):
+ """Trusted device verification progress."""
+
+ id: str
+ """Verification ID to pass in verification action paths."""
+
+ available_actions: List[Literal["accept", "cancel", "qr.confirmScanned", "sas.start", "sas.confirm"]] = FieldInfo(
+ alias="availableActions"
+ )
+ """Verification actions that are valid for the current state."""
+
+ direction: Literal["incoming", "outgoing"]
+ """Whether this device started or received the verification."""
+
+ methods: List[Literal["qr", "sas"]]
+ """Verification methods supported for this transaction."""
+
+ purpose: Literal["login", "device"]
+ """Why this verification exists."""
+
+ state: Literal["requested", "ready", "sas_ready", "qr_scanned", "done", "cancelled", "error"]
+ """Current trusted-device verification state."""
+
+ error: Optional[SessionVerificationError] = None
+ """Verification error details, if verification stopped."""
+
+ other_device: Optional[SessionVerificationOtherDevice] = FieldInfo(alias="otherDevice", default=None)
+ """Other device participating in verification."""
+
+ other_user_id: Optional[str] = FieldInfo(alias="otherUserID", default=None)
+ """Other Beeper user participating in verification."""
+
+ qr: Optional[SessionVerificationQR] = None
+ """QR verification data."""
+
+ sas: Optional[SessionVerificationSAS] = None
+ """Emoji or number comparison data for verification."""
+
+
+class Session(BaseModel):
+ """Current app sign-in and encrypted messaging setup state."""
+
+ e2ee: SessionE2EE
+ """Encrypted messaging setup status."""
+
+ state: Literal[
+ "needs-login",
+ "initializing",
+ "needs-cross-signing-setup",
+ "needs-verification",
+ "needs-secrets",
+ "needs-first-sync",
+ "ready",
+ ]
+ """
+ Current sign-in and encrypted messaging setup state for Beeper Desktop or Beeper
+ Server.
+ """
+
+ matrix: Optional[SessionMatrix] = None
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ verification: Optional[SessionVerification] = None
+ """Trusted device verification progress."""
+
+
+class VerificationError(BaseModel):
+ """Verification error details, if verification stopped."""
+
+ code: str
+ """Verification error code."""
+
+ reason: str
+ """User-facing verification error message."""
+
+
+class VerificationOtherDevice(BaseModel):
+ """Other device participating in verification."""
+
+ id: str
+ """Other device ID."""
+
+ name: Optional[str] = None
+ """Other device display name, if known."""
+
+
+class VerificationQR(BaseModel):
+ """QR verification data."""
+
+ data: str
+ """QR code payload to display for verification."""
+
+
+class VerificationSAS(BaseModel):
+ """Emoji or number comparison data for verification."""
+
+ emojis: str
+ """Emoji sequence to compare on both devices."""
+
+ decimals: Optional[str] = None
+ """Number sequence to compare on both devices."""
+
+
+class Verification(BaseModel):
+ """Trusted device verification progress."""
+
+ id: str
+ """Verification ID to pass in verification action paths."""
+
+ available_actions: List[Literal["accept", "cancel", "qr.confirmScanned", "sas.start", "sas.confirm"]] = FieldInfo(
+ alias="availableActions"
+ )
+ """Verification actions that are valid for the current state."""
+
+ direction: Literal["incoming", "outgoing"]
+ """Whether this device started or received the verification."""
+
+ methods: List[Literal["qr", "sas"]]
+ """Verification methods supported for this transaction."""
+
+ purpose: Literal["login", "device"]
+ """Why this verification exists."""
+
+ state: Literal["requested", "ready", "sas_ready", "qr_scanned", "done", "cancelled", "error"]
+ """Current trusted-device verification state."""
+
+ error: Optional[VerificationError] = None
+ """Verification error details, if verification stopped."""
+
+ other_device: Optional[VerificationOtherDevice] = FieldInfo(alias="otherDevice", default=None)
+ """Other device participating in verification."""
+
+ other_user_id: Optional[str] = FieldInfo(alias="otherUserID", default=None)
+ """Other Beeper user participating in verification."""
+
+ qr: Optional[VerificationQR] = None
+ """QR verification data."""
+
+ sas: Optional[VerificationSAS] = None
+ """Emoji or number comparison data for verification."""
+
+
+class VerificationCancelResponse(BaseModel):
+ session: Session
+ """Current app sign-in and encrypted messaging setup state."""
+
+ verification: Optional[Verification] = None
+ """Trusted device verification progress."""
diff --git a/src/beeper_desktop_api/types/app/setup/verification_create_params.py b/src/beeper_desktop_api/types/app/setup/verification_create_params.py
new file mode 100644
index 0000000..aa62c51
--- /dev/null
+++ b/src/beeper_desktop_api/types/app/setup/verification_create_params.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Annotated, TypedDict
+
+from ...._utils import PropertyInfo
+
+__all__ = ["VerificationCreateParams"]
+
+
+class VerificationCreateParams(TypedDict, total=False):
+ purpose: Literal["login", "device"]
+ """Why this verification is being started."""
+
+ user_id: Annotated[str, PropertyInfo(alias="userID")]
+ """Beeper user ID to verify. Defaults to the signed-in user."""
diff --git a/src/beeper_desktop_api/types/app/setup/verification_create_response.py b/src/beeper_desktop_api/types/app/setup/verification_create_response.py
new file mode 100644
index 0000000..51e4a89
--- /dev/null
+++ b/src/beeper_desktop_api/types/app/setup/verification_create_response.py
@@ -0,0 +1,276 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+
+__all__ = [
+ "VerificationCreateResponse",
+ "Session",
+ "SessionE2EE",
+ "SessionE2EESecrets",
+ "SessionMatrix",
+ "SessionVerification",
+ "SessionVerificationError",
+ "SessionVerificationOtherDevice",
+ "SessionVerificationQR",
+ "SessionVerificationSAS",
+ "Verification",
+ "VerificationError",
+ "VerificationOtherDevice",
+ "VerificationQR",
+ "VerificationSAS",
+]
+
+
+class SessionE2EESecrets(BaseModel):
+ """Encrypted messaging keys available on this device."""
+
+ master_key: bool = FieldInfo(alias="masterKey")
+ """Whether the account identity key is available."""
+
+ megolm_backup_key: bool = FieldInfo(alias="megolmBackupKey")
+ """Whether the encrypted message backup key is available."""
+
+ recovery_key: bool = FieldInfo(alias="recoveryKey")
+ """Whether a recovery key is available."""
+
+ self_signing_key: bool = FieldInfo(alias="selfSigningKey")
+ """Whether the device trust key is available."""
+
+ user_signing_key: bool = FieldInfo(alias="userSigningKey")
+ """Whether the user trust key is available."""
+
+
+class SessionE2EE(BaseModel):
+ """Encrypted messaging setup status."""
+
+ cross_signing: bool = FieldInfo(alias="crossSigning")
+ """Whether this account can verify trusted devices."""
+
+ first_sync_done: bool = FieldInfo(alias="firstSyncDone")
+ """Whether the first encrypted message sync is complete."""
+
+ has_backed_up_recovery_key: bool = FieldInfo(alias="hasBackedUpRecoveryKey")
+ """Whether the user confirmed that they saved their recovery key."""
+
+ initialized: bool
+ """Whether encrypted messaging setup has started."""
+
+ key_backup: bool = FieldInfo(alias="keyBackup")
+ """Whether encrypted message backup is available."""
+
+ secrets: SessionE2EESecrets
+ """Encrypted messaging keys available on this device."""
+
+ secret_storage: bool = FieldInfo(alias="secretStorage")
+ """Whether secure key storage is available."""
+
+ verified: bool
+ """Whether this device is trusted for encrypted messages."""
+
+ recovery_key_generated_at: Optional[float] = FieldInfo(alias="recoveryKeyGeneratedAt", default=None)
+ """Unix timestamp for when the recovery key was created."""
+
+
+class SessionMatrix(BaseModel):
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ device_id: str = FieldInfo(alias="deviceID")
+ """Current device ID."""
+
+ homeserver: str
+ """Beeper homeserver URL for this account."""
+
+ user_id: str = FieldInfo(alias="userID")
+ """Signed-in Beeper user ID."""
+
+
+class SessionVerificationError(BaseModel):
+ """Verification error details, if verification stopped."""
+
+ code: str
+ """Verification error code."""
+
+ reason: str
+ """User-facing verification error message."""
+
+
+class SessionVerificationOtherDevice(BaseModel):
+ """Other device participating in verification."""
+
+ id: str
+ """Other device ID."""
+
+ name: Optional[str] = None
+ """Other device display name, if known."""
+
+
+class SessionVerificationQR(BaseModel):
+ """QR verification data."""
+
+ data: str
+ """QR code payload to display for verification."""
+
+
+class SessionVerificationSAS(BaseModel):
+ """Emoji or number comparison data for verification."""
+
+ emojis: str
+ """Emoji sequence to compare on both devices."""
+
+ decimals: Optional[str] = None
+ """Number sequence to compare on both devices."""
+
+
+class SessionVerification(BaseModel):
+ """Trusted device verification progress."""
+
+ id: str
+ """Verification ID to pass in verification action paths."""
+
+ available_actions: List[Literal["accept", "cancel", "qr.confirmScanned", "sas.start", "sas.confirm"]] = FieldInfo(
+ alias="availableActions"
+ )
+ """Verification actions that are valid for the current state."""
+
+ direction: Literal["incoming", "outgoing"]
+ """Whether this device started or received the verification."""
+
+ methods: List[Literal["qr", "sas"]]
+ """Verification methods supported for this transaction."""
+
+ purpose: Literal["login", "device"]
+ """Why this verification exists."""
+
+ state: Literal["requested", "ready", "sas_ready", "qr_scanned", "done", "cancelled", "error"]
+ """Current trusted-device verification state."""
+
+ error: Optional[SessionVerificationError] = None
+ """Verification error details, if verification stopped."""
+
+ other_device: Optional[SessionVerificationOtherDevice] = FieldInfo(alias="otherDevice", default=None)
+ """Other device participating in verification."""
+
+ other_user_id: Optional[str] = FieldInfo(alias="otherUserID", default=None)
+ """Other Beeper user participating in verification."""
+
+ qr: Optional[SessionVerificationQR] = None
+ """QR verification data."""
+
+ sas: Optional[SessionVerificationSAS] = None
+ """Emoji or number comparison data for verification."""
+
+
+class Session(BaseModel):
+ """Current app sign-in and encrypted messaging setup state."""
+
+ e2ee: SessionE2EE
+ """Encrypted messaging setup status."""
+
+ state: Literal[
+ "needs-login",
+ "initializing",
+ "needs-cross-signing-setup",
+ "needs-verification",
+ "needs-secrets",
+ "needs-first-sync",
+ "ready",
+ ]
+ """
+ Current sign-in and encrypted messaging setup state for Beeper Desktop or Beeper
+ Server.
+ """
+
+ matrix: Optional[SessionMatrix] = None
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ verification: Optional[SessionVerification] = None
+ """Trusted device verification progress."""
+
+
+class VerificationError(BaseModel):
+ """Verification error details, if verification stopped."""
+
+ code: str
+ """Verification error code."""
+
+ reason: str
+ """User-facing verification error message."""
+
+
+class VerificationOtherDevice(BaseModel):
+ """Other device participating in verification."""
+
+ id: str
+ """Other device ID."""
+
+ name: Optional[str] = None
+ """Other device display name, if known."""
+
+
+class VerificationQR(BaseModel):
+ """QR verification data."""
+
+ data: str
+ """QR code payload to display for verification."""
+
+
+class VerificationSAS(BaseModel):
+ """Emoji or number comparison data for verification."""
+
+ emojis: str
+ """Emoji sequence to compare on both devices."""
+
+ decimals: Optional[str] = None
+ """Number sequence to compare on both devices."""
+
+
+class Verification(BaseModel):
+ """Trusted device verification progress."""
+
+ id: str
+ """Verification ID to pass in verification action paths."""
+
+ available_actions: List[Literal["accept", "cancel", "qr.confirmScanned", "sas.start", "sas.confirm"]] = FieldInfo(
+ alias="availableActions"
+ )
+ """Verification actions that are valid for the current state."""
+
+ direction: Literal["incoming", "outgoing"]
+ """Whether this device started or received the verification."""
+
+ methods: List[Literal["qr", "sas"]]
+ """Verification methods supported for this transaction."""
+
+ purpose: Literal["login", "device"]
+ """Why this verification exists."""
+
+ state: Literal["requested", "ready", "sas_ready", "qr_scanned", "done", "cancelled", "error"]
+ """Current trusted-device verification state."""
+
+ error: Optional[VerificationError] = None
+ """Verification error details, if verification stopped."""
+
+ other_device: Optional[VerificationOtherDevice] = FieldInfo(alias="otherDevice", default=None)
+ """Other device participating in verification."""
+
+ other_user_id: Optional[str] = FieldInfo(alias="otherUserID", default=None)
+ """Other Beeper user participating in verification."""
+
+ qr: Optional[VerificationQR] = None
+ """QR verification data."""
+
+ sas: Optional[VerificationSAS] = None
+ """Emoji or number comparison data for verification."""
+
+
+class VerificationCreateResponse(BaseModel):
+ session: Session
+ """Current app sign-in and encrypted messaging setup state."""
+
+ verification: Optional[Verification] = None
+ """Trusted device verification progress."""
diff --git a/src/beeper_desktop_api/types/app/setup/verification_list_response.py b/src/beeper_desktop_api/types/app/setup/verification_list_response.py
new file mode 100644
index 0000000..3ca325a
--- /dev/null
+++ b/src/beeper_desktop_api/types/app/setup/verification_list_response.py
@@ -0,0 +1,90 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+
+__all__ = ["VerificationListResponse", "Item", "ItemError", "ItemOtherDevice", "ItemQR", "ItemSAS"]
+
+
+class ItemError(BaseModel):
+ """Verification error details, if verification stopped."""
+
+ code: str
+ """Verification error code."""
+
+ reason: str
+ """User-facing verification error message."""
+
+
+class ItemOtherDevice(BaseModel):
+ """Other device participating in verification."""
+
+ id: str
+ """Other device ID."""
+
+ name: Optional[str] = None
+ """Other device display name, if known."""
+
+
+class ItemQR(BaseModel):
+ """QR verification data."""
+
+ data: str
+ """QR code payload to display for verification."""
+
+
+class ItemSAS(BaseModel):
+ """Emoji or number comparison data for verification."""
+
+ emojis: str
+ """Emoji sequence to compare on both devices."""
+
+ decimals: Optional[str] = None
+ """Number sequence to compare on both devices."""
+
+
+class Item(BaseModel):
+ """Trusted device verification progress."""
+
+ id: str
+ """Verification ID to pass in verification action paths."""
+
+ available_actions: List[Literal["accept", "cancel", "qr.confirmScanned", "sas.start", "sas.confirm"]] = FieldInfo(
+ alias="availableActions"
+ )
+ """Verification actions that are valid for the current state."""
+
+ direction: Literal["incoming", "outgoing"]
+ """Whether this device started or received the verification."""
+
+ methods: List[Literal["qr", "sas"]]
+ """Verification methods supported for this transaction."""
+
+ purpose: Literal["login", "device"]
+ """Why this verification exists."""
+
+ state: Literal["requested", "ready", "sas_ready", "qr_scanned", "done", "cancelled", "error"]
+ """Current trusted-device verification state."""
+
+ error: Optional[ItemError] = None
+ """Verification error details, if verification stopped."""
+
+ other_device: Optional[ItemOtherDevice] = FieldInfo(alias="otherDevice", default=None)
+ """Other device participating in verification."""
+
+ other_user_id: Optional[str] = FieldInfo(alias="otherUserID", default=None)
+ """Other Beeper user participating in verification."""
+
+ qr: Optional[ItemQR] = None
+ """QR verification data."""
+
+ sas: Optional[ItemSAS] = None
+ """Emoji or number comparison data for verification."""
+
+
+class VerificationListResponse(BaseModel):
+ items: List[Item]
diff --git a/src/beeper_desktop_api/types/app/setup/verification_retrieve_response.py b/src/beeper_desktop_api/types/app/setup/verification_retrieve_response.py
new file mode 100644
index 0000000..71354a7
--- /dev/null
+++ b/src/beeper_desktop_api/types/app/setup/verification_retrieve_response.py
@@ -0,0 +1,276 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+
+__all__ = [
+ "VerificationRetrieveResponse",
+ "Session",
+ "SessionE2EE",
+ "SessionE2EESecrets",
+ "SessionMatrix",
+ "SessionVerification",
+ "SessionVerificationError",
+ "SessionVerificationOtherDevice",
+ "SessionVerificationQR",
+ "SessionVerificationSAS",
+ "Verification",
+ "VerificationError",
+ "VerificationOtherDevice",
+ "VerificationQR",
+ "VerificationSAS",
+]
+
+
+class SessionE2EESecrets(BaseModel):
+ """Encrypted messaging keys available on this device."""
+
+ master_key: bool = FieldInfo(alias="masterKey")
+ """Whether the account identity key is available."""
+
+ megolm_backup_key: bool = FieldInfo(alias="megolmBackupKey")
+ """Whether the encrypted message backup key is available."""
+
+ recovery_key: bool = FieldInfo(alias="recoveryKey")
+ """Whether a recovery key is available."""
+
+ self_signing_key: bool = FieldInfo(alias="selfSigningKey")
+ """Whether the device trust key is available."""
+
+ user_signing_key: bool = FieldInfo(alias="userSigningKey")
+ """Whether the user trust key is available."""
+
+
+class SessionE2EE(BaseModel):
+ """Encrypted messaging setup status."""
+
+ cross_signing: bool = FieldInfo(alias="crossSigning")
+ """Whether this account can verify trusted devices."""
+
+ first_sync_done: bool = FieldInfo(alias="firstSyncDone")
+ """Whether the first encrypted message sync is complete."""
+
+ has_backed_up_recovery_key: bool = FieldInfo(alias="hasBackedUpRecoveryKey")
+ """Whether the user confirmed that they saved their recovery key."""
+
+ initialized: bool
+ """Whether encrypted messaging setup has started."""
+
+ key_backup: bool = FieldInfo(alias="keyBackup")
+ """Whether encrypted message backup is available."""
+
+ secrets: SessionE2EESecrets
+ """Encrypted messaging keys available on this device."""
+
+ secret_storage: bool = FieldInfo(alias="secretStorage")
+ """Whether secure key storage is available."""
+
+ verified: bool
+ """Whether this device is trusted for encrypted messages."""
+
+ recovery_key_generated_at: Optional[float] = FieldInfo(alias="recoveryKeyGeneratedAt", default=None)
+ """Unix timestamp for when the recovery key was created."""
+
+
+class SessionMatrix(BaseModel):
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ device_id: str = FieldInfo(alias="deviceID")
+ """Current device ID."""
+
+ homeserver: str
+ """Beeper homeserver URL for this account."""
+
+ user_id: str = FieldInfo(alias="userID")
+ """Signed-in Beeper user ID."""
+
+
+class SessionVerificationError(BaseModel):
+ """Verification error details, if verification stopped."""
+
+ code: str
+ """Verification error code."""
+
+ reason: str
+ """User-facing verification error message."""
+
+
+class SessionVerificationOtherDevice(BaseModel):
+ """Other device participating in verification."""
+
+ id: str
+ """Other device ID."""
+
+ name: Optional[str] = None
+ """Other device display name, if known."""
+
+
+class SessionVerificationQR(BaseModel):
+ """QR verification data."""
+
+ data: str
+ """QR code payload to display for verification."""
+
+
+class SessionVerificationSAS(BaseModel):
+ """Emoji or number comparison data for verification."""
+
+ emojis: str
+ """Emoji sequence to compare on both devices."""
+
+ decimals: Optional[str] = None
+ """Number sequence to compare on both devices."""
+
+
+class SessionVerification(BaseModel):
+ """Trusted device verification progress."""
+
+ id: str
+ """Verification ID to pass in verification action paths."""
+
+ available_actions: List[Literal["accept", "cancel", "qr.confirmScanned", "sas.start", "sas.confirm"]] = FieldInfo(
+ alias="availableActions"
+ )
+ """Verification actions that are valid for the current state."""
+
+ direction: Literal["incoming", "outgoing"]
+ """Whether this device started or received the verification."""
+
+ methods: List[Literal["qr", "sas"]]
+ """Verification methods supported for this transaction."""
+
+ purpose: Literal["login", "device"]
+ """Why this verification exists."""
+
+ state: Literal["requested", "ready", "sas_ready", "qr_scanned", "done", "cancelled", "error"]
+ """Current trusted-device verification state."""
+
+ error: Optional[SessionVerificationError] = None
+ """Verification error details, if verification stopped."""
+
+ other_device: Optional[SessionVerificationOtherDevice] = FieldInfo(alias="otherDevice", default=None)
+ """Other device participating in verification."""
+
+ other_user_id: Optional[str] = FieldInfo(alias="otherUserID", default=None)
+ """Other Beeper user participating in verification."""
+
+ qr: Optional[SessionVerificationQR] = None
+ """QR verification data."""
+
+ sas: Optional[SessionVerificationSAS] = None
+ """Emoji or number comparison data for verification."""
+
+
+class Session(BaseModel):
+ """Current app sign-in and encrypted messaging setup state."""
+
+ e2ee: SessionE2EE
+ """Encrypted messaging setup status."""
+
+ state: Literal[
+ "needs-login",
+ "initializing",
+ "needs-cross-signing-setup",
+ "needs-verification",
+ "needs-secrets",
+ "needs-first-sync",
+ "ready",
+ ]
+ """
+ Current sign-in and encrypted messaging setup state for Beeper Desktop or Beeper
+ Server.
+ """
+
+ matrix: Optional[SessionMatrix] = None
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ verification: Optional[SessionVerification] = None
+ """Trusted device verification progress."""
+
+
+class VerificationError(BaseModel):
+ """Verification error details, if verification stopped."""
+
+ code: str
+ """Verification error code."""
+
+ reason: str
+ """User-facing verification error message."""
+
+
+class VerificationOtherDevice(BaseModel):
+ """Other device participating in verification."""
+
+ id: str
+ """Other device ID."""
+
+ name: Optional[str] = None
+ """Other device display name, if known."""
+
+
+class VerificationQR(BaseModel):
+ """QR verification data."""
+
+ data: str
+ """QR code payload to display for verification."""
+
+
+class VerificationSAS(BaseModel):
+ """Emoji or number comparison data for verification."""
+
+ emojis: str
+ """Emoji sequence to compare on both devices."""
+
+ decimals: Optional[str] = None
+ """Number sequence to compare on both devices."""
+
+
+class Verification(BaseModel):
+ """Trusted device verification progress."""
+
+ id: str
+ """Verification ID to pass in verification action paths."""
+
+ available_actions: List[Literal["accept", "cancel", "qr.confirmScanned", "sas.start", "sas.confirm"]] = FieldInfo(
+ alias="availableActions"
+ )
+ """Verification actions that are valid for the current state."""
+
+ direction: Literal["incoming", "outgoing"]
+ """Whether this device started or received the verification."""
+
+ methods: List[Literal["qr", "sas"]]
+ """Verification methods supported for this transaction."""
+
+ purpose: Literal["login", "device"]
+ """Why this verification exists."""
+
+ state: Literal["requested", "ready", "sas_ready", "qr_scanned", "done", "cancelled", "error"]
+ """Current trusted-device verification state."""
+
+ error: Optional[VerificationError] = None
+ """Verification error details, if verification stopped."""
+
+ other_device: Optional[VerificationOtherDevice] = FieldInfo(alias="otherDevice", default=None)
+ """Other device participating in verification."""
+
+ other_user_id: Optional[str] = FieldInfo(alias="otherUserID", default=None)
+ """Other Beeper user participating in verification."""
+
+ qr: Optional[VerificationQR] = None
+ """QR verification data."""
+
+ sas: Optional[VerificationSAS] = None
+ """Emoji or number comparison data for verification."""
+
+
+class VerificationRetrieveResponse(BaseModel):
+ session: Session
+ """Current app sign-in and encrypted messaging setup state."""
+
+ verification: Optional[Verification] = None
+ """Trusted device verification progress."""
diff --git a/src/beeper_desktop_api/types/app/setup/verifications/__init__.py b/src/beeper_desktop_api/types/app/setup/verifications/__init__.py
new file mode 100644
index 0000000..1a45582
--- /dev/null
+++ b/src/beeper_desktop_api/types/app/setup/verifications/__init__.py
@@ -0,0 +1,9 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .qr_scan_params import QRScanParams as QRScanParams
+from .qr_scan_response import QRScanResponse as QRScanResponse
+from .sas_start_response import SASStartResponse as SASStartResponse
+from .sas_confirm_response import SASConfirmResponse as SASConfirmResponse
+from .qr_confirm_scanned_response import QRConfirmScannedResponse as QRConfirmScannedResponse
diff --git a/src/beeper_desktop_api/types/app/setup/verifications/qr_confirm_scanned_response.py b/src/beeper_desktop_api/types/app/setup/verifications/qr_confirm_scanned_response.py
new file mode 100644
index 0000000..472350a
--- /dev/null
+++ b/src/beeper_desktop_api/types/app/setup/verifications/qr_confirm_scanned_response.py
@@ -0,0 +1,276 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ....._models import BaseModel
+
+__all__ = [
+ "QRConfirmScannedResponse",
+ "Session",
+ "SessionE2EE",
+ "SessionE2EESecrets",
+ "SessionMatrix",
+ "SessionVerification",
+ "SessionVerificationError",
+ "SessionVerificationOtherDevice",
+ "SessionVerificationQR",
+ "SessionVerificationSAS",
+ "Verification",
+ "VerificationError",
+ "VerificationOtherDevice",
+ "VerificationQR",
+ "VerificationSAS",
+]
+
+
+class SessionE2EESecrets(BaseModel):
+ """Encrypted messaging keys available on this device."""
+
+ master_key: bool = FieldInfo(alias="masterKey")
+ """Whether the account identity key is available."""
+
+ megolm_backup_key: bool = FieldInfo(alias="megolmBackupKey")
+ """Whether the encrypted message backup key is available."""
+
+ recovery_key: bool = FieldInfo(alias="recoveryKey")
+ """Whether a recovery key is available."""
+
+ self_signing_key: bool = FieldInfo(alias="selfSigningKey")
+ """Whether the device trust key is available."""
+
+ user_signing_key: bool = FieldInfo(alias="userSigningKey")
+ """Whether the user trust key is available."""
+
+
+class SessionE2EE(BaseModel):
+ """Encrypted messaging setup status."""
+
+ cross_signing: bool = FieldInfo(alias="crossSigning")
+ """Whether this account can verify trusted devices."""
+
+ first_sync_done: bool = FieldInfo(alias="firstSyncDone")
+ """Whether the first encrypted message sync is complete."""
+
+ has_backed_up_recovery_key: bool = FieldInfo(alias="hasBackedUpRecoveryKey")
+ """Whether the user confirmed that they saved their recovery key."""
+
+ initialized: bool
+ """Whether encrypted messaging setup has started."""
+
+ key_backup: bool = FieldInfo(alias="keyBackup")
+ """Whether encrypted message backup is available."""
+
+ secrets: SessionE2EESecrets
+ """Encrypted messaging keys available on this device."""
+
+ secret_storage: bool = FieldInfo(alias="secretStorage")
+ """Whether secure key storage is available."""
+
+ verified: bool
+ """Whether this device is trusted for encrypted messages."""
+
+ recovery_key_generated_at: Optional[float] = FieldInfo(alias="recoveryKeyGeneratedAt", default=None)
+ """Unix timestamp for when the recovery key was created."""
+
+
+class SessionMatrix(BaseModel):
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ device_id: str = FieldInfo(alias="deviceID")
+ """Current device ID."""
+
+ homeserver: str
+ """Beeper homeserver URL for this account."""
+
+ user_id: str = FieldInfo(alias="userID")
+ """Signed-in Beeper user ID."""
+
+
+class SessionVerificationError(BaseModel):
+ """Verification error details, if verification stopped."""
+
+ code: str
+ """Verification error code."""
+
+ reason: str
+ """User-facing verification error message."""
+
+
+class SessionVerificationOtherDevice(BaseModel):
+ """Other device participating in verification."""
+
+ id: str
+ """Other device ID."""
+
+ name: Optional[str] = None
+ """Other device display name, if known."""
+
+
+class SessionVerificationQR(BaseModel):
+ """QR verification data."""
+
+ data: str
+ """QR code payload to display for verification."""
+
+
+class SessionVerificationSAS(BaseModel):
+ """Emoji or number comparison data for verification."""
+
+ emojis: str
+ """Emoji sequence to compare on both devices."""
+
+ decimals: Optional[str] = None
+ """Number sequence to compare on both devices."""
+
+
+class SessionVerification(BaseModel):
+ """Trusted device verification progress."""
+
+ id: str
+ """Verification ID to pass in verification action paths."""
+
+ available_actions: List[Literal["accept", "cancel", "qr.confirmScanned", "sas.start", "sas.confirm"]] = FieldInfo(
+ alias="availableActions"
+ )
+ """Verification actions that are valid for the current state."""
+
+ direction: Literal["incoming", "outgoing"]
+ """Whether this device started or received the verification."""
+
+ methods: List[Literal["qr", "sas"]]
+ """Verification methods supported for this transaction."""
+
+ purpose: Literal["login", "device"]
+ """Why this verification exists."""
+
+ state: Literal["requested", "ready", "sas_ready", "qr_scanned", "done", "cancelled", "error"]
+ """Current trusted-device verification state."""
+
+ error: Optional[SessionVerificationError] = None
+ """Verification error details, if verification stopped."""
+
+ other_device: Optional[SessionVerificationOtherDevice] = FieldInfo(alias="otherDevice", default=None)
+ """Other device participating in verification."""
+
+ other_user_id: Optional[str] = FieldInfo(alias="otherUserID", default=None)
+ """Other Beeper user participating in verification."""
+
+ qr: Optional[SessionVerificationQR] = None
+ """QR verification data."""
+
+ sas: Optional[SessionVerificationSAS] = None
+ """Emoji or number comparison data for verification."""
+
+
+class Session(BaseModel):
+ """Current app sign-in and encrypted messaging setup state."""
+
+ e2ee: SessionE2EE
+ """Encrypted messaging setup status."""
+
+ state: Literal[
+ "needs-login",
+ "initializing",
+ "needs-cross-signing-setup",
+ "needs-verification",
+ "needs-secrets",
+ "needs-first-sync",
+ "ready",
+ ]
+ """
+ Current sign-in and encrypted messaging setup state for Beeper Desktop or Beeper
+ Server.
+ """
+
+ matrix: Optional[SessionMatrix] = None
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ verification: Optional[SessionVerification] = None
+ """Trusted device verification progress."""
+
+
+class VerificationError(BaseModel):
+ """Verification error details, if verification stopped."""
+
+ code: str
+ """Verification error code."""
+
+ reason: str
+ """User-facing verification error message."""
+
+
+class VerificationOtherDevice(BaseModel):
+ """Other device participating in verification."""
+
+ id: str
+ """Other device ID."""
+
+ name: Optional[str] = None
+ """Other device display name, if known."""
+
+
+class VerificationQR(BaseModel):
+ """QR verification data."""
+
+ data: str
+ """QR code payload to display for verification."""
+
+
+class VerificationSAS(BaseModel):
+ """Emoji or number comparison data for verification."""
+
+ emojis: str
+ """Emoji sequence to compare on both devices."""
+
+ decimals: Optional[str] = None
+ """Number sequence to compare on both devices."""
+
+
+class Verification(BaseModel):
+ """Trusted device verification progress."""
+
+ id: str
+ """Verification ID to pass in verification action paths."""
+
+ available_actions: List[Literal["accept", "cancel", "qr.confirmScanned", "sas.start", "sas.confirm"]] = FieldInfo(
+ alias="availableActions"
+ )
+ """Verification actions that are valid for the current state."""
+
+ direction: Literal["incoming", "outgoing"]
+ """Whether this device started or received the verification."""
+
+ methods: List[Literal["qr", "sas"]]
+ """Verification methods supported for this transaction."""
+
+ purpose: Literal["login", "device"]
+ """Why this verification exists."""
+
+ state: Literal["requested", "ready", "sas_ready", "qr_scanned", "done", "cancelled", "error"]
+ """Current trusted-device verification state."""
+
+ error: Optional[VerificationError] = None
+ """Verification error details, if verification stopped."""
+
+ other_device: Optional[VerificationOtherDevice] = FieldInfo(alias="otherDevice", default=None)
+ """Other device participating in verification."""
+
+ other_user_id: Optional[str] = FieldInfo(alias="otherUserID", default=None)
+ """Other Beeper user participating in verification."""
+
+ qr: Optional[VerificationQR] = None
+ """QR verification data."""
+
+ sas: Optional[VerificationSAS] = None
+ """Emoji or number comparison data for verification."""
+
+
+class QRConfirmScannedResponse(BaseModel):
+ session: Session
+ """Current app sign-in and encrypted messaging setup state."""
+
+ verification: Optional[Verification] = None
+ """Trusted device verification progress."""
diff --git a/src/beeper_desktop_api/types/app/setup/verifications/qr_scan_params.py b/src/beeper_desktop_api/types/app/setup/verifications/qr_scan_params.py
new file mode 100644
index 0000000..b244e78
--- /dev/null
+++ b/src/beeper_desktop_api/types/app/setup/verifications/qr_scan_params.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["QRScanParams"]
+
+
+class QRScanParams(TypedDict, total=False):
+ data: Required[str]
+ """QR code payload scanned from the other device."""
diff --git a/src/beeper_desktop_api/types/app/setup/verifications/qr_scan_response.py b/src/beeper_desktop_api/types/app/setup/verifications/qr_scan_response.py
new file mode 100644
index 0000000..c4bb77b
--- /dev/null
+++ b/src/beeper_desktop_api/types/app/setup/verifications/qr_scan_response.py
@@ -0,0 +1,276 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ....._models import BaseModel
+
+__all__ = [
+ "QRScanResponse",
+ "Session",
+ "SessionE2EE",
+ "SessionE2EESecrets",
+ "SessionMatrix",
+ "SessionVerification",
+ "SessionVerificationError",
+ "SessionVerificationOtherDevice",
+ "SessionVerificationQR",
+ "SessionVerificationSAS",
+ "Verification",
+ "VerificationError",
+ "VerificationOtherDevice",
+ "VerificationQR",
+ "VerificationSAS",
+]
+
+
+class SessionE2EESecrets(BaseModel):
+ """Encrypted messaging keys available on this device."""
+
+ master_key: bool = FieldInfo(alias="masterKey")
+ """Whether the account identity key is available."""
+
+ megolm_backup_key: bool = FieldInfo(alias="megolmBackupKey")
+ """Whether the encrypted message backup key is available."""
+
+ recovery_key: bool = FieldInfo(alias="recoveryKey")
+ """Whether a recovery key is available."""
+
+ self_signing_key: bool = FieldInfo(alias="selfSigningKey")
+ """Whether the device trust key is available."""
+
+ user_signing_key: bool = FieldInfo(alias="userSigningKey")
+ """Whether the user trust key is available."""
+
+
+class SessionE2EE(BaseModel):
+ """Encrypted messaging setup status."""
+
+ cross_signing: bool = FieldInfo(alias="crossSigning")
+ """Whether this account can verify trusted devices."""
+
+ first_sync_done: bool = FieldInfo(alias="firstSyncDone")
+ """Whether the first encrypted message sync is complete."""
+
+ has_backed_up_recovery_key: bool = FieldInfo(alias="hasBackedUpRecoveryKey")
+ """Whether the user confirmed that they saved their recovery key."""
+
+ initialized: bool
+ """Whether encrypted messaging setup has started."""
+
+ key_backup: bool = FieldInfo(alias="keyBackup")
+ """Whether encrypted message backup is available."""
+
+ secrets: SessionE2EESecrets
+ """Encrypted messaging keys available on this device."""
+
+ secret_storage: bool = FieldInfo(alias="secretStorage")
+ """Whether secure key storage is available."""
+
+ verified: bool
+ """Whether this device is trusted for encrypted messages."""
+
+ recovery_key_generated_at: Optional[float] = FieldInfo(alias="recoveryKeyGeneratedAt", default=None)
+ """Unix timestamp for when the recovery key was created."""
+
+
+class SessionMatrix(BaseModel):
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ device_id: str = FieldInfo(alias="deviceID")
+ """Current device ID."""
+
+ homeserver: str
+ """Beeper homeserver URL for this account."""
+
+ user_id: str = FieldInfo(alias="userID")
+ """Signed-in Beeper user ID."""
+
+
+class SessionVerificationError(BaseModel):
+ """Verification error details, if verification stopped."""
+
+ code: str
+ """Verification error code."""
+
+ reason: str
+ """User-facing verification error message."""
+
+
+class SessionVerificationOtherDevice(BaseModel):
+ """Other device participating in verification."""
+
+ id: str
+ """Other device ID."""
+
+ name: Optional[str] = None
+ """Other device display name, if known."""
+
+
+class SessionVerificationQR(BaseModel):
+ """QR verification data."""
+
+ data: str
+ """QR code payload to display for verification."""
+
+
+class SessionVerificationSAS(BaseModel):
+ """Emoji or number comparison data for verification."""
+
+ emojis: str
+ """Emoji sequence to compare on both devices."""
+
+ decimals: Optional[str] = None
+ """Number sequence to compare on both devices."""
+
+
+class SessionVerification(BaseModel):
+ """Trusted device verification progress."""
+
+ id: str
+ """Verification ID to pass in verification action paths."""
+
+ available_actions: List[Literal["accept", "cancel", "qr.confirmScanned", "sas.start", "sas.confirm"]] = FieldInfo(
+ alias="availableActions"
+ )
+ """Verification actions that are valid for the current state."""
+
+ direction: Literal["incoming", "outgoing"]
+ """Whether this device started or received the verification."""
+
+ methods: List[Literal["qr", "sas"]]
+ """Verification methods supported for this transaction."""
+
+ purpose: Literal["login", "device"]
+ """Why this verification exists."""
+
+ state: Literal["requested", "ready", "sas_ready", "qr_scanned", "done", "cancelled", "error"]
+ """Current trusted-device verification state."""
+
+ error: Optional[SessionVerificationError] = None
+ """Verification error details, if verification stopped."""
+
+ other_device: Optional[SessionVerificationOtherDevice] = FieldInfo(alias="otherDevice", default=None)
+ """Other device participating in verification."""
+
+ other_user_id: Optional[str] = FieldInfo(alias="otherUserID", default=None)
+ """Other Beeper user participating in verification."""
+
+ qr: Optional[SessionVerificationQR] = None
+ """QR verification data."""
+
+ sas: Optional[SessionVerificationSAS] = None
+ """Emoji or number comparison data for verification."""
+
+
+class Session(BaseModel):
+ """Current app sign-in and encrypted messaging setup state."""
+
+ e2ee: SessionE2EE
+ """Encrypted messaging setup status."""
+
+ state: Literal[
+ "needs-login",
+ "initializing",
+ "needs-cross-signing-setup",
+ "needs-verification",
+ "needs-secrets",
+ "needs-first-sync",
+ "ready",
+ ]
+ """
+ Current sign-in and encrypted messaging setup state for Beeper Desktop or Beeper
+ Server.
+ """
+
+ matrix: Optional[SessionMatrix] = None
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ verification: Optional[SessionVerification] = None
+ """Trusted device verification progress."""
+
+
+class VerificationError(BaseModel):
+ """Verification error details, if verification stopped."""
+
+ code: str
+ """Verification error code."""
+
+ reason: str
+ """User-facing verification error message."""
+
+
+class VerificationOtherDevice(BaseModel):
+ """Other device participating in verification."""
+
+ id: str
+ """Other device ID."""
+
+ name: Optional[str] = None
+ """Other device display name, if known."""
+
+
+class VerificationQR(BaseModel):
+ """QR verification data."""
+
+ data: str
+ """QR code payload to display for verification."""
+
+
+class VerificationSAS(BaseModel):
+ """Emoji or number comparison data for verification."""
+
+ emojis: str
+ """Emoji sequence to compare on both devices."""
+
+ decimals: Optional[str] = None
+ """Number sequence to compare on both devices."""
+
+
+class Verification(BaseModel):
+ """Trusted device verification progress."""
+
+ id: str
+ """Verification ID to pass in verification action paths."""
+
+ available_actions: List[Literal["accept", "cancel", "qr.confirmScanned", "sas.start", "sas.confirm"]] = FieldInfo(
+ alias="availableActions"
+ )
+ """Verification actions that are valid for the current state."""
+
+ direction: Literal["incoming", "outgoing"]
+ """Whether this device started or received the verification."""
+
+ methods: List[Literal["qr", "sas"]]
+ """Verification methods supported for this transaction."""
+
+ purpose: Literal["login", "device"]
+ """Why this verification exists."""
+
+ state: Literal["requested", "ready", "sas_ready", "qr_scanned", "done", "cancelled", "error"]
+ """Current trusted-device verification state."""
+
+ error: Optional[VerificationError] = None
+ """Verification error details, if verification stopped."""
+
+ other_device: Optional[VerificationOtherDevice] = FieldInfo(alias="otherDevice", default=None)
+ """Other device participating in verification."""
+
+ other_user_id: Optional[str] = FieldInfo(alias="otherUserID", default=None)
+ """Other Beeper user participating in verification."""
+
+ qr: Optional[VerificationQR] = None
+ """QR verification data."""
+
+ sas: Optional[VerificationSAS] = None
+ """Emoji or number comparison data for verification."""
+
+
+class QRScanResponse(BaseModel):
+ session: Session
+ """Current app sign-in and encrypted messaging setup state."""
+
+ verification: Optional[Verification] = None
+ """Trusted device verification progress."""
diff --git a/src/beeper_desktop_api/types/app/setup/verifications/sas_confirm_response.py b/src/beeper_desktop_api/types/app/setup/verifications/sas_confirm_response.py
new file mode 100644
index 0000000..23f30e8
--- /dev/null
+++ b/src/beeper_desktop_api/types/app/setup/verifications/sas_confirm_response.py
@@ -0,0 +1,276 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ....._models import BaseModel
+
+__all__ = [
+ "SASConfirmResponse",
+ "Session",
+ "SessionE2EE",
+ "SessionE2EESecrets",
+ "SessionMatrix",
+ "SessionVerification",
+ "SessionVerificationError",
+ "SessionVerificationOtherDevice",
+ "SessionVerificationQR",
+ "SessionVerificationSAS",
+ "Verification",
+ "VerificationError",
+ "VerificationOtherDevice",
+ "VerificationQR",
+ "VerificationSAS",
+]
+
+
+class SessionE2EESecrets(BaseModel):
+ """Encrypted messaging keys available on this device."""
+
+ master_key: bool = FieldInfo(alias="masterKey")
+ """Whether the account identity key is available."""
+
+ megolm_backup_key: bool = FieldInfo(alias="megolmBackupKey")
+ """Whether the encrypted message backup key is available."""
+
+ recovery_key: bool = FieldInfo(alias="recoveryKey")
+ """Whether a recovery key is available."""
+
+ self_signing_key: bool = FieldInfo(alias="selfSigningKey")
+ """Whether the device trust key is available."""
+
+ user_signing_key: bool = FieldInfo(alias="userSigningKey")
+ """Whether the user trust key is available."""
+
+
+class SessionE2EE(BaseModel):
+ """Encrypted messaging setup status."""
+
+ cross_signing: bool = FieldInfo(alias="crossSigning")
+ """Whether this account can verify trusted devices."""
+
+ first_sync_done: bool = FieldInfo(alias="firstSyncDone")
+ """Whether the first encrypted message sync is complete."""
+
+ has_backed_up_recovery_key: bool = FieldInfo(alias="hasBackedUpRecoveryKey")
+ """Whether the user confirmed that they saved their recovery key."""
+
+ initialized: bool
+ """Whether encrypted messaging setup has started."""
+
+ key_backup: bool = FieldInfo(alias="keyBackup")
+ """Whether encrypted message backup is available."""
+
+ secrets: SessionE2EESecrets
+ """Encrypted messaging keys available on this device."""
+
+ secret_storage: bool = FieldInfo(alias="secretStorage")
+ """Whether secure key storage is available."""
+
+ verified: bool
+ """Whether this device is trusted for encrypted messages."""
+
+ recovery_key_generated_at: Optional[float] = FieldInfo(alias="recoveryKeyGeneratedAt", default=None)
+ """Unix timestamp for when the recovery key was created."""
+
+
+class SessionMatrix(BaseModel):
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ device_id: str = FieldInfo(alias="deviceID")
+ """Current device ID."""
+
+ homeserver: str
+ """Beeper homeserver URL for this account."""
+
+ user_id: str = FieldInfo(alias="userID")
+ """Signed-in Beeper user ID."""
+
+
+class SessionVerificationError(BaseModel):
+ """Verification error details, if verification stopped."""
+
+ code: str
+ """Verification error code."""
+
+ reason: str
+ """User-facing verification error message."""
+
+
+class SessionVerificationOtherDevice(BaseModel):
+ """Other device participating in verification."""
+
+ id: str
+ """Other device ID."""
+
+ name: Optional[str] = None
+ """Other device display name, if known."""
+
+
+class SessionVerificationQR(BaseModel):
+ """QR verification data."""
+
+ data: str
+ """QR code payload to display for verification."""
+
+
+class SessionVerificationSAS(BaseModel):
+ """Emoji or number comparison data for verification."""
+
+ emojis: str
+ """Emoji sequence to compare on both devices."""
+
+ decimals: Optional[str] = None
+ """Number sequence to compare on both devices."""
+
+
+class SessionVerification(BaseModel):
+ """Trusted device verification progress."""
+
+ id: str
+ """Verification ID to pass in verification action paths."""
+
+ available_actions: List[Literal["accept", "cancel", "qr.confirmScanned", "sas.start", "sas.confirm"]] = FieldInfo(
+ alias="availableActions"
+ )
+ """Verification actions that are valid for the current state."""
+
+ direction: Literal["incoming", "outgoing"]
+ """Whether this device started or received the verification."""
+
+ methods: List[Literal["qr", "sas"]]
+ """Verification methods supported for this transaction."""
+
+ purpose: Literal["login", "device"]
+ """Why this verification exists."""
+
+ state: Literal["requested", "ready", "sas_ready", "qr_scanned", "done", "cancelled", "error"]
+ """Current trusted-device verification state."""
+
+ error: Optional[SessionVerificationError] = None
+ """Verification error details, if verification stopped."""
+
+ other_device: Optional[SessionVerificationOtherDevice] = FieldInfo(alias="otherDevice", default=None)
+ """Other device participating in verification."""
+
+ other_user_id: Optional[str] = FieldInfo(alias="otherUserID", default=None)
+ """Other Beeper user participating in verification."""
+
+ qr: Optional[SessionVerificationQR] = None
+ """QR verification data."""
+
+ sas: Optional[SessionVerificationSAS] = None
+ """Emoji or number comparison data for verification."""
+
+
+class Session(BaseModel):
+ """Current app sign-in and encrypted messaging setup state."""
+
+ e2ee: SessionE2EE
+ """Encrypted messaging setup status."""
+
+ state: Literal[
+ "needs-login",
+ "initializing",
+ "needs-cross-signing-setup",
+ "needs-verification",
+ "needs-secrets",
+ "needs-first-sync",
+ "ready",
+ ]
+ """
+ Current sign-in and encrypted messaging setup state for Beeper Desktop or Beeper
+ Server.
+ """
+
+ matrix: Optional[SessionMatrix] = None
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ verification: Optional[SessionVerification] = None
+ """Trusted device verification progress."""
+
+
+class VerificationError(BaseModel):
+ """Verification error details, if verification stopped."""
+
+ code: str
+ """Verification error code."""
+
+ reason: str
+ """User-facing verification error message."""
+
+
+class VerificationOtherDevice(BaseModel):
+ """Other device participating in verification."""
+
+ id: str
+ """Other device ID."""
+
+ name: Optional[str] = None
+ """Other device display name, if known."""
+
+
+class VerificationQR(BaseModel):
+ """QR verification data."""
+
+ data: str
+ """QR code payload to display for verification."""
+
+
+class VerificationSAS(BaseModel):
+ """Emoji or number comparison data for verification."""
+
+ emojis: str
+ """Emoji sequence to compare on both devices."""
+
+ decimals: Optional[str] = None
+ """Number sequence to compare on both devices."""
+
+
+class Verification(BaseModel):
+ """Trusted device verification progress."""
+
+ id: str
+ """Verification ID to pass in verification action paths."""
+
+ available_actions: List[Literal["accept", "cancel", "qr.confirmScanned", "sas.start", "sas.confirm"]] = FieldInfo(
+ alias="availableActions"
+ )
+ """Verification actions that are valid for the current state."""
+
+ direction: Literal["incoming", "outgoing"]
+ """Whether this device started or received the verification."""
+
+ methods: List[Literal["qr", "sas"]]
+ """Verification methods supported for this transaction."""
+
+ purpose: Literal["login", "device"]
+ """Why this verification exists."""
+
+ state: Literal["requested", "ready", "sas_ready", "qr_scanned", "done", "cancelled", "error"]
+ """Current trusted-device verification state."""
+
+ error: Optional[VerificationError] = None
+ """Verification error details, if verification stopped."""
+
+ other_device: Optional[VerificationOtherDevice] = FieldInfo(alias="otherDevice", default=None)
+ """Other device participating in verification."""
+
+ other_user_id: Optional[str] = FieldInfo(alias="otherUserID", default=None)
+ """Other Beeper user participating in verification."""
+
+ qr: Optional[VerificationQR] = None
+ """QR verification data."""
+
+ sas: Optional[VerificationSAS] = None
+ """Emoji or number comparison data for verification."""
+
+
+class SASConfirmResponse(BaseModel):
+ session: Session
+ """Current app sign-in and encrypted messaging setup state."""
+
+ verification: Optional[Verification] = None
+ """Trusted device verification progress."""
diff --git a/src/beeper_desktop_api/types/app/setup/verifications/sas_start_response.py b/src/beeper_desktop_api/types/app/setup/verifications/sas_start_response.py
new file mode 100644
index 0000000..a9237b3
--- /dev/null
+++ b/src/beeper_desktop_api/types/app/setup/verifications/sas_start_response.py
@@ -0,0 +1,276 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ....._models import BaseModel
+
+__all__ = [
+ "SASStartResponse",
+ "Session",
+ "SessionE2EE",
+ "SessionE2EESecrets",
+ "SessionMatrix",
+ "SessionVerification",
+ "SessionVerificationError",
+ "SessionVerificationOtherDevice",
+ "SessionVerificationQR",
+ "SessionVerificationSAS",
+ "Verification",
+ "VerificationError",
+ "VerificationOtherDevice",
+ "VerificationQR",
+ "VerificationSAS",
+]
+
+
+class SessionE2EESecrets(BaseModel):
+ """Encrypted messaging keys available on this device."""
+
+ master_key: bool = FieldInfo(alias="masterKey")
+ """Whether the account identity key is available."""
+
+ megolm_backup_key: bool = FieldInfo(alias="megolmBackupKey")
+ """Whether the encrypted message backup key is available."""
+
+ recovery_key: bool = FieldInfo(alias="recoveryKey")
+ """Whether a recovery key is available."""
+
+ self_signing_key: bool = FieldInfo(alias="selfSigningKey")
+ """Whether the device trust key is available."""
+
+ user_signing_key: bool = FieldInfo(alias="userSigningKey")
+ """Whether the user trust key is available."""
+
+
+class SessionE2EE(BaseModel):
+ """Encrypted messaging setup status."""
+
+ cross_signing: bool = FieldInfo(alias="crossSigning")
+ """Whether this account can verify trusted devices."""
+
+ first_sync_done: bool = FieldInfo(alias="firstSyncDone")
+ """Whether the first encrypted message sync is complete."""
+
+ has_backed_up_recovery_key: bool = FieldInfo(alias="hasBackedUpRecoveryKey")
+ """Whether the user confirmed that they saved their recovery key."""
+
+ initialized: bool
+ """Whether encrypted messaging setup has started."""
+
+ key_backup: bool = FieldInfo(alias="keyBackup")
+ """Whether encrypted message backup is available."""
+
+ secrets: SessionE2EESecrets
+ """Encrypted messaging keys available on this device."""
+
+ secret_storage: bool = FieldInfo(alias="secretStorage")
+ """Whether secure key storage is available."""
+
+ verified: bool
+ """Whether this device is trusted for encrypted messages."""
+
+ recovery_key_generated_at: Optional[float] = FieldInfo(alias="recoveryKeyGeneratedAt", default=None)
+ """Unix timestamp for when the recovery key was created."""
+
+
+class SessionMatrix(BaseModel):
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ device_id: str = FieldInfo(alias="deviceID")
+ """Current device ID."""
+
+ homeserver: str
+ """Beeper homeserver URL for this account."""
+
+ user_id: str = FieldInfo(alias="userID")
+ """Signed-in Beeper user ID."""
+
+
+class SessionVerificationError(BaseModel):
+ """Verification error details, if verification stopped."""
+
+ code: str
+ """Verification error code."""
+
+ reason: str
+ """User-facing verification error message."""
+
+
+class SessionVerificationOtherDevice(BaseModel):
+ """Other device participating in verification."""
+
+ id: str
+ """Other device ID."""
+
+ name: Optional[str] = None
+ """Other device display name, if known."""
+
+
+class SessionVerificationQR(BaseModel):
+ """QR verification data."""
+
+ data: str
+ """QR code payload to display for verification."""
+
+
+class SessionVerificationSAS(BaseModel):
+ """Emoji or number comparison data for verification."""
+
+ emojis: str
+ """Emoji sequence to compare on both devices."""
+
+ decimals: Optional[str] = None
+ """Number sequence to compare on both devices."""
+
+
+class SessionVerification(BaseModel):
+ """Trusted device verification progress."""
+
+ id: str
+ """Verification ID to pass in verification action paths."""
+
+ available_actions: List[Literal["accept", "cancel", "qr.confirmScanned", "sas.start", "sas.confirm"]] = FieldInfo(
+ alias="availableActions"
+ )
+ """Verification actions that are valid for the current state."""
+
+ direction: Literal["incoming", "outgoing"]
+ """Whether this device started or received the verification."""
+
+ methods: List[Literal["qr", "sas"]]
+ """Verification methods supported for this transaction."""
+
+ purpose: Literal["login", "device"]
+ """Why this verification exists."""
+
+ state: Literal["requested", "ready", "sas_ready", "qr_scanned", "done", "cancelled", "error"]
+ """Current trusted-device verification state."""
+
+ error: Optional[SessionVerificationError] = None
+ """Verification error details, if verification stopped."""
+
+ other_device: Optional[SessionVerificationOtherDevice] = FieldInfo(alias="otherDevice", default=None)
+ """Other device participating in verification."""
+
+ other_user_id: Optional[str] = FieldInfo(alias="otherUserID", default=None)
+ """Other Beeper user participating in verification."""
+
+ qr: Optional[SessionVerificationQR] = None
+ """QR verification data."""
+
+ sas: Optional[SessionVerificationSAS] = None
+ """Emoji or number comparison data for verification."""
+
+
+class Session(BaseModel):
+ """Current app sign-in and encrypted messaging setup state."""
+
+ e2ee: SessionE2EE
+ """Encrypted messaging setup status."""
+
+ state: Literal[
+ "needs-login",
+ "initializing",
+ "needs-cross-signing-setup",
+ "needs-verification",
+ "needs-secrets",
+ "needs-first-sync",
+ "ready",
+ ]
+ """
+ Current sign-in and encrypted messaging setup state for Beeper Desktop or Beeper
+ Server.
+ """
+
+ matrix: Optional[SessionMatrix] = None
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ verification: Optional[SessionVerification] = None
+ """Trusted device verification progress."""
+
+
+class VerificationError(BaseModel):
+ """Verification error details, if verification stopped."""
+
+ code: str
+ """Verification error code."""
+
+ reason: str
+ """User-facing verification error message."""
+
+
+class VerificationOtherDevice(BaseModel):
+ """Other device participating in verification."""
+
+ id: str
+ """Other device ID."""
+
+ name: Optional[str] = None
+ """Other device display name, if known."""
+
+
+class VerificationQR(BaseModel):
+ """QR verification data."""
+
+ data: str
+ """QR code payload to display for verification."""
+
+
+class VerificationSAS(BaseModel):
+ """Emoji or number comparison data for verification."""
+
+ emojis: str
+ """Emoji sequence to compare on both devices."""
+
+ decimals: Optional[str] = None
+ """Number sequence to compare on both devices."""
+
+
+class Verification(BaseModel):
+ """Trusted device verification progress."""
+
+ id: str
+ """Verification ID to pass in verification action paths."""
+
+ available_actions: List[Literal["accept", "cancel", "qr.confirmScanned", "sas.start", "sas.confirm"]] = FieldInfo(
+ alias="availableActions"
+ )
+ """Verification actions that are valid for the current state."""
+
+ direction: Literal["incoming", "outgoing"]
+ """Whether this device started or received the verification."""
+
+ methods: List[Literal["qr", "sas"]]
+ """Verification methods supported for this transaction."""
+
+ purpose: Literal["login", "device"]
+ """Why this verification exists."""
+
+ state: Literal["requested", "ready", "sas_ready", "qr_scanned", "done", "cancelled", "error"]
+ """Current trusted-device verification state."""
+
+ error: Optional[VerificationError] = None
+ """Verification error details, if verification stopped."""
+
+ other_device: Optional[VerificationOtherDevice] = FieldInfo(alias="otherDevice", default=None)
+ """Other device participating in verification."""
+
+ other_user_id: Optional[str] = FieldInfo(alias="otherUserID", default=None)
+ """Other Beeper user participating in verification."""
+
+ qr: Optional[VerificationQR] = None
+ """QR verification data."""
+
+ sas: Optional[VerificationSAS] = None
+ """Emoji or number comparison data for verification."""
+
+
+class SASStartResponse(BaseModel):
+ session: Session
+ """Current app sign-in and encrypted messaging setup state."""
+
+ verification: Optional[Verification] = None
+ """Trusted device verification progress."""
diff --git a/src/beeper_desktop_api/types/app/setup_email_params.py b/src/beeper_desktop_api/types/app/setup_email_params.py
new file mode 100644
index 0000000..95b7b36
--- /dev/null
+++ b/src/beeper_desktop_api/types/app/setup_email_params.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["SetupEmailParams"]
+
+
+class SetupEmailParams(TypedDict, total=False):
+ email: Required[str]
+ """Email address to send the sign-in code to."""
+
+ setup_request_id: Required[Annotated[str, PropertyInfo(alias="setupRequestID")]]
+ """Setup request ID returned by the start step."""
diff --git a/src/beeper_desktop_api/types/app/setup_register_params.py b/src/beeper_desktop_api/types/app/setup_register_params.py
new file mode 100644
index 0000000..4cb67c2
--- /dev/null
+++ b/src/beeper_desktop_api/types/app/setup_register_params.py
@@ -0,0 +1,27 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["SetupRegisterParams"]
+
+
+class SetupRegisterParams(TypedDict, total=False):
+ accept_terms: Required[Annotated[Literal[True], PropertyInfo(alias="acceptTerms")]]
+ """
+ Confirms that the user agreed to our
+ [terms of use](https://www.beeper.com/terms-onboarding) and has read our
+ [privacy policy](https://www.beeper.com/privacy).
+ """
+
+ lead_token: Required[Annotated[str, PropertyInfo(alias="leadToken")]]
+ """Registration token returned by Beeper."""
+
+ setup_request_id: Required[Annotated[str, PropertyInfo(alias="setupRequestID")]]
+ """Setup request ID returned by the start step."""
+
+ username: Required[str]
+ """Username selected by the user."""
diff --git a/src/beeper_desktop_api/types/app/setup_register_response.py b/src/beeper_desktop_api/types/app/setup_register_response.py
new file mode 100644
index 0000000..fa569c4
--- /dev/null
+++ b/src/beeper_desktop_api/types/app/setup_register_response.py
@@ -0,0 +1,212 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = [
+ "SetupRegisterResponse",
+ "Matrix",
+ "Session",
+ "SessionE2EE",
+ "SessionE2EESecrets",
+ "SessionMatrix",
+ "SessionVerification",
+ "SessionVerificationError",
+ "SessionVerificationOtherDevice",
+ "SessionVerificationQR",
+ "SessionVerificationSAS",
+]
+
+
+class Matrix(BaseModel):
+ """Account credentials for first-party app setup."""
+
+ access_token: str = FieldInfo(alias="accessToken")
+ """Beeper account access token. Returned once for first-party app setup."""
+
+ device_id: str = FieldInfo(alias="deviceID")
+ """Current device ID."""
+
+ homeserver: str
+ """Beeper homeserver URL for this account."""
+
+ user_id: str = FieldInfo(alias="userID")
+ """Signed-in Beeper user ID."""
+
+
+class SessionE2EESecrets(BaseModel):
+ """Encrypted messaging keys available on this device."""
+
+ master_key: bool = FieldInfo(alias="masterKey")
+ """Whether the account identity key is available."""
+
+ megolm_backup_key: bool = FieldInfo(alias="megolmBackupKey")
+ """Whether the encrypted message backup key is available."""
+
+ recovery_key: bool = FieldInfo(alias="recoveryKey")
+ """Whether a recovery key is available."""
+
+ self_signing_key: bool = FieldInfo(alias="selfSigningKey")
+ """Whether the device trust key is available."""
+
+ user_signing_key: bool = FieldInfo(alias="userSigningKey")
+ """Whether the user trust key is available."""
+
+
+class SessionE2EE(BaseModel):
+ """Encrypted messaging setup status."""
+
+ cross_signing: bool = FieldInfo(alias="crossSigning")
+ """Whether this account can verify trusted devices."""
+
+ first_sync_done: bool = FieldInfo(alias="firstSyncDone")
+ """Whether the first encrypted message sync is complete."""
+
+ has_backed_up_recovery_key: bool = FieldInfo(alias="hasBackedUpRecoveryKey")
+ """Whether the user confirmed that they saved their recovery key."""
+
+ initialized: bool
+ """Whether encrypted messaging setup has started."""
+
+ key_backup: bool = FieldInfo(alias="keyBackup")
+ """Whether encrypted message backup is available."""
+
+ secrets: SessionE2EESecrets
+ """Encrypted messaging keys available on this device."""
+
+ secret_storage: bool = FieldInfo(alias="secretStorage")
+ """Whether secure key storage is available."""
+
+ verified: bool
+ """Whether this device is trusted for encrypted messages."""
+
+ recovery_key_generated_at: Optional[float] = FieldInfo(alias="recoveryKeyGeneratedAt", default=None)
+ """Unix timestamp for when the recovery key was created."""
+
+
+class SessionMatrix(BaseModel):
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ device_id: str = FieldInfo(alias="deviceID")
+ """Current device ID."""
+
+ homeserver: str
+ """Beeper homeserver URL for this account."""
+
+ user_id: str = FieldInfo(alias="userID")
+ """Signed-in Beeper user ID."""
+
+
+class SessionVerificationError(BaseModel):
+ """Verification error details, if verification stopped."""
+
+ code: str
+ """Verification error code."""
+
+ reason: str
+ """User-facing verification error message."""
+
+
+class SessionVerificationOtherDevice(BaseModel):
+ """Other device participating in verification."""
+
+ id: str
+ """Other device ID."""
+
+ name: Optional[str] = None
+ """Other device display name, if known."""
+
+
+class SessionVerificationQR(BaseModel):
+ """QR verification data."""
+
+ data: str
+ """QR code payload to display for verification."""
+
+
+class SessionVerificationSAS(BaseModel):
+ """Emoji or number comparison data for verification."""
+
+ emojis: str
+ """Emoji sequence to compare on both devices."""
+
+ decimals: Optional[str] = None
+ """Number sequence to compare on both devices."""
+
+
+class SessionVerification(BaseModel):
+ """Trusted device verification progress."""
+
+ id: str
+ """Verification ID to pass in verification action paths."""
+
+ available_actions: List[Literal["accept", "cancel", "qr.confirmScanned", "sas.start", "sas.confirm"]] = FieldInfo(
+ alias="availableActions"
+ )
+ """Verification actions that are valid for the current state."""
+
+ direction: Literal["incoming", "outgoing"]
+ """Whether this device started or received the verification."""
+
+ methods: List[Literal["qr", "sas"]]
+ """Verification methods supported for this transaction."""
+
+ purpose: Literal["login", "device"]
+ """Why this verification exists."""
+
+ state: Literal["requested", "ready", "sas_ready", "qr_scanned", "done", "cancelled", "error"]
+ """Current trusted-device verification state."""
+
+ error: Optional[SessionVerificationError] = None
+ """Verification error details, if verification stopped."""
+
+ other_device: Optional[SessionVerificationOtherDevice] = FieldInfo(alias="otherDevice", default=None)
+ """Other device participating in verification."""
+
+ other_user_id: Optional[str] = FieldInfo(alias="otherUserID", default=None)
+ """Other Beeper user participating in verification."""
+
+ qr: Optional[SessionVerificationQR] = None
+ """QR verification data."""
+
+ sas: Optional[SessionVerificationSAS] = None
+ """Emoji or number comparison data for verification."""
+
+
+class Session(BaseModel):
+ """Current app sign-in and encrypted messaging setup state after sign-in."""
+
+ e2ee: SessionE2EE
+ """Encrypted messaging setup status."""
+
+ state: Literal[
+ "needs-login",
+ "initializing",
+ "needs-cross-signing-setup",
+ "needs-verification",
+ "needs-secrets",
+ "needs-first-sync",
+ "ready",
+ ]
+ """
+ Current sign-in and encrypted messaging setup state for Beeper Desktop or Beeper
+ Server.
+ """
+
+ matrix: Optional[SessionMatrix] = None
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ verification: Optional[SessionVerification] = None
+ """Trusted device verification progress."""
+
+
+class SetupRegisterResponse(BaseModel):
+ matrix: Matrix
+ """Account credentials for first-party app setup."""
+
+ session: Session
+ """Current app sign-in and encrypted messaging setup state after sign-in."""
diff --git a/src/beeper_desktop_api/types/app/setup_response_params.py b/src/beeper_desktop_api/types/app/setup_response_params.py
new file mode 100644
index 0000000..5909c37
--- /dev/null
+++ b/src/beeper_desktop_api/types/app/setup_response_params.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["SetupResponseParams"]
+
+
+class SetupResponseParams(TypedDict, total=False):
+ response: Required[str]
+ """Sign-in code from the user email."""
+
+ setup_request_id: Required[Annotated[str, PropertyInfo(alias="setupRequestID")]]
+ """Setup request ID returned by the start step."""
diff --git a/src/beeper_desktop_api/types/app/setup_response_response.py b/src/beeper_desktop_api/types/app/setup_response_response.py
new file mode 100644
index 0000000..8587abc
--- /dev/null
+++ b/src/beeper_desktop_api/types/app/setup_response_response.py
@@ -0,0 +1,251 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Union, Optional
+from typing_extensions import Literal, TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = [
+ "SetupResponseResponse",
+ "Success",
+ "SuccessMatrix",
+ "SuccessSession",
+ "SuccessSessionE2EE",
+ "SuccessSessionE2EESecrets",
+ "SuccessSessionMatrix",
+ "SuccessSessionVerification",
+ "SuccessSessionVerificationError",
+ "SuccessSessionVerificationOtherDevice",
+ "SuccessSessionVerificationQR",
+ "SuccessSessionVerificationSAS",
+ "RegistrationRequired",
+ "RegistrationRequiredCopy",
+]
+
+
+class SuccessMatrix(BaseModel):
+ """Account credentials for first-party app setup."""
+
+ access_token: str = FieldInfo(alias="accessToken")
+ """Beeper account access token. Returned once for first-party app setup."""
+
+ device_id: str = FieldInfo(alias="deviceID")
+ """Current device ID."""
+
+ homeserver: str
+ """Beeper homeserver URL for this account."""
+
+ user_id: str = FieldInfo(alias="userID")
+ """Signed-in Beeper user ID."""
+
+
+class SuccessSessionE2EESecrets(BaseModel):
+ """Encrypted messaging keys available on this device."""
+
+ master_key: bool = FieldInfo(alias="masterKey")
+ """Whether the account identity key is available."""
+
+ megolm_backup_key: bool = FieldInfo(alias="megolmBackupKey")
+ """Whether the encrypted message backup key is available."""
+
+ recovery_key: bool = FieldInfo(alias="recoveryKey")
+ """Whether a recovery key is available."""
+
+ self_signing_key: bool = FieldInfo(alias="selfSigningKey")
+ """Whether the device trust key is available."""
+
+ user_signing_key: bool = FieldInfo(alias="userSigningKey")
+ """Whether the user trust key is available."""
+
+
+class SuccessSessionE2EE(BaseModel):
+ """Encrypted messaging setup status."""
+
+ cross_signing: bool = FieldInfo(alias="crossSigning")
+ """Whether this account can verify trusted devices."""
+
+ first_sync_done: bool = FieldInfo(alias="firstSyncDone")
+ """Whether the first encrypted message sync is complete."""
+
+ has_backed_up_recovery_key: bool = FieldInfo(alias="hasBackedUpRecoveryKey")
+ """Whether the user confirmed that they saved their recovery key."""
+
+ initialized: bool
+ """Whether encrypted messaging setup has started."""
+
+ key_backup: bool = FieldInfo(alias="keyBackup")
+ """Whether encrypted message backup is available."""
+
+ secrets: SuccessSessionE2EESecrets
+ """Encrypted messaging keys available on this device."""
+
+ secret_storage: bool = FieldInfo(alias="secretStorage")
+ """Whether secure key storage is available."""
+
+ verified: bool
+ """Whether this device is trusted for encrypted messages."""
+
+ recovery_key_generated_at: Optional[float] = FieldInfo(alias="recoveryKeyGeneratedAt", default=None)
+ """Unix timestamp for when the recovery key was created."""
+
+
+class SuccessSessionMatrix(BaseModel):
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ device_id: str = FieldInfo(alias="deviceID")
+ """Current device ID."""
+
+ homeserver: str
+ """Beeper homeserver URL for this account."""
+
+ user_id: str = FieldInfo(alias="userID")
+ """Signed-in Beeper user ID."""
+
+
+class SuccessSessionVerificationError(BaseModel):
+ """Verification error details, if verification stopped."""
+
+ code: str
+ """Verification error code."""
+
+ reason: str
+ """User-facing verification error message."""
+
+
+class SuccessSessionVerificationOtherDevice(BaseModel):
+ """Other device participating in verification."""
+
+ id: str
+ """Other device ID."""
+
+ name: Optional[str] = None
+ """Other device display name, if known."""
+
+
+class SuccessSessionVerificationQR(BaseModel):
+ """QR verification data."""
+
+ data: str
+ """QR code payload to display for verification."""
+
+
+class SuccessSessionVerificationSAS(BaseModel):
+ """Emoji or number comparison data for verification."""
+
+ emojis: str
+ """Emoji sequence to compare on both devices."""
+
+ decimals: Optional[str] = None
+ """Number sequence to compare on both devices."""
+
+
+class SuccessSessionVerification(BaseModel):
+ """Trusted device verification progress."""
+
+ id: str
+ """Verification ID to pass in verification action paths."""
+
+ available_actions: List[Literal["accept", "cancel", "qr.confirmScanned", "sas.start", "sas.confirm"]] = FieldInfo(
+ alias="availableActions"
+ )
+ """Verification actions that are valid for the current state."""
+
+ direction: Literal["incoming", "outgoing"]
+ """Whether this device started or received the verification."""
+
+ methods: List[Literal["qr", "sas"]]
+ """Verification methods supported for this transaction."""
+
+ purpose: Literal["login", "device"]
+ """Why this verification exists."""
+
+ state: Literal["requested", "ready", "sas_ready", "qr_scanned", "done", "cancelled", "error"]
+ """Current trusted-device verification state."""
+
+ error: Optional[SuccessSessionVerificationError] = None
+ """Verification error details, if verification stopped."""
+
+ other_device: Optional[SuccessSessionVerificationOtherDevice] = FieldInfo(alias="otherDevice", default=None)
+ """Other device participating in verification."""
+
+ other_user_id: Optional[str] = FieldInfo(alias="otherUserID", default=None)
+ """Other Beeper user participating in verification."""
+
+ qr: Optional[SuccessSessionVerificationQR] = None
+ """QR verification data."""
+
+ sas: Optional[SuccessSessionVerificationSAS] = None
+ """Emoji or number comparison data for verification."""
+
+
+class SuccessSession(BaseModel):
+ """Current app sign-in and encrypted messaging setup state after sign-in."""
+
+ e2ee: SuccessSessionE2EE
+ """Encrypted messaging setup status."""
+
+ state: Literal[
+ "needs-login",
+ "initializing",
+ "needs-cross-signing-setup",
+ "needs-verification",
+ "needs-secrets",
+ "needs-first-sync",
+ "ready",
+ ]
+ """
+ Current sign-in and encrypted messaging setup state for Beeper Desktop or Beeper
+ Server.
+ """
+
+ matrix: Optional[SuccessSessionMatrix] = None
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ verification: Optional[SuccessSessionVerification] = None
+ """Trusted device verification progress."""
+
+
+class Success(BaseModel):
+ matrix: SuccessMatrix
+ """Account credentials for first-party app setup."""
+
+ session: SuccessSession
+ """Current app sign-in and encrypted messaging setup state after sign-in."""
+
+
+class RegistrationRequiredCopy(BaseModel):
+ """Copy to display during account creation."""
+
+ submit: Literal["Continue"]
+ """Submit button label."""
+
+ terms: Literal["By continuing, you agree to the Terms of Use and acknowledge the Privacy Policy."]
+ """Terms and privacy notice to show before account creation."""
+
+ title: Literal["Choose your username"]
+ """Title for the username step."""
+
+ username_placeholder: Literal["Username"] = FieldInfo(alias="usernamePlaceholder")
+ """Placeholder for the username field."""
+
+
+class RegistrationRequired(BaseModel):
+ copy_: RegistrationRequiredCopy = FieldInfo(alias="copy")
+ """Copy to display during account creation."""
+
+ lead_token: str = FieldInfo(alias="leadToken")
+ """Registration token returned by Beeper."""
+
+ registration_required: Literal[True] = FieldInfo(alias="registrationRequired")
+ """Indicates that the user needs to create a Beeper account."""
+
+ setup_request_id: str = FieldInfo(alias="setupRequestID")
+ """Setup request ID to use when creating the account."""
+
+ username_suggestions: Optional[List[str]] = FieldInfo(alias="usernameSuggestions", default=None)
+ """Suggested usernames for the new account."""
+
+
+SetupResponseResponse: TypeAlias = Union[Success, RegistrationRequired]
diff --git a/src/beeper_desktop_api/types/app/setup_retrieve_response.py b/src/beeper_desktop_api/types/app/setup_retrieve_response.py
new file mode 100644
index 0000000..409f28f
--- /dev/null
+++ b/src/beeper_desktop_api/types/app/setup_retrieve_response.py
@@ -0,0 +1,184 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = [
+ "SetupRetrieveResponse",
+ "E2EE",
+ "E2EESecrets",
+ "Matrix",
+ "Verification",
+ "VerificationError",
+ "VerificationOtherDevice",
+ "VerificationQR",
+ "VerificationSAS",
+]
+
+
+class E2EESecrets(BaseModel):
+ """Encrypted messaging keys available on this device."""
+
+ master_key: bool = FieldInfo(alias="masterKey")
+ """Whether the account identity key is available."""
+
+ megolm_backup_key: bool = FieldInfo(alias="megolmBackupKey")
+ """Whether the encrypted message backup key is available."""
+
+ recovery_key: bool = FieldInfo(alias="recoveryKey")
+ """Whether a recovery key is available."""
+
+ self_signing_key: bool = FieldInfo(alias="selfSigningKey")
+ """Whether the device trust key is available."""
+
+ user_signing_key: bool = FieldInfo(alias="userSigningKey")
+ """Whether the user trust key is available."""
+
+
+class E2EE(BaseModel):
+ """Encrypted messaging setup status."""
+
+ cross_signing: bool = FieldInfo(alias="crossSigning")
+ """Whether this account can verify trusted devices."""
+
+ first_sync_done: bool = FieldInfo(alias="firstSyncDone")
+ """Whether the first encrypted message sync is complete."""
+
+ has_backed_up_recovery_key: bool = FieldInfo(alias="hasBackedUpRecoveryKey")
+ """Whether the user confirmed that they saved their recovery key."""
+
+ initialized: bool
+ """Whether encrypted messaging setup has started."""
+
+ key_backup: bool = FieldInfo(alias="keyBackup")
+ """Whether encrypted message backup is available."""
+
+ secrets: E2EESecrets
+ """Encrypted messaging keys available on this device."""
+
+ secret_storage: bool = FieldInfo(alias="secretStorage")
+ """Whether secure key storage is available."""
+
+ verified: bool
+ """Whether this device is trusted for encrypted messages."""
+
+ recovery_key_generated_at: Optional[float] = FieldInfo(alias="recoveryKeyGeneratedAt", default=None)
+ """Unix timestamp for when the recovery key was created."""
+
+
+class Matrix(BaseModel):
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ device_id: str = FieldInfo(alias="deviceID")
+ """Current device ID."""
+
+ homeserver: str
+ """Beeper homeserver URL for this account."""
+
+ user_id: str = FieldInfo(alias="userID")
+ """Signed-in Beeper user ID."""
+
+
+class VerificationError(BaseModel):
+ """Verification error details, if verification stopped."""
+
+ code: str
+ """Verification error code."""
+
+ reason: str
+ """User-facing verification error message."""
+
+
+class VerificationOtherDevice(BaseModel):
+ """Other device participating in verification."""
+
+ id: str
+ """Other device ID."""
+
+ name: Optional[str] = None
+ """Other device display name, if known."""
+
+
+class VerificationQR(BaseModel):
+ """QR verification data."""
+
+ data: str
+ """QR code payload to display for verification."""
+
+
+class VerificationSAS(BaseModel):
+ """Emoji or number comparison data for verification."""
+
+ emojis: str
+ """Emoji sequence to compare on both devices."""
+
+ decimals: Optional[str] = None
+ """Number sequence to compare on both devices."""
+
+
+class Verification(BaseModel):
+ """Trusted device verification progress."""
+
+ id: str
+ """Verification ID to pass in verification action paths."""
+
+ available_actions: List[Literal["accept", "cancel", "qr.confirmScanned", "sas.start", "sas.confirm"]] = FieldInfo(
+ alias="availableActions"
+ )
+ """Verification actions that are valid for the current state."""
+
+ direction: Literal["incoming", "outgoing"]
+ """Whether this device started or received the verification."""
+
+ methods: List[Literal["qr", "sas"]]
+ """Verification methods supported for this transaction."""
+
+ purpose: Literal["login", "device"]
+ """Why this verification exists."""
+
+ state: Literal["requested", "ready", "sas_ready", "qr_scanned", "done", "cancelled", "error"]
+ """Current trusted-device verification state."""
+
+ error: Optional[VerificationError] = None
+ """Verification error details, if verification stopped."""
+
+ other_device: Optional[VerificationOtherDevice] = FieldInfo(alias="otherDevice", default=None)
+ """Other device participating in verification."""
+
+ other_user_id: Optional[str] = FieldInfo(alias="otherUserID", default=None)
+ """Other Beeper user participating in verification."""
+
+ qr: Optional[VerificationQR] = None
+ """QR verification data."""
+
+ sas: Optional[VerificationSAS] = None
+ """Emoji or number comparison data for verification."""
+
+
+class SetupRetrieveResponse(BaseModel):
+ e2ee: E2EE
+ """Encrypted messaging setup status."""
+
+ state: Literal[
+ "needs-login",
+ "initializing",
+ "needs-cross-signing-setup",
+ "needs-verification",
+ "needs-secrets",
+ "needs-first-sync",
+ "ready",
+ ]
+ """
+ Current sign-in and encrypted messaging setup state for Beeper Desktop or Beeper
+ Server.
+ """
+
+ matrix: Optional[Matrix] = None
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ verification: Optional[Verification] = None
+ """Trusted device verification progress."""
diff --git a/src/beeper_desktop_api/types/app/setup_start_response.py b/src/beeper_desktop_api/types/app/setup_start_response.py
new file mode 100644
index 0000000..883e5cb
--- /dev/null
+++ b/src/beeper_desktop_api/types/app/setup_start_response.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["SetupStartResponse"]
+
+
+class SetupStartResponse(BaseModel):
+ setup_request_id: str = FieldInfo(alias="setupRequestID")
+ """Setup request ID to use in the next sign-in step."""
+
+ sign_in_methods: List[str] = FieldInfo(alias="signInMethods")
+ """Available sign-in methods for this setup request."""
diff --git a/src/beeper_desktop_api/types/asset_download_params.py b/src/beeper_desktop_api/types/asset_download_params.py
index 62e7c02..58a04a1 100644
--- a/src/beeper_desktop_api/types/asset_download_params.py
+++ b/src/beeper_desktop_api/types/asset_download_params.py
@@ -9,4 +9,4 @@
class AssetDownloadParams(TypedDict, total=False):
url: Required[str]
- """Matrix content URL (mxc:// or localmxc://) for the file to download."""
+ """Beeper media URL (mxc:// or localmxc://) for the file to download."""
diff --git a/src/beeper_desktop_api/types/bridge.py b/src/beeper_desktop_api/types/bridge.py
new file mode 100644
index 0000000..38adb1b
--- /dev/null
+++ b/src/beeper_desktop_api/types/bridge.py
@@ -0,0 +1,51 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .account import Account
+from .._models import BaseModel
+
+__all__ = ["Bridge"]
+
+
+class Bridge(BaseModel):
+ """Available bridge that can connect or reconnect chat accounts."""
+
+ id: str
+ """Bridge ID. Use with bridge endpoints."""
+
+ accounts: List[Account]
+ """Connected accounts for this bridge.
+
+ Uses the same Account schema as GET /v1/accounts.
+ """
+
+ active_account_count: int = FieldInfo(alias="activeAccountCount")
+ """Number of active accounts for this network on this device."""
+
+ display_name: str = FieldInfo(alias="displayName")
+ """Human-friendly bridge name shown in Beeper."""
+
+ provider: Literal["cloud", "self-hosted", "local", "platform-sdk"]
+ """Where accounts for this bridge run: on this device or in Beeper Cloud."""
+
+ status: Literal["available", "connected", "limit_reached", "temporarily_unavailable", "disabled"]
+ """Whether this bridge can currently be used to connect new accounts."""
+
+ supports_multiple_accounts: bool = FieldInfo(alias="supportsMultipleAccounts")
+ """Whether this bridge can have multiple active accounts for the same network."""
+
+ type: str
+ """
+ Underlying bridge type, such as matrix, discordgo, slackgo, whatsapp, telegram,
+ or twitter.
+ """
+
+ network: Optional[str] = None
+ """Network grouping used for account counts and limits."""
+
+ status_text: Optional[str] = FieldInfo(alias="statusText", default=None)
+ """Human-friendly status text matching Beeper account management language."""
diff --git a/src/beeper_desktop_api/types/bridge_list_response.py b/src/beeper_desktop_api/types/bridge_list_response.py
new file mode 100644
index 0000000..ed0d497
--- /dev/null
+++ b/src/beeper_desktop_api/types/bridge_list_response.py
@@ -0,0 +1,14 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+
+from .bridge import Bridge
+from .._models import BaseModel
+
+__all__ = ["BridgeListResponse"]
+
+
+class BridgeListResponse(BaseModel):
+ """Available bridges and their connected accounts."""
+
+ items: List[Bridge]
diff --git a/src/beeper_desktop_api/types/bridge_login.py b/src/beeper_desktop_api/types/bridge_login.py
new file mode 100644
index 0000000..6dec6aa
--- /dev/null
+++ b/src/beeper_desktop_api/types/bridge_login.py
@@ -0,0 +1,37 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+from .shared.user import User
+
+__all__ = ["BridgeLogin"]
+
+
+class BridgeLogin(BaseModel):
+ """Signed-in identity for a bridge.
+
+ One bridge login can contain multiple chat accounts.
+ """
+
+ bridge_id: str = FieldInfo(alias="bridgeID")
+ """Bridge ID."""
+
+ login_id: str = FieldInfo(alias="loginID")
+ """Bridge login ID."""
+
+ remove_scopes: List[Literal["current-device", "all-devices"]] = FieldInfo(alias="removeScopes")
+
+ status: Literal["connected", "connecting", "needs_login", "logged_out", "unknown"]
+
+ account_ids: Optional[List[str]] = FieldInfo(alias="accountIDs", default=None)
+ """Chat accounts that belong to this bridge login, when known."""
+
+ status_text: Optional[str] = FieldInfo(alias="statusText", default=None)
+ """Human-friendly bridge login status text."""
+
+ user: Optional[User] = None
+ """User the account belongs to."""
diff --git a/src/beeper_desktop_api/types/bridge_retrieve_response.py b/src/beeper_desktop_api/types/bridge_retrieve_response.py
new file mode 100644
index 0000000..89b4512
--- /dev/null
+++ b/src/beeper_desktop_api/types/bridge_retrieve_response.py
@@ -0,0 +1,51 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from .account import Account
+from .._models import BaseModel
+
+__all__ = ["BridgeRetrieveResponse"]
+
+
+class BridgeRetrieveResponse(BaseModel):
+ """Available bridge that can connect or reconnect chat accounts."""
+
+ id: str
+ """Bridge ID. Use with bridge endpoints."""
+
+ accounts: List[Account]
+ """Connected accounts for this bridge.
+
+ Uses the same Account schema as GET /v1/accounts.
+ """
+
+ active_account_count: int = FieldInfo(alias="activeAccountCount")
+ """Number of active accounts for this network on this device."""
+
+ display_name: str = FieldInfo(alias="displayName")
+ """Human-friendly bridge name shown in Beeper."""
+
+ provider: Literal["cloud", "self-hosted", "local", "platform-sdk"]
+ """Where accounts for this bridge run: on this device or in Beeper Cloud."""
+
+ status: Literal["available", "connected", "limit_reached", "temporarily_unavailable", "disabled"]
+ """Whether this bridge can currently be used to connect new accounts."""
+
+ supports_multiple_accounts: bool = FieldInfo(alias="supportsMultipleAccounts")
+ """Whether this bridge can have multiple active accounts for the same network."""
+
+ type: str
+ """
+ Underlying bridge type, such as matrix, discordgo, slackgo, whatsapp, telegram,
+ or twitter.
+ """
+
+ network: Optional[str] = None
+ """Network grouping used for account counts and limits."""
+
+ status_text: Optional[str] = FieldInfo(alias="statusText", default=None)
+ """Human-friendly status text matching Beeper account management language."""
diff --git a/src/beeper_desktop_api/types/bridges/__init__.py b/src/beeper_desktop_api/types/bridges/__init__.py
new file mode 100644
index 0000000..7fe33f8
--- /dev/null
+++ b/src/beeper_desktop_api/types/bridges/__init__.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .login_list_response import LoginListResponse as LoginListResponse
+from .login_remove_params import LoginRemoveParams as LoginRemoveParams
+from .login_remove_response import LoginRemoveResponse as LoginRemoveResponse
+from .login_flow_list_response import LoginFlowListResponse as LoginFlowListResponse
+from .login_session_create_params import LoginSessionCreateParams as LoginSessionCreateParams
+from .login_session_cancel_response import LoginSessionCancelResponse as LoginSessionCancelResponse
diff --git a/src/beeper_desktop_api/types/bridges/login_flow_list_response.py b/src/beeper_desktop_api/types/bridges/login_flow_list_response.py
new file mode 100644
index 0000000..1d625e3
--- /dev/null
+++ b/src/beeper_desktop_api/types/bridges/login_flow_list_response.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+
+from ..._models import BaseModel
+from ..login_flow import LoginFlow
+
+__all__ = ["LoginFlowListResponse"]
+
+
+class LoginFlowListResponse(BaseModel):
+ items: List[LoginFlow]
diff --git a/src/beeper_desktop_api/types/bridges/login_list_response.py b/src/beeper_desktop_api/types/bridges/login_list_response.py
new file mode 100644
index 0000000..485cc78
--- /dev/null
+++ b/src/beeper_desktop_api/types/bridges/login_list_response.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+
+from ..._models import BaseModel
+from ..bridge_login import BridgeLogin
+
+__all__ = ["LoginListResponse"]
+
+
+class LoginListResponse(BaseModel):
+ items: List[BridgeLogin]
diff --git a/src/beeper_desktop_api/types/bridges/login_remove_params.py b/src/beeper_desktop_api/types/bridges/login_remove_params.py
new file mode 100644
index 0000000..7c1e974
--- /dev/null
+++ b/src/beeper_desktop_api/types/bridges/login_remove_params.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["LoginRemoveParams"]
+
+
+class LoginRemoveParams(TypedDict, total=False):
+ bridge_id: Required[Annotated[str, PropertyInfo(alias="bridgeID")]]
+ """Bridge ID."""
+
+ scope: Required[Literal["current-device", "all-devices"]]
+ """Where this bridge login should be removed."""
diff --git a/src/beeper_desktop_api/types/bridges/login_remove_response.py b/src/beeper_desktop_api/types/bridges/login_remove_response.py
new file mode 100644
index 0000000..cbb9d80
--- /dev/null
+++ b/src/beeper_desktop_api/types/bridges/login_remove_response.py
@@ -0,0 +1,23 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["LoginRemoveResponse"]
+
+
+class LoginRemoveResponse(BaseModel):
+ bridge_id: str = FieldInfo(alias="bridgeID")
+
+ login_id: str = FieldInfo(alias="loginID")
+
+ scope: Literal["current-device", "all-devices"]
+ """Where this bridge login should be removed."""
+
+ status: Literal["removed"]
+
+ affected_account_ids: Optional[List[str]] = FieldInfo(alias="affectedAccountIDs", default=None)
diff --git a/src/beeper_desktop_api/types/bridges/login_session_cancel_response.py b/src/beeper_desktop_api/types/bridges/login_session_cancel_response.py
new file mode 100644
index 0000000..a3aaee6
--- /dev/null
+++ b/src/beeper_desktop_api/types/bridges/login_session_cancel_response.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["LoginSessionCancelResponse"]
+
+
+class LoginSessionCancelResponse(BaseModel):
+ bridge_id: str = FieldInfo(alias="bridgeID")
+
+ login_session_id: str = FieldInfo(alias="loginSessionID")
+
+ status: Literal["cancelled"]
diff --git a/src/beeper_desktop_api/types/bridges/login_session_create_params.py b/src/beeper_desktop_api/types/bridges/login_session_create_params.py
new file mode 100644
index 0000000..8da4234
--- /dev/null
+++ b/src/beeper_desktop_api/types/bridges/login_session_create_params.py
@@ -0,0 +1,23 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from ..._utils import PropertyInfo
+
+__all__ = ["LoginSessionCreateParams"]
+
+
+class LoginSessionCreateParams(TypedDict, total=False):
+ account_id: Annotated[str, PropertyInfo(alias="accountID")]
+ """Existing chat account ID to reconnect. Omit to connect a new account."""
+
+ flow_id: Annotated[str, PropertyInfo(alias="flowID")]
+ """Optional flow ID returned by the list login flows endpoint.
+
+ If omitted, Beeper chooses the default flow.
+ """
+
+ login_id: Annotated[str, PropertyInfo(alias="loginID")]
+ """Existing bridge login ID to reconnect. Omit to connect a new account."""
diff --git a/src/beeper_desktop_api/types/bridges/login_sessions/__init__.py b/src/beeper_desktop_api/types/bridges/login_sessions/__init__.py
new file mode 100644
index 0000000..a83b5fb
--- /dev/null
+++ b/src/beeper_desktop_api/types/bridges/login_sessions/__init__.py
@@ -0,0 +1,5 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .step_submit_params import StepSubmitParams as StepSubmitParams
diff --git a/src/beeper_desktop_api/types/bridges/login_sessions/step_submit_params.py b/src/beeper_desktop_api/types/bridges/login_sessions/step_submit_params.py
new file mode 100644
index 0000000..e099810
--- /dev/null
+++ b/src/beeper_desktop_api/types/bridges/login_sessions/step_submit_params.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict
+from typing_extensions import Literal, Required, Annotated, TypedDict
+
+from ...._utils import PropertyInfo
+
+__all__ = ["StepSubmitParams"]
+
+
+class StepSubmitParams(TypedDict, total=False):
+ bridge_id: Required[Annotated[str, PropertyInfo(alias="bridgeID")]]
+ """Bridge ID."""
+
+ login_session_id: Required[Annotated[str, PropertyInfo(alias="loginSessionID")]]
+ """Temporary bridge login session ID."""
+
+ type: Required[Literal["user_input", "cookies", "display_and_wait"]]
+
+ fields: Dict[str, str]
+ """Field values keyed by the field IDs from the current step."""
+
+ last_url: Annotated[str, PropertyInfo(alias="lastURL")]
+ """Last browser URL reached during a cookies step, if available."""
+
+ source: Literal["api", "webview", "browser_extension"]
+ """How the step was completed.
+
+ Omit unless the client needs to distinguish an embedded webview or browser
+ extension.
+ """
diff --git a/src/beeper_desktop_api/types/chat.py b/src/beeper_desktop_api/types/chat.py
index 4cd922c..125ec5e 100644
--- a/src/beeper_desktop_api/types/chat.py
+++ b/src/beeper_desktop_api/types/chat.py
@@ -1,34 +1,18 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import Dict, List, Optional
+from typing import List, Optional
from datetime import datetime
from typing_extensions import Literal
from pydantic import Field as FieldInfo
+from .label import Label
from .._models import BaseModel
from .shared.user import User
+from .shared.chat_draft import ChatDraft
+from .shared.chat_capabilities import ChatCapabilities
-__all__ = [
- "Chat",
- "Participants",
- "ParticipantsItem",
- "Capabilities",
- "CapabilitiesAttachments",
- "CapabilitiesDisappearingTimer",
- "CapabilitiesMessageRequest",
- "CapabilitiesParticipantActions",
- "CapabilitiesState",
- "CapabilitiesStateAvatar",
- "CapabilitiesStateDescription",
- "CapabilitiesStateDisappearingTimer",
- "CapabilitiesStateTitle",
- "Draft",
- "DraftAttachments",
- "DraftAttachmentsSize",
- "Reminder",
- "Snooze",
-]
+__all__ = ["Chat", "Participants", "ParticipantsItem", "Merge", "Reminder", "Snooze"]
class ParticipantsItem(User):
@@ -38,7 +22,7 @@ class ParticipantsItem(User):
"""True if this participant has admin privileges in the chat."""
is_network_bot: Optional[bool] = FieldInfo(alias="isNetworkBot", default=None)
- """True if this participant represents a network or bridge bot."""
+ """True if this participant represents an automated network account."""
is_pending: Optional[bool] = FieldInfo(alias="isPending", default=None)
"""True if this participant has been invited but has not joined yet."""
@@ -57,324 +41,21 @@ class Participants(BaseModel):
"""Total number of participants in the chat."""
-class CapabilitiesAttachments(BaseModel):
- """Capabilities for one attachment message type."""
-
- mime_types: Dict[str, Literal[-2, -1, 0, 1, 2]] = FieldInfo(alias="mimeTypes")
- """Supported MIME types or MIME patterns for this file message type.
-
- Missing MIME types should be treated as rejected.
- """
-
- caption: Optional[Literal[-2, -1, 0, 1, 2]] = None
- """
- -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
- supported.
- """
-
- max_caption_length: Optional[int] = FieldInfo(alias="maxCaptionLength", default=None)
- """Maximum caption length when captions are supported."""
-
- max_duration: Optional[int] = FieldInfo(alias="maxDuration", default=None)
- """Maximum audio or video duration in seconds."""
-
- max_height: Optional[int] = FieldInfo(alias="maxHeight", default=None)
- """Maximum image or video height in pixels."""
-
- max_size: Optional[int] = FieldInfo(alias="maxSize", default=None)
- """Maximum file size in bytes."""
-
- max_width: Optional[int] = FieldInfo(alias="maxWidth", default=None)
- """Maximum image or video width in pixels."""
-
- view_once: Optional[bool] = FieldInfo(alias="viewOnce", default=None)
- """True if this file type can be sent as view-once media."""
-
-
-class CapabilitiesDisappearingTimer(BaseModel):
- """Disappearing-message timer capabilities."""
-
- omit_empty_timer: Optional[bool] = FieldInfo(alias="omitEmptyTimer", default=None)
- """True if empty timer objects should be omitted from message content."""
-
- timers: Optional[List[int]] = None
- """Allowed disappearing timer values in milliseconds.
-
- Omitted means any timer is allowed.
- """
-
- types: Optional[List[Literal["afterRead", "afterSend"]]] = None
- """Supported disappearing timer types."""
-
-
-class CapabilitiesMessageRequest(BaseModel):
- """Message request capabilities."""
-
- accept_with_button: Optional[Literal[-2, -1, 0, 1, 2]] = FieldInfo(alias="acceptWithButton", default=None)
- """
- -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
- supported.
- """
-
- accept_with_message: Optional[Literal[-2, -1, 0, 1, 2]] = FieldInfo(alias="acceptWithMessage", default=None)
- """
- -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
- supported.
- """
-
-
-class CapabilitiesParticipantActions(BaseModel):
- """Participant management capabilities."""
-
- ban: Optional[Literal[-2, -1, 0, 1, 2]] = None
+class Merge(BaseModel):
"""
- -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
- supported.
+ Present when this chat is a merged chat: one person whose conversations across networks (or across accounts on the same network) are grouped into a single chat. A merged chat holds no messages of its own - read messages from the member chats, and send either to a member directly or to the merged chat ID to route automatically.
"""
- invite: Optional[Literal[-2, -1, 0, 1, 2]] = None
- """
- -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
- supported.
- """
+ chat_ids: List[str] = FieldInfo(alias="chatIDs")
+ """Chat IDs of the member chats grouped by this merged chat."""
- kick: Optional[Literal[-2, -1, 0, 1, 2]] = None
+ default_chat_id: Optional[str] = FieldInfo(alias="defaultChatID", default=None)
"""
- -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
- supported.
+ Member chat that receives messages sent to the merged chat, when the user has
+ picked one. This preference is per-device; when absent, sends route to the most
+ recently active member.
"""
- leave: Optional[Literal[-2, -1, 0, 1, 2]] = None
- """
- -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
- supported.
- """
-
- revoke_invite: Optional[Literal[-2, -1, 0, 1, 2]] = FieldInfo(alias="revokeInvite", default=None)
- """
- -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
- supported.
- """
-
-
-class CapabilitiesStateAvatar(BaseModel):
- """Chat avatar state capability."""
-
- level: Literal[-2, -1, 0, 1, 2]
- """
- -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
- supported.
- """
-
-
-class CapabilitiesStateDescription(BaseModel):
- """Chat description/topic state capability."""
-
- level: Literal[-2, -1, 0, 1, 2]
- """
- -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
- supported.
- """
-
-
-class CapabilitiesStateDisappearingTimer(BaseModel):
- """Disappearing-message timer state capability."""
-
- level: Literal[-2, -1, 0, 1, 2]
- """
- -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
- supported.
- """
-
-
-class CapabilitiesStateTitle(BaseModel):
- """Chat title state capability."""
-
- level: Literal[-2, -1, 0, 1, 2]
- """
- -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
- supported.
- """
-
-
-class CapabilitiesState(BaseModel):
- """Chat state update capabilities."""
-
- avatar: Optional[CapabilitiesStateAvatar] = None
- """Chat avatar state capability."""
-
- description: Optional[CapabilitiesStateDescription] = None
- """Chat description/topic state capability."""
-
- disappearing_timer: Optional[CapabilitiesStateDisappearingTimer] = FieldInfo(
- alias="disappearingTimer", default=None
- )
- """Disappearing-message timer state capability."""
-
- title: Optional[CapabilitiesStateTitle] = None
- """Chat title state capability."""
-
-
-class Capabilities(BaseModel):
- """Chat capabilities reported by the platform."""
-
- allowed_reactions: Optional[List[str]] = FieldInfo(alias="allowedReactions", default=None)
- """Allowed Unicode reactions. Omitted means all emoji reactions are allowed."""
-
- archive: Optional[bool] = None
- """True if archive/unarchive is supported."""
-
- attachments: Optional[Dict[str, CapabilitiesAttachments]] = None
- """
- Supported attachment message types and their per-type constraints, keyed by
- Matrix msgtype or pseudo-msgtype (for example m.image, m.video,
- org.matrix.msc3245.voice). Missing message types should be treated as rejected.
- """
-
- custom_emoji_reactions: Optional[bool] = FieldInfo(alias="customEmojiReactions", default=None)
- """True if custom emoji reactions are supported."""
-
- delete: Optional[Literal[-2, -1, 0, 1, 2]] = None
- """
- -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
- supported.
- """
-
- delete_chat: Optional[bool] = FieldInfo(alias="deleteChat", default=None)
- """True if deleting chats for the authenticated user is supported."""
-
- delete_chat_for_everyone: Optional[bool] = FieldInfo(alias="deleteChatForEveryone", default=None)
- """True if deleting chats for everyone is supported."""
-
- delete_for_me: Optional[bool] = FieldInfo(alias="deleteForMe", default=None)
- """True if deleting messages only for the authenticated user is supported."""
-
- delete_max_age: Optional[int] = FieldInfo(alias="deleteMaxAge", default=None)
- """Maximum message age for delete-for-everyone, in seconds."""
-
- disappearing_timer: Optional[CapabilitiesDisappearingTimer] = FieldInfo(alias="disappearingTimer", default=None)
- """Disappearing-message timer capabilities."""
-
- edit: Optional[Literal[-2, -1, 0, 1, 2]] = None
- """
- -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
- supported.
- """
-
- edit_max_age: Optional[int] = FieldInfo(alias="editMaxAge", default=None)
- """Maximum message age for edits, in seconds."""
-
- edit_max_count: Optional[int] = FieldInfo(alias="editMaxCount", default=None)
- """Maximum number of edits allowed for one message."""
-
- formatting: Optional[Dict[str, Literal[-2, -1, 0, 1, 2]]] = None
- """
- Supported rich-text formatting features keyed by feature name (for example bold,
- inline_code, code_block.syntax_highlighting). Omitted means no formatting
- support is advertised.
- """
-
- location_message: Optional[Literal[-2, -1, 0, 1, 2]] = FieldInfo(alias="locationMessage", default=None)
- """
- -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
- supported.
- """
-
- mark_as_unread: Optional[bool] = FieldInfo(alias="markAsUnread", default=None)
- """True if marking chats unread is supported."""
-
- max_text_length: Optional[int] = FieldInfo(alias="maxTextLength", default=None)
- """Maximum length of normal text messages."""
-
- message_request: Optional[CapabilitiesMessageRequest] = FieldInfo(alias="messageRequest", default=None)
- """Message request capabilities."""
-
- participant_actions: Optional[CapabilitiesParticipantActions] = FieldInfo(alias="participantActions", default=None)
- """Participant management capabilities."""
-
- poll: Optional[Literal[-2, -1, 0, 1, 2]] = None
- """
- -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
- supported.
- """
-
- reaction: Optional[Literal[-2, -1, 0, 1, 2]] = None
- """
- -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
- supported.
- """
-
- reaction_count: Optional[int] = FieldInfo(alias="reactionCount", default=None)
- """Maximum number of reactions allowed on a single message."""
-
- read_receipts: Optional[bool] = FieldInfo(alias="readReceipts", default=None)
- """True if read receipts are supported."""
-
- reply: Optional[Literal[-2, -1, 0, 1, 2]] = None
- """
- -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
- supported.
- """
-
- state: Optional[CapabilitiesState] = None
- """Chat state update capabilities."""
-
- thread: Optional[Literal[-2, -1, 0, 1, 2]] = None
- """
- -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
- supported.
- """
-
- typing_notifications: Optional[bool] = FieldInfo(alias="typingNotifications", default=None)
- """True if typing notifications are supported."""
-
-
-class DraftAttachmentsSize(BaseModel):
- """Pixel dimensions of the attachment."""
-
- height: Optional[float] = None
-
- width: Optional[float] = None
-
-
-class DraftAttachments(BaseModel):
- id: str
- """Draft attachment identifier."""
-
- type: Literal["file", "gif", "recorded_audio"]
- """Draft attachment type. GIF and recorded audio are mutually exclusive types."""
-
- audio_duration_seconds: Optional[float] = FieldInfo(alias="audioDurationSeconds", default=None)
- """Audio duration in seconds if known."""
-
- file_name: Optional[str] = FieldInfo(alias="fileName", default=None)
- """Original filename if available."""
-
- file_path: Optional[str] = FieldInfo(alias="filePath", default=None)
- """Local filesystem path for the draft attachment."""
-
- file_size: Optional[float] = FieldInfo(alias="fileSize", default=None)
- """File size in bytes if known."""
-
- mime_type: Optional[str] = FieldInfo(alias="mimeType", default=None)
- """MIME type if known."""
-
- size: Optional[DraftAttachmentsSize] = None
- """Pixel dimensions of the attachment."""
-
- sticker_id: Optional[str] = FieldInfo(alias="stickerID", default=None)
- """Sticker identifier if the draft attachment is a sticker."""
-
-
-class Draft(BaseModel):
- """Current draft object for this chat, or null when no draft is set."""
-
- text: str
- """Matrix HTML draft body."""
-
- attachments: Optional[Dict[str, DraftAttachments]] = None
- """Draft attachments keyed by attachment ID."""
-
class Reminder(BaseModel):
"""Current reminder for this chat, or null when no reminder is set."""
@@ -418,13 +99,13 @@ class Chat(BaseModel):
unread_count: int = FieldInfo(alias="unreadCount")
"""Number of unread messages."""
- capabilities: Optional[Capabilities] = None
+ capabilities: Optional[ChatCapabilities] = None
"""Chat capabilities reported by the platform."""
description: Optional[str] = None
"""Group chat description/topic when available."""
- draft: Optional[Draft] = None
+ draft: Optional[ChatDraft] = None
"""Current draft object for this chat, or null when no draft is set."""
img_url: Optional[str] = FieldInfo(alias="imgURL", default=None)
@@ -448,6 +129,12 @@ class Chat(BaseModel):
is_read_only: Optional[bool] = FieldInfo(alias="isReadOnly", default=None)
"""True if messages cannot be sent in this chat."""
+ labels: Optional[List[Label]] = None
+ """Labels applied to this chat.
+
+ Absent when the chat has none, or when labels are not enabled for this user.
+ """
+
last_activity: Optional[datetime] = FieldInfo(alias="lastActivity", default=None)
"""Timestamp of last activity."""
@@ -455,7 +142,23 @@ class Chat(BaseModel):
"""Last read message sortKey."""
local_chat_id: Optional[str] = FieldInfo(alias="localChatID", default=None)
- """Local chat ID specific to this Beeper Desktop installation."""
+ """Local chat ID specific to this installation."""
+
+ merge: Optional[Merge] = None
+ """
+ Present when this chat is a merged chat: one person whose conversations across
+ networks (or across accounts on the same network) are grouped into a single
+ chat. A merged chat holds no messages of its own - read messages from the member
+ chats, and send either to a member directly or to the merged chat ID to route
+ automatically.
+ """
+
+ merged_into_chat_id: Optional[str] = FieldInfo(alias="mergedIntoChatID", default=None)
+ """When this chat is a member of a merged chat, the ID of that merged chat.
+
+ Clients that render merged chats as one conversation should list the merged chat
+ and hide chats carrying this field.
+ """
message_expiry_seconds: Optional[int] = FieldInfo(alias="messageExpirySeconds", default=None)
"""Disappearing-message timer in seconds when available."""
diff --git a/src/beeper_desktop_api/types/chat_list_params.py b/src/beeper_desktop_api/types/chat_list_params.py
index d216046..b80ae83 100644
--- a/src/beeper_desktop_api/types/chat_list_params.py
+++ b/src/beeper_desktop_api/types/chat_list_params.py
@@ -22,3 +22,6 @@ class ChatListParams(TypedDict, total=False):
Pagination direction used with 'cursor': 'before' fetches older results, 'after'
fetches newer results. Defaults to 'before' when only 'cursor' is provided.
"""
+
+ limit: int
+ """Set the maximum number of chats to retrieve. Valid range: 1-200, default is 25"""
diff --git a/src/beeper_desktop_api/types/chat_search_params.py b/src/beeper_desktop_api/types/chat_search_params.py
index 661e9de..48bcb47 100644
--- a/src/beeper_desktop_api/types/chat_search_params.py
+++ b/src/beeper_desktop_api/types/chat_search_params.py
@@ -14,10 +14,7 @@
class ChatSearchParams(TypedDict, total=False):
account_ids: Annotated[SequenceNotStr[str], PropertyInfo(alias="accountIDs")]
- """
- Provide an array of account IDs to filter chats from specific messaging accounts
- only
- """
+ """Limit results to specific chat accounts."""
cursor: str
"""Opaque pagination cursor; do not inspect. Use together with 'direction'."""
@@ -30,8 +27,9 @@ class ChatSearchParams(TypedDict, total=False):
inbox: Literal["primary", "low-priority", "archive"]
"""
- Filter by inbox type: "primary" (non-archived, non-low-priority),
- "low-priority", or "archive". If not specified, shows all chats.
+ Filter by inbox type: "primary" (the chats the Beeper inbox shows: non-archived,
+ non-low-priority, honoring inbox visibility rules and labels), "low-priority",
+ or "archive". If not specified, shows all chats.
"""
include_muted: Annotated[Optional[bool], PropertyInfo(alias="includeMuted")]
@@ -40,26 +38,23 @@ class ChatSearchParams(TypedDict, total=False):
Default: true. Set to false if the user wants a more refined search.
"""
+ label_id: Annotated[str, PropertyInfo(alias="labelID")]
+ """Only include chats that carry this label. Label IDs come from GET /v1/labels."""
+
last_activity_after: Annotated[Union[str, datetime], PropertyInfo(alias="lastActivityAfter", format="iso8601")]
- """
- Provide an ISO datetime string to only retrieve chats with last activity after
- this time
- """
+ """Only include chats with last activity after this ISO 8601 datetime."""
last_activity_before: Annotated[Union[str, datetime], PropertyInfo(alias="lastActivityBefore", format="iso8601")]
- """
- Provide an ISO datetime string to only retrieve chats with last activity before
- this time
- """
+ """Only include chats with last activity before this ISO 8601 datetime."""
limit: int
"""Set the maximum number of chats to retrieve. Valid range: 1-200, default is 50"""
query: str
- """Literal token search (non-semantic).
+ """Literal chat search.
- Use single words users type (e.g., "dinner"). When multiple words provided, ALL
- must match. Case-insensitive.
+ Use words the user typed, such as "dinner". When multiple words are provided,
+ all must match. Case-insensitive.
"""
scope: Literal["titles", "participants"]
diff --git a/src/beeper_desktop_api/types/chat_start_params.py b/src/beeper_desktop_api/types/chat_start_params.py
index e70216d..69030f1 100644
--- a/src/beeper_desktop_api/types/chat_start_params.py
+++ b/src/beeper_desktop_api/types/chat_start_params.py
@@ -14,7 +14,7 @@ class ChatStartParams(TypedDict, total=False):
"""Account to create or start the chat on."""
user: Required[User]
- """Merged user-like contact payload used to resolve the best identifier."""
+ """Contact-like user payload used to resolve the best identifier."""
allow_invite: Annotated[bool, PropertyInfo(alias="allowInvite")]
"""Whether invite-based DM creation is allowed when required by the platform."""
@@ -24,7 +24,7 @@ class ChatStartParams(TypedDict, total=False):
class User(TypedDict, total=False):
- """Merged user-like contact payload used to resolve the best identifier."""
+ """Contact-like user payload used to resolve the best identifier."""
id: str
"""Known user ID when available."""
diff --git a/src/beeper_desktop_api/types/chat_update_params.py b/src/beeper_desktop_api/types/chat_update_params.py
index d485a1b..1e6da03 100644
--- a/src/beeper_desktop_api/types/chat_update_params.py
+++ b/src/beeper_desktop_api/types/chat_update_params.py
@@ -95,8 +95,8 @@ class Draft(TypedDict, total=False):
text: Required[str]
"""Draft text.
- Plain text and Markdown are converted to Matrix HTML with the same rules used by
- send and edit.
+ Plain text and Markdown are converted to Beeper rich text with the same rules
+ used by send and edit.
"""
attachments: Dict[str, DraftAttachments]
diff --git a/src/beeper_desktop_api/types/chats/messages/reaction_add_params.py b/src/beeper_desktop_api/types/chats/messages/reaction_add_params.py
index 976e0a0..24b3585 100644
--- a/src/beeper_desktop_api/types/chats/messages/reaction_add_params.py
+++ b/src/beeper_desktop_api/types/chats/messages/reaction_add_params.py
@@ -13,8 +13,8 @@ class ReactionAddParams(TypedDict, total=False):
chat_id: Required[Annotated[str, PropertyInfo(alias="chatID")]]
"""Chat ID.
- Input routes also accept the local chat ID from this Beeper Desktop installation
- when available.
+ Input routes also accept the local chat ID from this installation when
+ available.
"""
reaction_key: Required[Annotated[str, PropertyInfo(alias="reactionKey")]]
diff --git a/src/beeper_desktop_api/types/chats/messages/reaction_add_response.py b/src/beeper_desktop_api/types/chats/messages/reaction_add_response.py
index 3e0a638..d465e10 100644
--- a/src/beeper_desktop_api/types/chats/messages/reaction_add_response.py
+++ b/src/beeper_desktop_api/types/chats/messages/reaction_add_response.py
@@ -13,8 +13,8 @@ class ReactionAddResponse(BaseModel):
chat_id: str = FieldInfo(alias="chatID")
"""Chat ID.
- Input routes also accept the local chat ID from this Beeper Desktop installation
- when available.
+ Input routes also accept the local chat ID from this installation when
+ available.
"""
message_id: str = FieldInfo(alias="messageID")
diff --git a/src/beeper_desktop_api/types/chats/messages/reaction_delete_response.py b/src/beeper_desktop_api/types/chats/messages/reaction_delete_response.py
index 196a739..48b65de 100644
--- a/src/beeper_desktop_api/types/chats/messages/reaction_delete_response.py
+++ b/src/beeper_desktop_api/types/chats/messages/reaction_delete_response.py
@@ -13,8 +13,8 @@ class ReactionDeleteResponse(BaseModel):
chat_id: str = FieldInfo(alias="chatID")
"""Chat ID.
- Input routes also accept the local chat ID from this Beeper Desktop installation
- when available.
+ Input routes also accept the local chat ID from this installation when
+ available.
"""
message_id: str = FieldInfo(alias="messageID")
diff --git a/src/beeper_desktop_api/types/client_focus_params.py b/src/beeper_desktop_api/types/client_focus_params.py
index df3106f..c8e0d9d 100644
--- a/src/beeper_desktop_api/types/client_focus_params.py
+++ b/src/beeper_desktop_api/types/client_focus_params.py
@@ -17,7 +17,7 @@ class ClientFocusParams(TypedDict, total=False):
"""
draft_attachment_path: Annotated[str, PropertyInfo(alias="draftAttachmentPath")]
- """Optional image path to populate in the message input field."""
+ """Optional local image path to populate in the message input field."""
draft_text: Annotated[str, PropertyInfo(alias="draftText")]
"""Optional plain text to populate in the message input field."""
diff --git a/src/beeper_desktop_api/types/client_search_params.py b/src/beeper_desktop_api/types/client_search_params.py
index 6135164..fb7f161 100644
--- a/src/beeper_desktop_api/types/client_search_params.py
+++ b/src/beeper_desktop_api/types/client_search_params.py
@@ -9,4 +9,4 @@
class ClientSearchParams(TypedDict, total=False):
query: Required[str]
- """User-typed search text. Literal word matching (non-semantic)."""
+ """User-typed search text. Uses literal word matching."""
diff --git a/src/beeper_desktop_api/types/cookie_field.py b/src/beeper_desktop_api/types/cookie_field.py
new file mode 100644
index 0000000..5c20149
--- /dev/null
+++ b/src/beeper_desktop_api/types/cookie_field.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from .._models import BaseModel
+
+__all__ = ["CookieField"]
+
+
+class CookieField(BaseModel):
+ id: str
+ """Field ID to send back in the fields object."""
+
+ name: Optional[str] = None
+ """Cookie, header, or local storage key to collect."""
+
+ type: Optional[Literal["cookie", "header", "local_storage"]] = None
+ """Browser storage source for this value."""
diff --git a/src/beeper_desktop_api/types/disappearing_timer_capability.py b/src/beeper_desktop_api/types/disappearing_timer_capability.py
new file mode 100644
index 0000000..1bd523a
--- /dev/null
+++ b/src/beeper_desktop_api/types/disappearing_timer_capability.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from .._models import BaseModel
+
+__all__ = ["DisappearingTimerCapability"]
+
+
+class DisappearingTimerCapability(BaseModel):
+ """Disappearing-message timer capability."""
+
+ types: List[Literal["", "after_read", "after_read_by_recipient", "after_send"]]
+
+ omit_empty_timer: Optional[Literal[True]] = None
+
+ timers: Optional[List[int]] = None
diff --git a/src/beeper_desktop_api/types/group_field_capability.py b/src/beeper_desktop_api/types/group_field_capability.py
new file mode 100644
index 0000000..2ecbb92
--- /dev/null
+++ b/src/beeper_desktop_api/types/group_field_capability.py
@@ -0,0 +1,23 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from .._models import BaseModel
+from .disappearing_timer_capability import DisappearingTimerCapability
+
+__all__ = ["GroupFieldCapability"]
+
+
+class GroupFieldCapability(BaseModel):
+ """Group creation field capability."""
+
+ allowed: bool
+
+ max_length: Optional[int] = None
+
+ min_length: Optional[int] = None
+
+ required: Optional[bool] = None
+
+ settings: Optional[DisappearingTimerCapability] = None
+ """Disappearing-message timer capability."""
diff --git a/src/beeper_desktop_api/types/group_type_capabilities.py b/src/beeper_desktop_api/types/group_type_capabilities.py
new file mode 100644
index 0000000..abb841f
--- /dev/null
+++ b/src/beeper_desktop_api/types/group_type_capabilities.py
@@ -0,0 +1,35 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from .._models import BaseModel
+from .group_field_capability import GroupFieldCapability
+
+__all__ = ["GroupTypeCapabilities"]
+
+
+class GroupTypeCapabilities(BaseModel):
+ """Group creation capabilities for one group type."""
+
+ type_description: str
+
+ avatar: Optional[GroupFieldCapability] = None
+ """Group creation field capability."""
+
+ disappear: Optional[GroupFieldCapability] = None
+ """Group creation field capability."""
+
+ name: Optional[GroupFieldCapability] = None
+ """Group creation field capability."""
+
+ parent: Optional[GroupFieldCapability] = None
+ """Group creation field capability."""
+
+ participants: Optional[GroupFieldCapability] = None
+ """Group creation field capability."""
+
+ topic: Optional[GroupFieldCapability] = None
+ """Group creation field capability."""
+
+ username: Optional[GroupFieldCapability] = None
+ """Group creation field capability."""
diff --git a/src/beeper_desktop_api/types/info_retrieve_response.py b/src/beeper_desktop_api/types/info_retrieve_response.py
index 9a643aa..74124de 100644
--- a/src/beeper_desktop_api/types/info_retrieve_response.py
+++ b/src/beeper_desktop_api/types/info_retrieve_response.py
@@ -64,7 +64,7 @@ class Platform(BaseModel):
class Server(BaseModel):
base_url: str
- """Base URL of the Beeper Desktop API server"""
+ """Base URL of the Beeper Client API server"""
hostname: str
"""Listening host"""
diff --git a/src/beeper_desktop_api/types/label.py b/src/beeper_desktop_api/types/label.py
new file mode 100644
index 0000000..843038d
--- /dev/null
+++ b/src/beeper_desktop_api/types/label.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+
+__all__ = ["Label"]
+
+
+class Label(BaseModel):
+ """A user-created label that organizes chats across accounts."""
+
+ id: str
+ """Unique identifier of the label."""
+
+ name: str
+ """Display name of the label."""
diff --git a/src/beeper_desktop_api/types/label_list_response.py b/src/beeper_desktop_api/types/label_list_response.py
new file mode 100644
index 0000000..a0ea667
--- /dev/null
+++ b/src/beeper_desktop_api/types/label_list_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+from typing_extensions import TypeAlias
+
+from .label import Label
+
+__all__ = ["LabelListResponse"]
+
+LabelListResponse: TypeAlias = List[Label]
diff --git a/src/beeper_desktop_api/types/login_flow.py b/src/beeper_desktop_api/types/login_flow.py
new file mode 100644
index 0000000..fe0c71b
--- /dev/null
+++ b/src/beeper_desktop_api/types/login_flow.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from .._models import BaseModel
+
+__all__ = ["LoginFlow"]
+
+
+class LoginFlow(BaseModel):
+ """Connect or reconnect flow option for a bridge."""
+
+ id: str
+ """Flow ID to pass when creating a bridge login session."""
+
+ description: Optional[str] = None
+ """Short explanation for when to use this flow, when provided."""
+
+ name: Optional[str] = None
+ """Display name for the flow, when provided."""
diff --git a/src/beeper_desktop_api/types/login_input_field.py b/src/beeper_desktop_api/types/login_input_field.py
new file mode 100644
index 0000000..4407f27
--- /dev/null
+++ b/src/beeper_desktop_api/types/login_input_field.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from .._models import BaseModel
+
+__all__ = ["LoginInputField"]
+
+
+class LoginInputField(BaseModel):
+ id: str
+ """Field ID to send back in the fields object."""
+
+ initial_value: Optional[str] = FieldInfo(alias="initialValue", default=None)
+ """Initial field value, when provided by the network."""
+
+ label: Optional[str] = None
+ """Field label to show to the user."""
+
+ optional: Optional[bool] = None
+ """True if the user can leave this field empty."""
+
+ placeholder: Optional[str] = None
+ """Placeholder text to show when the field is empty."""
+
+ type: Optional[str] = None
+ """Suggested input type, such as text, password, or email."""
diff --git a/src/beeper_desktop_api/types/login_session.py b/src/beeper_desktop_api/types/login_session.py
new file mode 100644
index 0000000..ceb390d
--- /dev/null
+++ b/src/beeper_desktop_api/types/login_session.py
@@ -0,0 +1,163 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Union, Optional
+from typing_extensions import Literal, TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from .account import Account
+from .._models import BaseModel
+from .bridge_login import BridgeLogin
+from .cookie_field import CookieField
+from .shared.api_error import APIError
+from .login_input_field import LoginInputField
+
+__all__ = [
+ "LoginSession",
+ "CurrentStep",
+ "CurrentStepUserInput",
+ "CurrentStepCookies",
+ "CurrentStepDisplayAndWait",
+ "CurrentStepDisplayAndWaitDisplay",
+ "CurrentStepDisplayAndWaitDisplayQRCode",
+ "CurrentStepDisplayAndWaitDisplayEmoji",
+ "CurrentStepDisplayAndWaitDisplayCode",
+ "CurrentStepDisplayAndWaitDisplayEmpty",
+ "CurrentStepComplete",
+]
+
+
+class CurrentStepUserInput(BaseModel):
+ fields: List[LoginInputField]
+
+ step_id: str = FieldInfo(alias="stepID")
+
+ type: Literal["user_input"]
+
+ attachments: Optional[List[Optional[object]]] = None
+
+ instructions: Optional[str] = None
+ """User-facing instructions for this step."""
+
+
+class CurrentStepCookies(BaseModel):
+ fields: List[CookieField]
+
+ step_id: str = FieldInfo(alias="stepID")
+
+ type: Literal["cookies"]
+
+ url: str
+ """URL to open for the user."""
+
+ expected_final_url_regex: Optional[str] = FieldInfo(alias="expectedFinalURLRegex", default=None)
+ """Regular expression that identifies the final URL after sign-in."""
+
+ extract_js: Optional[str] = FieldInfo(alias="extractJS", default=None)
+ """Optional extraction script for browser-based sign-in helpers.
+
+ Treat as an opaque helper value.
+ """
+
+ instructions: Optional[str] = None
+ """User-facing instructions for this browser step."""
+
+ user_agent: Optional[str] = FieldInfo(alias="userAgent", default=None)
+ """Suggested user agent for the browser session."""
+
+
+class CurrentStepDisplayAndWaitDisplayQRCode(BaseModel):
+ data: str
+
+ type: Literal["qr"]
+
+
+class CurrentStepDisplayAndWaitDisplayEmoji(BaseModel):
+ image_url: str = FieldInfo(alias="imageURL")
+
+ type: Literal["emoji"]
+
+
+class CurrentStepDisplayAndWaitDisplayCode(BaseModel):
+ data: str
+
+ type: Literal["code"]
+
+
+class CurrentStepDisplayAndWaitDisplayEmpty(BaseModel):
+ type: Literal["nothing"]
+
+
+CurrentStepDisplayAndWaitDisplay: TypeAlias = Union[
+ CurrentStepDisplayAndWaitDisplayQRCode,
+ CurrentStepDisplayAndWaitDisplayEmoji,
+ CurrentStepDisplayAndWaitDisplayCode,
+ CurrentStepDisplayAndWaitDisplayEmpty,
+]
+
+
+class CurrentStepDisplayAndWait(BaseModel):
+ display: CurrentStepDisplayAndWaitDisplay
+
+ step_id: str = FieldInfo(alias="stepID")
+
+ type: Literal["display_and_wait"]
+
+ instructions: Optional[str] = None
+ """User-facing instructions for this step."""
+
+
+class CurrentStepComplete(BaseModel):
+ type: Literal["complete"]
+
+ account: Optional[Account] = None
+ """A chat account added to Beeper."""
+
+ instructions: Optional[str] = None
+ """Completion instructions, when provided."""
+
+ login: Optional[BridgeLogin] = None
+ """Signed-in identity for a bridge.
+
+ One bridge login can contain multiple chat accounts.
+ """
+
+ step_id: Optional[str] = FieldInfo(alias="stepID", default=None)
+
+
+CurrentStep: TypeAlias = Union[CurrentStepUserInput, CurrentStepCookies, CurrentStepDisplayAndWait, CurrentStepComplete]
+
+
+class LoginSession(BaseModel):
+ bridge_id: str = FieldInfo(alias="bridgeID")
+ """Bridge ID."""
+
+ login_session_id: str = FieldInfo(alias="loginSessionID")
+ """Temporary bridge login session ID."""
+
+ status: Literal[
+ "waiting_for_input", "waiting_for_cookies", "waiting_for_display", "complete", "cancelled", "failed"
+ ]
+
+ account: Optional[Account] = None
+ """A chat account added to Beeper."""
+
+ account_id: Optional[str] = FieldInfo(alias="accountID", default=None)
+ """Chat account ID for reconnect flows, when known."""
+
+ current_step: Optional[CurrentStep] = FieldInfo(alias="currentStep", default=None)
+ """Step the client should show or complete next.
+
+ Omitted when the session is complete, cancelled, or failed.
+ """
+
+ error: Optional[APIError] = None
+
+ login: Optional[BridgeLogin] = None
+ """Signed-in identity for a bridge.
+
+ One bridge login can contain multiple chat accounts.
+ """
+
+ login_id: Optional[str] = FieldInfo(alias="loginID", default=None)
+ """Bridge login ID for reconnect flows, when known."""
diff --git a/src/beeper_desktop_api/types/message_delete_params.py b/src/beeper_desktop_api/types/message_delete_params.py
index f8c7fff..ee4ca5e 100644
--- a/src/beeper_desktop_api/types/message_delete_params.py
+++ b/src/beeper_desktop_api/types/message_delete_params.py
@@ -14,8 +14,8 @@ class MessageDeleteParams(TypedDict, total=False):
chat_id: Required[Annotated[str, PropertyInfo(alias="chatID")]]
"""Chat ID.
- Input routes also accept the local chat ID from this Beeper Desktop installation
- when available.
+ Input routes also accept the local chat ID from this installation when
+ available.
"""
for_everyone: Annotated[Optional[bool], PropertyInfo(alias="forEveryone")]
diff --git a/src/beeper_desktop_api/types/message_search_params.py b/src/beeper_desktop_api/types/message_search_params.py
index e9bab35..09b3336 100644
--- a/src/beeper_desktop_api/types/message_search_params.py
+++ b/src/beeper_desktop_api/types/message_search_params.py
@@ -66,12 +66,11 @@ class MessageSearchParams(TypedDict, total=False):
"""
query: str
- """Literal word search (non-semantic).
+ """Literal word search.
- Finds messages containing these EXACT words in any order. Use single words users
- actually type, not concepts or phrases. Example: use "dinner" not "dinner
- plans", use "sick" not "health issues". If omitted, returns results filtered
- only by other parameters.
+ Finds messages containing these words in any order. Use words the user actually
+ typed, not inferred concepts. Example: use "dinner" rather than "dinner plans".
+ If omitted, returns results filtered only by the other parameters.
"""
sender: str
diff --git a/src/beeper_desktop_api/types/message_send_params.py b/src/beeper_desktop_api/types/message_send_params.py
index 78de9d1..f73d26d 100644
--- a/src/beeper_desktop_api/types/message_send_params.py
+++ b/src/beeper_desktop_api/types/message_send_params.py
@@ -19,8 +19,8 @@ class MessageSendParams(TypedDict, total=False):
text: str
"""Draft text.
- Plain text and Markdown are converted to Matrix HTML with the same rules used by
- send and edit.
+ Plain text and Markdown are converted to Beeper rich text with the same rules
+ used by send and edit.
"""
diff --git a/src/beeper_desktop_api/types/message_send_response.py b/src/beeper_desktop_api/types/message_send_response.py
index 37b739c..77bf6dd 100644
--- a/src/beeper_desktop_api/types/message_send_response.py
+++ b/src/beeper_desktop_api/types/message_send_response.py
@@ -9,10 +9,9 @@
class MessageSendResponse(BaseModel):
chat_id: str = FieldInfo(alias="chatID")
- """Chat ID.
+ """Chat the message was actually sent to.
- Input routes also accept the local chat ID from this Beeper Desktop installation
- when available.
+ When sending to a merged chat, this is the member chat the send was routed to.
"""
pending_message_id: str = FieldInfo(alias="pendingMessageID")
diff --git a/src/beeper_desktop_api/types/message_update_params.py b/src/beeper_desktop_api/types/message_update_params.py
index 9f62d93..024259b 100644
--- a/src/beeper_desktop_api/types/message_update_params.py
+++ b/src/beeper_desktop_api/types/message_update_params.py
@@ -13,8 +13,8 @@ class MessageUpdateParams(TypedDict, total=False):
chat_id: Required[Annotated[str, PropertyInfo(alias="chatID")]]
"""Chat ID.
- Input routes also accept the local chat ID from this Beeper Desktop installation
- when available.
+ Input routes also accept the local chat ID from this installation when
+ available.
"""
text: Required[str]
diff --git a/src/beeper_desktop_api/types/provisioning_capabilities.py b/src/beeper_desktop_api/types/provisioning_capabilities.py
new file mode 100644
index 0000000..2a6d15a
--- /dev/null
+++ b/src/beeper_desktop_api/types/provisioning_capabilities.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, Optional
+
+from .._models import BaseModel
+from .group_type_capabilities import GroupTypeCapabilities
+from .resolve_identifier_capabilities import ResolveIdentifierCapabilities
+
+__all__ = ["ProvisioningCapabilities"]
+
+
+class ProvisioningCapabilities(BaseModel):
+ """Advanced network capabilities for account lookup and group creation."""
+
+ group_creation: Dict[str, GroupTypeCapabilities]
+
+ resolve_identifier: ResolveIdentifierCapabilities
+ """Identifier lookup capabilities for this bridge."""
+
+ image_pack_import: Optional[bool] = None
diff --git a/src/beeper_desktop_api/types/resolve_identifier_capabilities.py b/src/beeper_desktop_api/types/resolve_identifier_capabilities.py
new file mode 100644
index 0000000..23038a6
--- /dev/null
+++ b/src/beeper_desktop_api/types/resolve_identifier_capabilities.py
@@ -0,0 +1,23 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+
+__all__ = ["ResolveIdentifierCapabilities"]
+
+
+class ResolveIdentifierCapabilities(BaseModel):
+ """Identifier lookup capabilities for this bridge."""
+
+ any_phone: bool
+
+ contact_list: bool
+
+ create_dm: bool
+
+ lookup_email: bool
+
+ lookup_phone: bool
+
+ lookup_username: bool
+
+ search: bool
diff --git a/src/beeper_desktop_api/types/shared/__init__.py b/src/beeper_desktop_api/types/shared/__init__.py
index cb669ed..97ecfa5 100644
--- a/src/beeper_desktop_api/types/shared/__init__.py
+++ b/src/beeper_desktop_api/types/shared/__init__.py
@@ -4,4 +4,13 @@
from .error import Error as Error
from .message import Message as Message
from .reaction import Reaction as Reaction
+from .api_error import APIError as APIError
from .attachment import Attachment as Attachment
+from .chat_draft import ChatDraft as ChatDraft
+from .send_status import SendStatus as SendStatus
+from .link_preview import LinkPreview as LinkPreview
+from .draft_attachment import DraftAttachment as DraftAttachment
+from .chat_capabilities import ChatCapabilities as ChatCapabilities
+from .app_state_snapshot import AppStateSnapshot as AppStateSnapshot
+from .attachment_capabilities import AttachmentCapabilities as AttachmentCapabilities
+from .chat_state_capabilities import ChatStateCapabilities as ChatStateCapabilities
diff --git a/src/beeper_desktop_api/types/shared/api_error.py b/src/beeper_desktop_api/types/shared/api_error.py
new file mode 100644
index 0000000..1f000f0
--- /dev/null
+++ b/src/beeper_desktop_api/types/shared/api_error.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, Optional
+
+from ..._models import BaseModel
+
+__all__ = ["APIError"]
+
+
+class APIError(BaseModel):
+ code: str
+
+ message: str
+
+ details: Optional[Dict[str, Optional[object]]] = None
diff --git a/src/beeper_desktop_api/types/shared/app_state_snapshot.py b/src/beeper_desktop_api/types/shared/app_state_snapshot.py
new file mode 100644
index 0000000..99a5ae4
--- /dev/null
+++ b/src/beeper_desktop_api/types/shared/app_state_snapshot.py
@@ -0,0 +1,184 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = [
+ "AppStateSnapshot",
+ "E2EE",
+ "E2EESecrets",
+ "Matrix",
+ "Verification",
+ "VerificationError",
+ "VerificationOtherDevice",
+ "VerificationQR",
+ "VerificationSAS",
+]
+
+
+class E2EESecrets(BaseModel):
+ """Encrypted messaging keys available on this device."""
+
+ master_key: bool = FieldInfo(alias="masterKey")
+ """Whether the account identity key is available."""
+
+ megolm_backup_key: bool = FieldInfo(alias="megolmBackupKey")
+ """Whether the encrypted message backup key is available."""
+
+ recovery_key: bool = FieldInfo(alias="recoveryKey")
+ """Whether a recovery key is available."""
+
+ self_signing_key: bool = FieldInfo(alias="selfSigningKey")
+ """Whether the device trust key is available."""
+
+ user_signing_key: bool = FieldInfo(alias="userSigningKey")
+ """Whether the user trust key is available."""
+
+
+class E2EE(BaseModel):
+ """Encrypted messaging setup status."""
+
+ cross_signing: bool = FieldInfo(alias="crossSigning")
+ """Whether this account can verify trusted devices."""
+
+ first_sync_done: bool = FieldInfo(alias="firstSyncDone")
+ """Whether the first encrypted message sync is complete."""
+
+ has_backed_up_recovery_key: bool = FieldInfo(alias="hasBackedUpRecoveryKey")
+ """Whether the user confirmed that they saved their recovery key."""
+
+ initialized: bool
+ """Whether encrypted messaging setup has started."""
+
+ key_backup: bool = FieldInfo(alias="keyBackup")
+ """Whether encrypted message backup is available."""
+
+ secrets: E2EESecrets
+ """Encrypted messaging keys available on this device."""
+
+ secret_storage: bool = FieldInfo(alias="secretStorage")
+ """Whether secure key storage is available."""
+
+ verified: bool
+ """Whether this device is trusted for encrypted messages."""
+
+ recovery_key_generated_at: Optional[float] = FieldInfo(alias="recoveryKeyGeneratedAt", default=None)
+ """Unix timestamp for when the recovery key was created."""
+
+
+class Matrix(BaseModel):
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ device_id: str = FieldInfo(alias="deviceID")
+ """Current device ID."""
+
+ homeserver: str
+ """Beeper homeserver URL for this account."""
+
+ user_id: str = FieldInfo(alias="userID")
+ """Signed-in Beeper user ID."""
+
+
+class VerificationError(BaseModel):
+ """Verification error details, if verification stopped."""
+
+ code: str
+ """Verification error code."""
+
+ reason: str
+ """User-facing verification error message."""
+
+
+class VerificationOtherDevice(BaseModel):
+ """Other device participating in verification."""
+
+ id: str
+ """Other device ID."""
+
+ name: Optional[str] = None
+ """Other device display name, if known."""
+
+
+class VerificationQR(BaseModel):
+ """QR verification data."""
+
+ data: str
+ """QR code payload to display for verification."""
+
+
+class VerificationSAS(BaseModel):
+ """Emoji or number comparison data for verification."""
+
+ emojis: str
+ """Emoji sequence to compare on both devices."""
+
+ decimals: Optional[str] = None
+ """Number sequence to compare on both devices."""
+
+
+class Verification(BaseModel):
+ """Trusted device verification progress."""
+
+ id: str
+ """Verification ID to pass in verification action paths."""
+
+ available_actions: List[Literal["accept", "cancel", "qr.confirmScanned", "sas.start", "sas.confirm"]] = FieldInfo(
+ alias="availableActions"
+ )
+ """Verification actions that are valid for the current state."""
+
+ direction: Literal["incoming", "outgoing"]
+ """Whether this device started or received the verification."""
+
+ methods: List[Literal["qr", "sas"]]
+ """Verification methods supported for this transaction."""
+
+ purpose: Literal["login", "device"]
+ """Why this verification exists."""
+
+ state: Literal["requested", "ready", "sas_ready", "qr_scanned", "done", "cancelled", "error"]
+ """Current trusted-device verification state."""
+
+ error: Optional[VerificationError] = None
+ """Verification error details, if verification stopped."""
+
+ other_device: Optional[VerificationOtherDevice] = FieldInfo(alias="otherDevice", default=None)
+ """Other device participating in verification."""
+
+ other_user_id: Optional[str] = FieldInfo(alias="otherUserID", default=None)
+ """Other Beeper user participating in verification."""
+
+ qr: Optional[VerificationQR] = None
+ """QR verification data."""
+
+ sas: Optional[VerificationSAS] = None
+ """Emoji or number comparison data for verification."""
+
+
+class AppStateSnapshot(BaseModel):
+ e2ee: E2EE
+ """Encrypted messaging setup status."""
+
+ state: Literal[
+ "needs-login",
+ "initializing",
+ "needs-cross-signing-setup",
+ "needs-verification",
+ "needs-secrets",
+ "needs-first-sync",
+ "ready",
+ ]
+ """
+ Current sign-in and encrypted messaging setup state for Beeper Desktop or Beeper
+ Server.
+ """
+
+ matrix: Optional[Matrix] = None
+ """Signed-in account details. Omitted until sign-in is complete."""
+
+ verification: Optional[Verification] = None
+ """Trusted device verification progress."""
diff --git a/src/beeper_desktop_api/types/shared/attachment.py b/src/beeper_desktop_api/types/shared/attachment.py
index 1ec39f6..cd7ed3b 100644
--- a/src/beeper_desktop_api/types/shared/attachment.py
+++ b/src/beeper_desktop_api/types/shared/attachment.py
@@ -36,7 +36,7 @@ class Attachment(BaseModel):
"""Attachment type."""
id: Optional[str] = None
- """Attachment identifier (typically an mxc:// URL).
+ """Attachment identifier, typically an mxc:// URL.
Use the download file endpoint to get a local file path.
"""
@@ -65,7 +65,7 @@ class Attachment(BaseModel):
poster_img: Optional[str] = FieldInfo(alias="posterImg", default=None)
"""Preview image URL for video attachments (poster frame).
- May be temporary or local-only to this device; download promptly if durable
+ May be temporary or available only on this device; download promptly if durable
access is needed.
"""
@@ -75,7 +75,7 @@ class Attachment(BaseModel):
src_url: Optional[str] = FieldInfo(alias="srcURL", default=None)
"""Public URL or local file path to fetch the file.
- May be temporary or local-only to this device; download promptly if durable
+ May be temporary or available only on this device; download promptly if durable
access is needed.
"""
diff --git a/src/beeper_desktop_api/types/shared/attachment_capabilities.py b/src/beeper_desktop_api/types/shared/attachment_capabilities.py
new file mode 100644
index 0000000..4ddfe76
--- /dev/null
+++ b/src/beeper_desktop_api/types/shared/attachment_capabilities.py
@@ -0,0 +1,44 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["AttachmentCapabilities"]
+
+
+class AttachmentCapabilities(BaseModel):
+ """Capabilities for one attachment message type."""
+
+ mime_types: Dict[str, Literal[-2, -1, 0, 1, 2]] = FieldInfo(alias="mimeTypes")
+ """Supported MIME types or MIME patterns for this file message type.
+
+ Missing MIME types should be treated as rejected.
+ """
+
+ caption: Optional[Literal[-2, -1, 0, 1, 2]] = None
+ """
+ -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
+ supported.
+ """
+
+ max_caption_length: Optional[int] = FieldInfo(alias="maxCaptionLength", default=None)
+ """Maximum caption length when captions are supported."""
+
+ max_duration: Optional[int] = FieldInfo(alias="maxDuration", default=None)
+ """Maximum audio or video duration in seconds."""
+
+ max_height: Optional[int] = FieldInfo(alias="maxHeight", default=None)
+ """Maximum image or video height in pixels."""
+
+ max_size: Optional[int] = FieldInfo(alias="maxSize", default=None)
+ """Maximum file size in bytes."""
+
+ max_width: Optional[int] = FieldInfo(alias="maxWidth", default=None)
+ """Maximum image or video width in pixels."""
+
+ view_once: Optional[bool] = FieldInfo(alias="viewOnce", default=None)
+ """True if this file type can be sent as view-once media."""
diff --git a/src/beeper_desktop_api/types/shared/chat_capabilities.py b/src/beeper_desktop_api/types/shared/chat_capabilities.py
new file mode 100644
index 0000000..c0223c1
--- /dev/null
+++ b/src/beeper_desktop_api/types/shared/chat_capabilities.py
@@ -0,0 +1,192 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, List, Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+from .attachment_capabilities import AttachmentCapabilities
+from .chat_state_capabilities import ChatStateCapabilities
+
+__all__ = ["ChatCapabilities", "DisappearingTimer", "MessageRequest", "ParticipantActions"]
+
+
+class DisappearingTimer(BaseModel):
+ """Disappearing-message timer capabilities."""
+
+ omit_empty_timer: Optional[bool] = FieldInfo(alias="omitEmptyTimer", default=None)
+ """True if empty timer objects should be omitted from message content."""
+
+ timers: Optional[List[int]] = None
+ """Allowed disappearing timer values in milliseconds.
+
+ Omitted means any timer is allowed.
+ """
+
+ types: Optional[List[Literal["afterRead", "afterReadByRecipient", "afterSend"]]] = None
+ """Supported disappearing timer types."""
+
+
+class MessageRequest(BaseModel):
+ """Message request capabilities."""
+
+ accept_with_button: Optional[Literal[-2, -1, 0, 1, 2]] = FieldInfo(alias="acceptWithButton", default=None)
+ """
+ -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
+ supported.
+ """
+
+ accept_with_message: Optional[Literal[-2, -1, 0, 1, 2]] = FieldInfo(alias="acceptWithMessage", default=None)
+ """
+ -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
+ supported.
+ """
+
+
+class ParticipantActions(BaseModel):
+ """Participant management capabilities."""
+
+ ban: Optional[Literal[-2, -1, 0, 1, 2]] = None
+ """
+ -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
+ supported.
+ """
+
+ invite: Optional[Literal[-2, -1, 0, 1, 2]] = None
+ """
+ -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
+ supported.
+ """
+
+ kick: Optional[Literal[-2, -1, 0, 1, 2]] = None
+ """
+ -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
+ supported.
+ """
+
+ leave: Optional[Literal[-2, -1, 0, 1, 2]] = None
+ """
+ -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
+ supported.
+ """
+
+ revoke_invite: Optional[Literal[-2, -1, 0, 1, 2]] = FieldInfo(alias="revokeInvite", default=None)
+ """
+ -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
+ supported.
+ """
+
+
+class ChatCapabilities(BaseModel):
+ """Chat capabilities reported by the platform."""
+
+ allowed_reactions: Optional[List[str]] = FieldInfo(alias="allowedReactions", default=None)
+ """Allowed Unicode reactions. Omitted means all emoji reactions are allowed."""
+
+ archive: Optional[bool] = None
+ """True if archive/unarchive is supported."""
+
+ attachments: Optional[Dict[str, AttachmentCapabilities]] = None
+ """
+ Supported attachment message types and their per-type constraints, keyed by
+ Matrix msgtype or pseudo-msgtype (for example m.image, m.video,
+ org.matrix.msc3245.voice). Missing message types should be treated as rejected.
+ """
+
+ custom_emoji_reactions: Optional[bool] = FieldInfo(alias="customEmojiReactions", default=None)
+ """True if custom emoji reactions are supported."""
+
+ delete: Optional[Literal[-2, -1, 0, 1, 2]] = None
+ """
+ -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
+ supported.
+ """
+
+ delete_chat: Optional[bool] = FieldInfo(alias="deleteChat", default=None)
+ """True if deleting chats for the authenticated user is supported."""
+
+ delete_chat_for_everyone: Optional[bool] = FieldInfo(alias="deleteChatForEveryone", default=None)
+ """True if deleting chats for everyone is supported."""
+
+ delete_for_me: Optional[bool] = FieldInfo(alias="deleteForMe", default=None)
+ """True if deleting messages only for the authenticated user is supported."""
+
+ delete_max_age: Optional[int] = FieldInfo(alias="deleteMaxAge", default=None)
+ """Maximum message age for delete-for-everyone, in seconds."""
+
+ disappearing_timer: Optional[DisappearingTimer] = FieldInfo(alias="disappearingTimer", default=None)
+ """Disappearing-message timer capabilities."""
+
+ edit: Optional[Literal[-2, -1, 0, 1, 2]] = None
+ """
+ -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
+ supported.
+ """
+
+ edit_max_age: Optional[int] = FieldInfo(alias="editMaxAge", default=None)
+ """Maximum message age for edits, in seconds."""
+
+ edit_max_count: Optional[int] = FieldInfo(alias="editMaxCount", default=None)
+ """Maximum number of edits allowed for one message."""
+
+ formatting: Optional[Dict[str, Literal[-2, -1, 0, 1, 2]]] = None
+ """
+ Supported rich-text formatting features keyed by feature name (for example bold,
+ inline_code, code_block.syntax_highlighting). Omitted means no formatting
+ support is advertised.
+ """
+
+ location_message: Optional[Literal[-2, -1, 0, 1, 2]] = FieldInfo(alias="locationMessage", default=None)
+ """
+ -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
+ supported.
+ """
+
+ mark_as_unread: Optional[bool] = FieldInfo(alias="markAsUnread", default=None)
+ """True if marking chats unread is supported."""
+
+ max_text_length: Optional[int] = FieldInfo(alias="maxTextLength", default=None)
+ """Maximum length of normal text messages."""
+
+ message_request: Optional[MessageRequest] = FieldInfo(alias="messageRequest", default=None)
+ """Message request capabilities."""
+
+ participant_actions: Optional[ParticipantActions] = FieldInfo(alias="participantActions", default=None)
+ """Participant management capabilities."""
+
+ poll: Optional[Literal[-2, -1, 0, 1, 2]] = None
+ """
+ -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
+ supported.
+ """
+
+ reaction: Optional[Literal[-2, -1, 0, 1, 2]] = None
+ """
+ -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
+ supported.
+ """
+
+ reaction_count: Optional[int] = FieldInfo(alias="reactionCount", default=None)
+ """Maximum number of reactions allowed on a single message."""
+
+ read_receipts: Optional[bool] = FieldInfo(alias="readReceipts", default=None)
+ """True if read receipts are supported."""
+
+ reply: Optional[Literal[-2, -1, 0, 1, 2]] = None
+ """
+ -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
+ supported.
+ """
+
+ state: Optional[ChatStateCapabilities] = None
+ """Chat state update capabilities."""
+
+ thread: Optional[Literal[-2, -1, 0, 1, 2]] = None
+ """
+ -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
+ supported.
+ """
+
+ typing_notifications: Optional[bool] = FieldInfo(alias="typingNotifications", default=None)
+ """True if typing notifications are supported."""
diff --git a/src/beeper_desktop_api/types/shared/chat_draft.py b/src/beeper_desktop_api/types/shared/chat_draft.py
new file mode 100644
index 0000000..bdcde97
--- /dev/null
+++ b/src/beeper_desktop_api/types/shared/chat_draft.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, Optional
+
+from ..._models import BaseModel
+from .draft_attachment import DraftAttachment
+
+__all__ = ["ChatDraft"]
+
+
+class ChatDraft(BaseModel):
+ """Current draft object for this chat, or null when no draft is set."""
+
+ text: str
+ """Rich-text draft body as returned by Beeper."""
+
+ attachments: Optional[Dict[str, DraftAttachment]] = None
+ """Draft attachments keyed by attachment ID."""
diff --git a/src/beeper_desktop_api/types/shared/chat_state_capabilities.py b/src/beeper_desktop_api/types/shared/chat_state_capabilities.py
new file mode 100644
index 0000000..15f2d75
--- /dev/null
+++ b/src/beeper_desktop_api/types/shared/chat_state_capabilities.py
@@ -0,0 +1,66 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["ChatStateCapabilities", "Avatar", "Description", "DisappearingTimer", "Title"]
+
+
+class Avatar(BaseModel):
+ """Chat avatar state capability."""
+
+ level: Literal[-2, -1, 0, 1, 2]
+ """
+ -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
+ supported.
+ """
+
+
+class Description(BaseModel):
+ """Chat description/topic state capability."""
+
+ level: Literal[-2, -1, 0, 1, 2]
+ """
+ -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
+ supported.
+ """
+
+
+class DisappearingTimer(BaseModel):
+ """Disappearing-message timer state capability."""
+
+ level: Literal[-2, -1, 0, 1, 2]
+ """
+ -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
+ supported.
+ """
+
+
+class Title(BaseModel):
+ """Chat title state capability."""
+
+ level: Literal[-2, -1, 0, 1, 2]
+ """
+ -2: rejected, -1: dropped, 0: unsupported, 1: partially supported, 2: fully
+ supported.
+ """
+
+
+class ChatStateCapabilities(BaseModel):
+ """Chat state update capabilities."""
+
+ avatar: Optional[Avatar] = None
+ """Chat avatar state capability."""
+
+ description: Optional[Description] = None
+ """Chat description/topic state capability."""
+
+ disappearing_timer: Optional[DisappearingTimer] = FieldInfo(alias="disappearingTimer", default=None)
+ """Disappearing-message timer state capability."""
+
+ title: Optional[Title] = None
+ """Chat title state capability."""
diff --git a/src/beeper_desktop_api/types/shared/draft_attachment.py b/src/beeper_desktop_api/types/shared/draft_attachment.py
new file mode 100644
index 0000000..4438468
--- /dev/null
+++ b/src/beeper_desktop_api/types/shared/draft_attachment.py
@@ -0,0 +1,47 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["DraftAttachment", "Size"]
+
+
+class Size(BaseModel):
+ """Pixel dimensions of the attachment."""
+
+ height: Optional[float] = None
+
+ width: Optional[float] = None
+
+
+class DraftAttachment(BaseModel):
+ id: str
+ """Draft attachment identifier."""
+
+ type: Literal["file", "gif", "recorded_audio"]
+ """Draft attachment type. GIF and recorded audio are mutually exclusive types."""
+
+ audio_duration_seconds: Optional[float] = FieldInfo(alias="audioDurationSeconds", default=None)
+ """Audio duration in seconds if known."""
+
+ file_name: Optional[str] = FieldInfo(alias="fileName", default=None)
+ """Original filename if available."""
+
+ file_path: Optional[str] = FieldInfo(alias="filePath", default=None)
+ """Local filesystem path for the draft attachment."""
+
+ file_size: Optional[float] = FieldInfo(alias="fileSize", default=None)
+ """File size in bytes if known."""
+
+ mime_type: Optional[str] = FieldInfo(alias="mimeType", default=None)
+ """MIME type if known."""
+
+ size: Optional[Size] = None
+ """Pixel dimensions of the attachment."""
+
+ sticker_id: Optional[str] = FieldInfo(alias="stickerID", default=None)
+ """Sticker identifier if the draft attachment is a sticker."""
diff --git a/src/beeper_desktop_api/types/shared/link_preview.py b/src/beeper_desktop_api/types/shared/link_preview.py
new file mode 100644
index 0000000..88e2bd2
--- /dev/null
+++ b/src/beeper_desktop_api/types/shared/link_preview.py
@@ -0,0 +1,50 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["LinkPreview", "ImgSize"]
+
+
+class ImgSize(BaseModel):
+ """Preview image dimensions."""
+
+ height: Optional[float] = None
+
+ width: Optional[float] = None
+
+
+class LinkPreview(BaseModel):
+ """Link preview included with a message."""
+
+ title: str
+ """Link preview title."""
+
+ url: str
+ """Resolved link URL."""
+
+ favicon: Optional[str] = None
+ """Favicon URL if available.
+
+ May be temporary or available only on this device; download promptly if durable
+ access is needed.
+ """
+
+ img: Optional[str] = None
+ """Preview image URL if available.
+
+ May be temporary or available only on this device; download promptly if durable
+ access is needed.
+ """
+
+ img_size: Optional[ImgSize] = FieldInfo(alias="imgSize", default=None)
+ """Preview image dimensions."""
+
+ original_url: Optional[str] = FieldInfo(alias="originalURL", default=None)
+ """Original URL when the displayed URL is shortened or redirected."""
+
+ summary: Optional[str] = None
+ """Link preview summary."""
diff --git a/src/beeper_desktop_api/types/shared/message.py b/src/beeper_desktop_api/types/shared/message.py
index 40206de..ed0b9c4 100644
--- a/src/beeper_desktop_api/types/shared/message.py
+++ b/src/beeper_desktop_api/types/shared/message.py
@@ -9,71 +9,10 @@
from .reaction import Reaction
from ..._models import BaseModel
from .attachment import Attachment
+from .send_status import SendStatus
+from .link_preview import LinkPreview
-__all__ = ["Message", "Link", "LinkImgSize", "SendStatus"]
-
-
-class LinkImgSize(BaseModel):
- """Preview image dimensions."""
-
- height: Optional[float] = None
-
- width: Optional[float] = None
-
-
-class Link(BaseModel):
- """Link preview included with a message."""
-
- title: str
- """Link preview title."""
-
- url: str
- """Resolved link URL."""
-
- favicon: Optional[str] = None
- """Favicon URL if available.
-
- May be temporary or local-only to this device; download promptly if durable
- access is needed.
- """
-
- img: Optional[str] = None
- """Preview image URL if available.
-
- May be temporary or local-only to this device; download promptly if durable
- access is needed.
- """
-
- img_size: Optional[LinkImgSize] = FieldInfo(alias="imgSize", default=None)
- """Preview image dimensions."""
-
- original_url: Optional[str] = FieldInfo(alias="originalURL", default=None)
- """Original URL when the displayed URL is shortened or redirected."""
-
- summary: Optional[str] = None
- """Link preview summary."""
-
-
-class SendStatus(BaseModel):
- """Message send status for this message, when reported by the bridge."""
-
- status: Literal["SUCCESS", "PENDING", "FAIL_RETRIABLE", "FAIL_PERMANENT"]
- """Current status of the message send attempt."""
-
- timestamp: datetime
- """Timestamp for the send status event."""
-
- delivered_to_users: Optional[List[str]] = FieldInfo(alias="deliveredToUsers", default=None)
- """User IDs the message was delivered to, when reported by the network."""
-
- internal_error: Optional[str] = FieldInfo(alias="internalError", default=None)
- """Internal bridge error detail. Intended for diagnostics, not end-user display."""
-
- message: Optional[str] = None
- """Human-readable send status or failure message."""
-
- reason: Optional[str] = None
- """Machine-readable failure reason. Present when the send status is a failure."""
+__all__ = ["Message"]
class Message(BaseModel):
@@ -86,14 +25,14 @@ class Message(BaseModel):
chat_id: str = FieldInfo(alias="chatID")
"""Chat ID.
- Input routes also accept the local chat ID from this Beeper Desktop installation
- when available.
+ Input routes also accept the local chat ID from this installation when
+ available.
"""
sender_id: str = FieldInfo(alias="senderID")
- """
- Matrix-style fully-qualified sender user ID, usually including a bridge prefix
- and homeserver.
+ """Fully qualified sender user ID.
+
+ Network-backed IDs usually include the network prefix and homeserver.
"""
sort_key: str = FieldInfo(alias="sortKey")
@@ -123,7 +62,7 @@ class Message(BaseModel):
linked_message_id: Optional[str] = FieldInfo(alias="linkedMessageID", default=None)
"""ID of the message this is a reply to, if any."""
- links: Optional[List[Link]] = None
+ links: Optional[List[LinkPreview]] = None
"""Link previews included with this message, if any."""
mentions: Optional[List[str]] = None
@@ -139,15 +78,13 @@ class Message(BaseModel):
"""Read receipt state for this message, when available."""
sender_name: Optional[str] = FieldInfo(alias="senderName", default=None)
- """
- Resolved sender display name (impersonator/full name/username/participant name).
- """
+ """Resolved sender display name."""
send_status: Optional[SendStatus] = FieldInfo(alias="sendStatus", default=None)
"""Message send status for this message, when reported by the bridge."""
text: Optional[str] = None
- """Matrix HTML body if present."""
+ """Rich-text message body if present."""
type: Optional[
Literal["TEXT", "NOTICE", "IMAGE", "VIDEO", "VOICE", "AUDIO", "FILE", "STICKER", "LOCATION", "REACTION"]
diff --git a/src/beeper_desktop_api/types/shared/reaction.py b/src/beeper_desktop_api/types/shared/reaction.py
index e28fb69..751db4e 100644
--- a/src/beeper_desktop_api/types/shared/reaction.py
+++ b/src/beeper_desktop_api/types/shared/reaction.py
@@ -32,6 +32,6 @@ class Reaction(BaseModel):
img_url: Optional[str] = FieldInfo(alias="imgURL", default=None)
"""URL to the reaction's image.
- May be temporary or local-only to this device; download promptly if durable
+ May be temporary or available only on this device; download promptly if durable
access is needed.
"""
diff --git a/src/beeper_desktop_api/types/shared/send_status.py b/src/beeper_desktop_api/types/shared/send_status.py
new file mode 100644
index 0000000..88e63f4
--- /dev/null
+++ b/src/beeper_desktop_api/types/shared/send_status.py
@@ -0,0 +1,36 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ..._models import BaseModel
+
+__all__ = ["SendStatus"]
+
+
+class SendStatus(BaseModel):
+ """Message send status for this message, when reported by the bridge."""
+
+ status: Literal["SUCCESS", "PENDING", "FAIL_RETRIABLE", "FAIL_PERMANENT"]
+ """Current status of the message send attempt."""
+
+ timestamp: datetime
+ """Timestamp for the send status event."""
+
+ delivered_to_users: Optional[List[str]] = FieldInfo(alias="deliveredToUsers", default=None)
+ """User IDs the message was delivered to, when reported by the network."""
+
+ internal_error: Optional[str] = FieldInfo(alias="internalError", default=None)
+ """Diagnostic error detail from the messaging network adapter.
+
+ Do not show directly to users.
+ """
+
+ message: Optional[str] = None
+ """Human-readable send status or failure message."""
+
+ reason: Optional[str] = None
+ """Machine-readable failure reason. Present when the send status is a failure."""
diff --git a/src/beeper_desktop_api/types/shared/user.py b/src/beeper_desktop_api/types/shared/user.py
index f77a31f..2f170ab 100644
--- a/src/beeper_desktop_api/types/shared/user.py
+++ b/src/beeper_desktop_api/types/shared/user.py
@@ -30,9 +30,9 @@ class User(BaseModel):
img_url: Optional[str] = FieldInfo(alias="imgURL", default=None)
"""Avatar image URL if available.
- This may be a remote URL, Matrix media URL, data URL, or local filesystem URL
- depending on source and endpoint. May be temporary or local-only to this device;
- download promptly if durable access is needed.
+ This may be a remote URL, media URL, data URL, or local file URL depending on
+ the source. May be temporary or available only on this device; download promptly
+ if durable access is needed.
"""
is_self: Optional[bool] = FieldInfo(alias="isSelf", default=None)
diff --git a/tests/api_resources/app/__init__.py b/tests/api_resources/app/__init__.py
new file mode 100644
index 0000000..fd8019a
--- /dev/null
+++ b/tests/api_resources/app/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/app/setup/__init__.py b/tests/api_resources/app/setup/__init__.py
new file mode 100644
index 0000000..fd8019a
--- /dev/null
+++ b/tests/api_resources/app/setup/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/app/setup/recovery_key/__init__.py b/tests/api_resources/app/setup/recovery_key/__init__.py
new file mode 100644
index 0000000..fd8019a
--- /dev/null
+++ b/tests/api_resources/app/setup/recovery_key/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/app/setup/recovery_key/test_reset.py b/tests/api_resources/app/setup/recovery_key/test_reset.py
new file mode 100644
index 0000000..b4795d2
--- /dev/null
+++ b/tests/api_resources/app/setup/recovery_key/test_reset.py
@@ -0,0 +1,153 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from tests.utils import assert_matches_type
+from beeper_desktop_api import BeeperDesktop, AsyncBeeperDesktop
+from beeper_desktop_api.types.app.setup.recovery_key import (
+ ResetCreateResponse,
+ ResetConfirmResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestReset:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_create(self, client: BeeperDesktop) -> None:
+ reset = client.app.setup.recovery_key.reset.create()
+ assert_matches_type(ResetCreateResponse, reset, path=["response"])
+
+ @parametrize
+ def test_method_create_with_all_params(self, client: BeeperDesktop) -> None:
+ reset = client.app.setup.recovery_key.reset.create(
+ existing_recovery_key="existingRecoveryKey",
+ )
+ assert_matches_type(ResetCreateResponse, reset, path=["response"])
+
+ @parametrize
+ def test_raw_response_create(self, client: BeeperDesktop) -> None:
+ response = client.app.setup.recovery_key.reset.with_raw_response.create()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ reset = response.parse()
+ assert_matches_type(ResetCreateResponse, reset, path=["response"])
+
+ @parametrize
+ def test_streaming_response_create(self, client: BeeperDesktop) -> None:
+ with client.app.setup.recovery_key.reset.with_streaming_response.create() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ reset = response.parse()
+ assert_matches_type(ResetCreateResponse, reset, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_confirm(self, client: BeeperDesktop) -> None:
+ reset = client.app.setup.recovery_key.reset.confirm(
+ recovery_key="x",
+ )
+ assert_matches_type(ResetConfirmResponse, reset, path=["response"])
+
+ @parametrize
+ def test_raw_response_confirm(self, client: BeeperDesktop) -> None:
+ response = client.app.setup.recovery_key.reset.with_raw_response.confirm(
+ recovery_key="x",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ reset = response.parse()
+ assert_matches_type(ResetConfirmResponse, reset, path=["response"])
+
+ @parametrize
+ def test_streaming_response_confirm(self, client: BeeperDesktop) -> None:
+ with client.app.setup.recovery_key.reset.with_streaming_response.confirm(
+ recovery_key="x",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ reset = response.parse()
+ assert_matches_type(ResetConfirmResponse, reset, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncReset:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_create(self, async_client: AsyncBeeperDesktop) -> None:
+ reset = await async_client.app.setup.recovery_key.reset.create()
+ assert_matches_type(ResetCreateResponse, reset, path=["response"])
+
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncBeeperDesktop) -> None:
+ reset = await async_client.app.setup.recovery_key.reset.create(
+ existing_recovery_key="existingRecoveryKey",
+ )
+ assert_matches_type(ResetCreateResponse, reset, path=["response"])
+
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.app.setup.recovery_key.reset.with_raw_response.create()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ reset = await response.parse()
+ assert_matches_type(ResetCreateResponse, reset, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.app.setup.recovery_key.reset.with_streaming_response.create() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ reset = await response.parse()
+ assert_matches_type(ResetCreateResponse, reset, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_confirm(self, async_client: AsyncBeeperDesktop) -> None:
+ reset = await async_client.app.setup.recovery_key.reset.confirm(
+ recovery_key="x",
+ )
+ assert_matches_type(ResetConfirmResponse, reset, path=["response"])
+
+ @parametrize
+ async def test_raw_response_confirm(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.app.setup.recovery_key.reset.with_raw_response.confirm(
+ recovery_key="x",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ reset = await response.parse()
+ assert_matches_type(ResetConfirmResponse, reset, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_confirm(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.app.setup.recovery_key.reset.with_streaming_response.confirm(
+ recovery_key="x",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ reset = await response.parse()
+ assert_matches_type(ResetConfirmResponse, reset, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/app/setup/test_recovery_key.py b/tests/api_resources/app/setup/test_recovery_key.py
new file mode 100644
index 0000000..df7d573
--- /dev/null
+++ b/tests/api_resources/app/setup/test_recovery_key.py
@@ -0,0 +1,86 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from tests.utils import assert_matches_type
+from beeper_desktop_api import BeeperDesktop, AsyncBeeperDesktop
+from beeper_desktop_api.types.app.setup import RecoveryKeyVerifyResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestRecoveryKey:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_verify(self, client: BeeperDesktop) -> None:
+ recovery_key = client.app.setup.recovery_key.verify(
+ recovery_key="x",
+ )
+ assert_matches_type(RecoveryKeyVerifyResponse, recovery_key, path=["response"])
+
+ @parametrize
+ def test_raw_response_verify(self, client: BeeperDesktop) -> None:
+ response = client.app.setup.recovery_key.with_raw_response.verify(
+ recovery_key="x",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ recovery_key = response.parse()
+ assert_matches_type(RecoveryKeyVerifyResponse, recovery_key, path=["response"])
+
+ @parametrize
+ def test_streaming_response_verify(self, client: BeeperDesktop) -> None:
+ with client.app.setup.recovery_key.with_streaming_response.verify(
+ recovery_key="x",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ recovery_key = response.parse()
+ assert_matches_type(RecoveryKeyVerifyResponse, recovery_key, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncRecoveryKey:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_verify(self, async_client: AsyncBeeperDesktop) -> None:
+ recovery_key = await async_client.app.setup.recovery_key.verify(
+ recovery_key="x",
+ )
+ assert_matches_type(RecoveryKeyVerifyResponse, recovery_key, path=["response"])
+
+ @parametrize
+ async def test_raw_response_verify(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.app.setup.recovery_key.with_raw_response.verify(
+ recovery_key="x",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ recovery_key = await response.parse()
+ assert_matches_type(RecoveryKeyVerifyResponse, recovery_key, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_verify(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.app.setup.recovery_key.with_streaming_response.verify(
+ recovery_key="x",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ recovery_key = await response.parse()
+ assert_matches_type(RecoveryKeyVerifyResponse, recovery_key, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/app/setup/test_verifications.py b/tests/api_resources/app/setup/test_verifications.py
new file mode 100644
index 0000000..d696bc3
--- /dev/null
+++ b/tests/api_resources/app/setup/test_verifications.py
@@ -0,0 +1,392 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from tests.utils import assert_matches_type
+from beeper_desktop_api import BeeperDesktop, AsyncBeeperDesktop
+from beeper_desktop_api.types.app.setup import (
+ VerificationListResponse,
+ VerificationAcceptResponse,
+ VerificationCancelResponse,
+ VerificationCreateResponse,
+ VerificationRetrieveResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestVerifications:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_create(self, client: BeeperDesktop) -> None:
+ verification = client.app.setup.verifications.create()
+ assert_matches_type(VerificationCreateResponse, verification, path=["response"])
+
+ @parametrize
+ def test_method_create_with_all_params(self, client: BeeperDesktop) -> None:
+ verification = client.app.setup.verifications.create(
+ purpose="login",
+ user_id="userID",
+ )
+ assert_matches_type(VerificationCreateResponse, verification, path=["response"])
+
+ @parametrize
+ def test_raw_response_create(self, client: BeeperDesktop) -> None:
+ response = client.app.setup.verifications.with_raw_response.create()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ verification = response.parse()
+ assert_matches_type(VerificationCreateResponse, verification, path=["response"])
+
+ @parametrize
+ def test_streaming_response_create(self, client: BeeperDesktop) -> None:
+ with client.app.setup.verifications.with_streaming_response.create() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ verification = response.parse()
+ assert_matches_type(VerificationCreateResponse, verification, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_retrieve(self, client: BeeperDesktop) -> None:
+ verification = client.app.setup.verifications.retrieve(
+ "x",
+ )
+ assert_matches_type(VerificationRetrieveResponse, verification, path=["response"])
+
+ @parametrize
+ def test_raw_response_retrieve(self, client: BeeperDesktop) -> None:
+ response = client.app.setup.verifications.with_raw_response.retrieve(
+ "x",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ verification = response.parse()
+ assert_matches_type(VerificationRetrieveResponse, verification, path=["response"])
+
+ @parametrize
+ def test_streaming_response_retrieve(self, client: BeeperDesktop) -> None:
+ with client.app.setup.verifications.with_streaming_response.retrieve(
+ "x",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ verification = response.parse()
+ assert_matches_type(VerificationRetrieveResponse, verification, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_retrieve(self, client: BeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `verification_id` but received ''"):
+ client.app.setup.verifications.with_raw_response.retrieve(
+ "",
+ )
+
+ @parametrize
+ def test_method_list(self, client: BeeperDesktop) -> None:
+ verification = client.app.setup.verifications.list()
+ assert_matches_type(VerificationListResponse, verification, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: BeeperDesktop) -> None:
+ response = client.app.setup.verifications.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ verification = response.parse()
+ assert_matches_type(VerificationListResponse, verification, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: BeeperDesktop) -> None:
+ with client.app.setup.verifications.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ verification = response.parse()
+ assert_matches_type(VerificationListResponse, verification, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_accept(self, client: BeeperDesktop) -> None:
+ verification = client.app.setup.verifications.accept(
+ "x",
+ )
+ assert_matches_type(VerificationAcceptResponse, verification, path=["response"])
+
+ @parametrize
+ def test_raw_response_accept(self, client: BeeperDesktop) -> None:
+ response = client.app.setup.verifications.with_raw_response.accept(
+ "x",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ verification = response.parse()
+ assert_matches_type(VerificationAcceptResponse, verification, path=["response"])
+
+ @parametrize
+ def test_streaming_response_accept(self, client: BeeperDesktop) -> None:
+ with client.app.setup.verifications.with_streaming_response.accept(
+ "x",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ verification = response.parse()
+ assert_matches_type(VerificationAcceptResponse, verification, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_accept(self, client: BeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `verification_id` but received ''"):
+ client.app.setup.verifications.with_raw_response.accept(
+ "",
+ )
+
+ @parametrize
+ def test_method_cancel(self, client: BeeperDesktop) -> None:
+ verification = client.app.setup.verifications.cancel(
+ verification_id="x",
+ )
+ assert_matches_type(VerificationCancelResponse, verification, path=["response"])
+
+ @parametrize
+ def test_method_cancel_with_all_params(self, client: BeeperDesktop) -> None:
+ verification = client.app.setup.verifications.cancel(
+ verification_id="x",
+ code="code",
+ reason="reason",
+ )
+ assert_matches_type(VerificationCancelResponse, verification, path=["response"])
+
+ @parametrize
+ def test_raw_response_cancel(self, client: BeeperDesktop) -> None:
+ response = client.app.setup.verifications.with_raw_response.cancel(
+ verification_id="x",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ verification = response.parse()
+ assert_matches_type(VerificationCancelResponse, verification, path=["response"])
+
+ @parametrize
+ def test_streaming_response_cancel(self, client: BeeperDesktop) -> None:
+ with client.app.setup.verifications.with_streaming_response.cancel(
+ verification_id="x",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ verification = response.parse()
+ assert_matches_type(VerificationCancelResponse, verification, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_cancel(self, client: BeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `verification_id` but received ''"):
+ client.app.setup.verifications.with_raw_response.cancel(
+ verification_id="",
+ )
+
+
+class TestAsyncVerifications:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_create(self, async_client: AsyncBeeperDesktop) -> None:
+ verification = await async_client.app.setup.verifications.create()
+ assert_matches_type(VerificationCreateResponse, verification, path=["response"])
+
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncBeeperDesktop) -> None:
+ verification = await async_client.app.setup.verifications.create(
+ purpose="login",
+ user_id="userID",
+ )
+ assert_matches_type(VerificationCreateResponse, verification, path=["response"])
+
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.app.setup.verifications.with_raw_response.create()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ verification = await response.parse()
+ assert_matches_type(VerificationCreateResponse, verification, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.app.setup.verifications.with_streaming_response.create() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ verification = await response.parse()
+ assert_matches_type(VerificationCreateResponse, verification, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_retrieve(self, async_client: AsyncBeeperDesktop) -> None:
+ verification = await async_client.app.setup.verifications.retrieve(
+ "x",
+ )
+ assert_matches_type(VerificationRetrieveResponse, verification, path=["response"])
+
+ @parametrize
+ async def test_raw_response_retrieve(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.app.setup.verifications.with_raw_response.retrieve(
+ "x",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ verification = await response.parse()
+ assert_matches_type(VerificationRetrieveResponse, verification, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_retrieve(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.app.setup.verifications.with_streaming_response.retrieve(
+ "x",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ verification = await response.parse()
+ assert_matches_type(VerificationRetrieveResponse, verification, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_retrieve(self, async_client: AsyncBeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `verification_id` but received ''"):
+ await async_client.app.setup.verifications.with_raw_response.retrieve(
+ "",
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncBeeperDesktop) -> None:
+ verification = await async_client.app.setup.verifications.list()
+ assert_matches_type(VerificationListResponse, verification, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.app.setup.verifications.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ verification = await response.parse()
+ assert_matches_type(VerificationListResponse, verification, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.app.setup.verifications.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ verification = await response.parse()
+ assert_matches_type(VerificationListResponse, verification, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_accept(self, async_client: AsyncBeeperDesktop) -> None:
+ verification = await async_client.app.setup.verifications.accept(
+ "x",
+ )
+ assert_matches_type(VerificationAcceptResponse, verification, path=["response"])
+
+ @parametrize
+ async def test_raw_response_accept(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.app.setup.verifications.with_raw_response.accept(
+ "x",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ verification = await response.parse()
+ assert_matches_type(VerificationAcceptResponse, verification, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_accept(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.app.setup.verifications.with_streaming_response.accept(
+ "x",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ verification = await response.parse()
+ assert_matches_type(VerificationAcceptResponse, verification, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_accept(self, async_client: AsyncBeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `verification_id` but received ''"):
+ await async_client.app.setup.verifications.with_raw_response.accept(
+ "",
+ )
+
+ @parametrize
+ async def test_method_cancel(self, async_client: AsyncBeeperDesktop) -> None:
+ verification = await async_client.app.setup.verifications.cancel(
+ verification_id="x",
+ )
+ assert_matches_type(VerificationCancelResponse, verification, path=["response"])
+
+ @parametrize
+ async def test_method_cancel_with_all_params(self, async_client: AsyncBeeperDesktop) -> None:
+ verification = await async_client.app.setup.verifications.cancel(
+ verification_id="x",
+ code="code",
+ reason="reason",
+ )
+ assert_matches_type(VerificationCancelResponse, verification, path=["response"])
+
+ @parametrize
+ async def test_raw_response_cancel(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.app.setup.verifications.with_raw_response.cancel(
+ verification_id="x",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ verification = await response.parse()
+ assert_matches_type(VerificationCancelResponse, verification, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_cancel(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.app.setup.verifications.with_streaming_response.cancel(
+ verification_id="x",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ verification = await response.parse()
+ assert_matches_type(VerificationCancelResponse, verification, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_cancel(self, async_client: AsyncBeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `verification_id` but received ''"):
+ await async_client.app.setup.verifications.with_raw_response.cancel(
+ verification_id="",
+ )
diff --git a/tests/api_resources/app/setup/verifications/__init__.py b/tests/api_resources/app/setup/verifications/__init__.py
new file mode 100644
index 0000000..fd8019a
--- /dev/null
+++ b/tests/api_resources/app/setup/verifications/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/app/setup/verifications/test_qr.py b/tests/api_resources/app/setup/verifications/test_qr.py
new file mode 100644
index 0000000..e029f92
--- /dev/null
+++ b/tests/api_resources/app/setup/verifications/test_qr.py
@@ -0,0 +1,162 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from tests.utils import assert_matches_type
+from beeper_desktop_api import BeeperDesktop, AsyncBeeperDesktop
+from beeper_desktop_api.types.app.setup.verifications import QRScanResponse, QRConfirmScannedResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestQR:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_confirm_scanned(self, client: BeeperDesktop) -> None:
+ qr = client.app.setup.verifications.qr.confirm_scanned(
+ "x",
+ )
+ assert_matches_type(QRConfirmScannedResponse, qr, path=["response"])
+
+ @parametrize
+ def test_raw_response_confirm_scanned(self, client: BeeperDesktop) -> None:
+ response = client.app.setup.verifications.qr.with_raw_response.confirm_scanned(
+ "x",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ qr = response.parse()
+ assert_matches_type(QRConfirmScannedResponse, qr, path=["response"])
+
+ @parametrize
+ def test_streaming_response_confirm_scanned(self, client: BeeperDesktop) -> None:
+ with client.app.setup.verifications.qr.with_streaming_response.confirm_scanned(
+ "x",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ qr = response.parse()
+ assert_matches_type(QRConfirmScannedResponse, qr, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_confirm_scanned(self, client: BeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `verification_id` but received ''"):
+ client.app.setup.verifications.qr.with_raw_response.confirm_scanned(
+ "",
+ )
+
+ @parametrize
+ def test_method_scan(self, client: BeeperDesktop) -> None:
+ qr = client.app.setup.verifications.qr.scan(
+ data="x",
+ )
+ assert_matches_type(QRScanResponse, qr, path=["response"])
+
+ @parametrize
+ def test_raw_response_scan(self, client: BeeperDesktop) -> None:
+ response = client.app.setup.verifications.qr.with_raw_response.scan(
+ data="x",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ qr = response.parse()
+ assert_matches_type(QRScanResponse, qr, path=["response"])
+
+ @parametrize
+ def test_streaming_response_scan(self, client: BeeperDesktop) -> None:
+ with client.app.setup.verifications.qr.with_streaming_response.scan(
+ data="x",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ qr = response.parse()
+ assert_matches_type(QRScanResponse, qr, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncQR:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_confirm_scanned(self, async_client: AsyncBeeperDesktop) -> None:
+ qr = await async_client.app.setup.verifications.qr.confirm_scanned(
+ "x",
+ )
+ assert_matches_type(QRConfirmScannedResponse, qr, path=["response"])
+
+ @parametrize
+ async def test_raw_response_confirm_scanned(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.app.setup.verifications.qr.with_raw_response.confirm_scanned(
+ "x",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ qr = await response.parse()
+ assert_matches_type(QRConfirmScannedResponse, qr, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_confirm_scanned(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.app.setup.verifications.qr.with_streaming_response.confirm_scanned(
+ "x",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ qr = await response.parse()
+ assert_matches_type(QRConfirmScannedResponse, qr, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_confirm_scanned(self, async_client: AsyncBeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `verification_id` but received ''"):
+ await async_client.app.setup.verifications.qr.with_raw_response.confirm_scanned(
+ "",
+ )
+
+ @parametrize
+ async def test_method_scan(self, async_client: AsyncBeeperDesktop) -> None:
+ qr = await async_client.app.setup.verifications.qr.scan(
+ data="x",
+ )
+ assert_matches_type(QRScanResponse, qr, path=["response"])
+
+ @parametrize
+ async def test_raw_response_scan(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.app.setup.verifications.qr.with_raw_response.scan(
+ data="x",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ qr = await response.parse()
+ assert_matches_type(QRScanResponse, qr, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_scan(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.app.setup.verifications.qr.with_streaming_response.scan(
+ data="x",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ qr = await response.parse()
+ assert_matches_type(QRScanResponse, qr, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/app/setup/verifications/test_sas.py b/tests/api_resources/app/setup/verifications/test_sas.py
new file mode 100644
index 0000000..cd9cd42
--- /dev/null
+++ b/tests/api_resources/app/setup/verifications/test_sas.py
@@ -0,0 +1,176 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from tests.utils import assert_matches_type
+from beeper_desktop_api import BeeperDesktop, AsyncBeeperDesktop
+from beeper_desktop_api.types.app.setup.verifications import SASStartResponse, SASConfirmResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestSAS:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_confirm(self, client: BeeperDesktop) -> None:
+ sas = client.app.setup.verifications.sas.confirm(
+ "x",
+ )
+ assert_matches_type(SASConfirmResponse, sas, path=["response"])
+
+ @parametrize
+ def test_raw_response_confirm(self, client: BeeperDesktop) -> None:
+ response = client.app.setup.verifications.sas.with_raw_response.confirm(
+ "x",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ sas = response.parse()
+ assert_matches_type(SASConfirmResponse, sas, path=["response"])
+
+ @parametrize
+ def test_streaming_response_confirm(self, client: BeeperDesktop) -> None:
+ with client.app.setup.verifications.sas.with_streaming_response.confirm(
+ "x",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ sas = response.parse()
+ assert_matches_type(SASConfirmResponse, sas, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_confirm(self, client: BeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `verification_id` but received ''"):
+ client.app.setup.verifications.sas.with_raw_response.confirm(
+ "",
+ )
+
+ @parametrize
+ def test_method_start(self, client: BeeperDesktop) -> None:
+ sas = client.app.setup.verifications.sas.start(
+ "x",
+ )
+ assert_matches_type(SASStartResponse, sas, path=["response"])
+
+ @parametrize
+ def test_raw_response_start(self, client: BeeperDesktop) -> None:
+ response = client.app.setup.verifications.sas.with_raw_response.start(
+ "x",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ sas = response.parse()
+ assert_matches_type(SASStartResponse, sas, path=["response"])
+
+ @parametrize
+ def test_streaming_response_start(self, client: BeeperDesktop) -> None:
+ with client.app.setup.verifications.sas.with_streaming_response.start(
+ "x",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ sas = response.parse()
+ assert_matches_type(SASStartResponse, sas, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_start(self, client: BeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `verification_id` but received ''"):
+ client.app.setup.verifications.sas.with_raw_response.start(
+ "",
+ )
+
+
+class TestAsyncSAS:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_confirm(self, async_client: AsyncBeeperDesktop) -> None:
+ sas = await async_client.app.setup.verifications.sas.confirm(
+ "x",
+ )
+ assert_matches_type(SASConfirmResponse, sas, path=["response"])
+
+ @parametrize
+ async def test_raw_response_confirm(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.app.setup.verifications.sas.with_raw_response.confirm(
+ "x",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ sas = await response.parse()
+ assert_matches_type(SASConfirmResponse, sas, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_confirm(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.app.setup.verifications.sas.with_streaming_response.confirm(
+ "x",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ sas = await response.parse()
+ assert_matches_type(SASConfirmResponse, sas, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_confirm(self, async_client: AsyncBeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `verification_id` but received ''"):
+ await async_client.app.setup.verifications.sas.with_raw_response.confirm(
+ "",
+ )
+
+ @parametrize
+ async def test_method_start(self, async_client: AsyncBeeperDesktop) -> None:
+ sas = await async_client.app.setup.verifications.sas.start(
+ "x",
+ )
+ assert_matches_type(SASStartResponse, sas, path=["response"])
+
+ @parametrize
+ async def test_raw_response_start(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.app.setup.verifications.sas.with_raw_response.start(
+ "x",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ sas = await response.parse()
+ assert_matches_type(SASStartResponse, sas, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_start(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.app.setup.verifications.sas.with_streaming_response.start(
+ "x",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ sas = await response.parse()
+ assert_matches_type(SASStartResponse, sas, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_start(self, async_client: AsyncBeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `verification_id` but received ''"):
+ await async_client.app.setup.verifications.sas.with_raw_response.start(
+ "",
+ )
diff --git a/tests/api_resources/app/test_setup.py b/tests/api_resources/app/test_setup.py
new file mode 100644
index 0000000..76c676e
--- /dev/null
+++ b/tests/api_resources/app/test_setup.py
@@ -0,0 +1,345 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from tests.utils import assert_matches_type
+from beeper_desktop_api import BeeperDesktop, AsyncBeeperDesktop
+from beeper_desktop_api.types.app import (
+ SetupStartResponse,
+ SetupRegisterResponse,
+ SetupResponseResponse,
+ SetupRetrieveResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestSetup:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_retrieve(self, client: BeeperDesktop) -> None:
+ setup = client.app.setup.retrieve()
+ assert_matches_type(SetupRetrieveResponse, setup, path=["response"])
+
+ @parametrize
+ def test_raw_response_retrieve(self, client: BeeperDesktop) -> None:
+ response = client.app.setup.with_raw_response.retrieve()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ setup = response.parse()
+ assert_matches_type(SetupRetrieveResponse, setup, path=["response"])
+
+ @parametrize
+ def test_streaming_response_retrieve(self, client: BeeperDesktop) -> None:
+ with client.app.setup.with_streaming_response.retrieve() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ setup = response.parse()
+ assert_matches_type(SetupRetrieveResponse, setup, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_email(self, client: BeeperDesktop) -> None:
+ setup = client.app.setup.email(
+ email="dev@stainless.com",
+ setup_request_id="setupRequestID",
+ )
+ assert setup is None
+
+ @parametrize
+ def test_raw_response_email(self, client: BeeperDesktop) -> None:
+ response = client.app.setup.with_raw_response.email(
+ email="dev@stainless.com",
+ setup_request_id="setupRequestID",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ setup = response.parse()
+ assert setup is None
+
+ @parametrize
+ def test_streaming_response_email(self, client: BeeperDesktop) -> None:
+ with client.app.setup.with_streaming_response.email(
+ email="dev@stainless.com",
+ setup_request_id="setupRequestID",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ setup = response.parse()
+ assert setup is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_register(self, client: BeeperDesktop) -> None:
+ setup = client.app.setup.register(
+ accept_terms=True,
+ lead_token="leadToken",
+ setup_request_id="setupRequestID",
+ username="x",
+ )
+ assert_matches_type(SetupRegisterResponse, setup, path=["response"])
+
+ @parametrize
+ def test_raw_response_register(self, client: BeeperDesktop) -> None:
+ response = client.app.setup.with_raw_response.register(
+ accept_terms=True,
+ lead_token="leadToken",
+ setup_request_id="setupRequestID",
+ username="x",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ setup = response.parse()
+ assert_matches_type(SetupRegisterResponse, setup, path=["response"])
+
+ @parametrize
+ def test_streaming_response_register(self, client: BeeperDesktop) -> None:
+ with client.app.setup.with_streaming_response.register(
+ accept_terms=True,
+ lead_token="leadToken",
+ setup_request_id="setupRequestID",
+ username="x",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ setup = response.parse()
+ assert_matches_type(SetupRegisterResponse, setup, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_response(self, client: BeeperDesktop) -> None:
+ setup = client.app.setup.response(
+ response="response",
+ setup_request_id="setupRequestID",
+ )
+ assert_matches_type(SetupResponseResponse, setup, path=["response"])
+
+ @parametrize
+ def test_raw_response_response(self, client: BeeperDesktop) -> None:
+ response = client.app.setup.with_raw_response.response(
+ response="response",
+ setup_request_id="setupRequestID",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ setup = response.parse()
+ assert_matches_type(SetupResponseResponse, setup, path=["response"])
+
+ @parametrize
+ def test_streaming_response_response(self, client: BeeperDesktop) -> None:
+ with client.app.setup.with_streaming_response.response(
+ response="response",
+ setup_request_id="setupRequestID",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ setup = response.parse()
+ assert_matches_type(SetupResponseResponse, setup, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_start(self, client: BeeperDesktop) -> None:
+ setup = client.app.setup.start()
+ assert_matches_type(SetupStartResponse, setup, path=["response"])
+
+ @parametrize
+ def test_raw_response_start(self, client: BeeperDesktop) -> None:
+ response = client.app.setup.with_raw_response.start()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ setup = response.parse()
+ assert_matches_type(SetupStartResponse, setup, path=["response"])
+
+ @parametrize
+ def test_streaming_response_start(self, client: BeeperDesktop) -> None:
+ with client.app.setup.with_streaming_response.start() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ setup = response.parse()
+ assert_matches_type(SetupStartResponse, setup, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncSetup:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_retrieve(self, async_client: AsyncBeeperDesktop) -> None:
+ setup = await async_client.app.setup.retrieve()
+ assert_matches_type(SetupRetrieveResponse, setup, path=["response"])
+
+ @parametrize
+ async def test_raw_response_retrieve(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.app.setup.with_raw_response.retrieve()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ setup = await response.parse()
+ assert_matches_type(SetupRetrieveResponse, setup, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_retrieve(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.app.setup.with_streaming_response.retrieve() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ setup = await response.parse()
+ assert_matches_type(SetupRetrieveResponse, setup, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_email(self, async_client: AsyncBeeperDesktop) -> None:
+ setup = await async_client.app.setup.email(
+ email="dev@stainless.com",
+ setup_request_id="setupRequestID",
+ )
+ assert setup is None
+
+ @parametrize
+ async def test_raw_response_email(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.app.setup.with_raw_response.email(
+ email="dev@stainless.com",
+ setup_request_id="setupRequestID",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ setup = await response.parse()
+ assert setup is None
+
+ @parametrize
+ async def test_streaming_response_email(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.app.setup.with_streaming_response.email(
+ email="dev@stainless.com",
+ setup_request_id="setupRequestID",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ setup = await response.parse()
+ assert setup is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_register(self, async_client: AsyncBeeperDesktop) -> None:
+ setup = await async_client.app.setup.register(
+ accept_terms=True,
+ lead_token="leadToken",
+ setup_request_id="setupRequestID",
+ username="x",
+ )
+ assert_matches_type(SetupRegisterResponse, setup, path=["response"])
+
+ @parametrize
+ async def test_raw_response_register(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.app.setup.with_raw_response.register(
+ accept_terms=True,
+ lead_token="leadToken",
+ setup_request_id="setupRequestID",
+ username="x",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ setup = await response.parse()
+ assert_matches_type(SetupRegisterResponse, setup, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_register(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.app.setup.with_streaming_response.register(
+ accept_terms=True,
+ lead_token="leadToken",
+ setup_request_id="setupRequestID",
+ username="x",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ setup = await response.parse()
+ assert_matches_type(SetupRegisterResponse, setup, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_response(self, async_client: AsyncBeeperDesktop) -> None:
+ setup = await async_client.app.setup.response(
+ response="response",
+ setup_request_id="setupRequestID",
+ )
+ assert_matches_type(SetupResponseResponse, setup, path=["response"])
+
+ @parametrize
+ async def test_raw_response_response(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.app.setup.with_raw_response.response(
+ response="response",
+ setup_request_id="setupRequestID",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ setup = await response.parse()
+ assert_matches_type(SetupResponseResponse, setup, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_response(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.app.setup.with_streaming_response.response(
+ response="response",
+ setup_request_id="setupRequestID",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ setup = await response.parse()
+ assert_matches_type(SetupResponseResponse, setup, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_start(self, async_client: AsyncBeeperDesktop) -> None:
+ setup = await async_client.app.setup.start()
+ assert_matches_type(SetupStartResponse, setup, path=["response"])
+
+ @parametrize
+ async def test_raw_response_start(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.app.setup.with_raw_response.start()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ setup = await response.parse()
+ assert_matches_type(SetupStartResponse, setup, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_start(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.app.setup.with_streaming_response.start() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ setup = await response.parse()
+ assert_matches_type(SetupStartResponse, setup, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/bridges/__init__.py b/tests/api_resources/bridges/__init__.py
new file mode 100644
index 0000000..fd8019a
--- /dev/null
+++ b/tests/api_resources/bridges/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/bridges/login_sessions/__init__.py b/tests/api_resources/bridges/login_sessions/__init__.py
new file mode 100644
index 0000000..fd8019a
--- /dev/null
+++ b/tests/api_resources/bridges/login_sessions/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/bridges/login_sessions/test_steps.py b/tests/api_resources/bridges/login_sessions/test_steps.py
new file mode 100644
index 0000000..7b8f9b4
--- /dev/null
+++ b/tests/api_resources/bridges/login_sessions/test_steps.py
@@ -0,0 +1,182 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from tests.utils import assert_matches_type
+from beeper_desktop_api import BeeperDesktop, AsyncBeeperDesktop
+from beeper_desktop_api.types import LoginSession
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestSteps:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_submit(self, client: BeeperDesktop) -> None:
+ step = client.bridges.login_sessions.steps.submit(
+ step_id="x",
+ bridge_id="local-whatsapp",
+ login_session_id="123",
+ type="user_input",
+ )
+ assert_matches_type(LoginSession, step, path=["response"])
+
+ @parametrize
+ def test_method_submit_with_all_params(self, client: BeeperDesktop) -> None:
+ step = client.bridges.login_sessions.steps.submit(
+ step_id="x",
+ bridge_id="local-whatsapp",
+ login_session_id="123",
+ type="user_input",
+ fields={"foo": "string"},
+ last_url="lastURL",
+ source="api",
+ )
+ assert_matches_type(LoginSession, step, path=["response"])
+
+ @parametrize
+ def test_raw_response_submit(self, client: BeeperDesktop) -> None:
+ response = client.bridges.login_sessions.steps.with_raw_response.submit(
+ step_id="x",
+ bridge_id="local-whatsapp",
+ login_session_id="123",
+ type="user_input",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ step = response.parse()
+ assert_matches_type(LoginSession, step, path=["response"])
+
+ @parametrize
+ def test_streaming_response_submit(self, client: BeeperDesktop) -> None:
+ with client.bridges.login_sessions.steps.with_streaming_response.submit(
+ step_id="x",
+ bridge_id="local-whatsapp",
+ login_session_id="123",
+ type="user_input",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ step = response.parse()
+ assert_matches_type(LoginSession, step, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_submit(self, client: BeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `bridge_id` but received ''"):
+ client.bridges.login_sessions.steps.with_raw_response.submit(
+ step_id="x",
+ bridge_id="",
+ login_session_id="123",
+ type="user_input",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `login_session_id` but received ''"):
+ client.bridges.login_sessions.steps.with_raw_response.submit(
+ step_id="x",
+ bridge_id="local-whatsapp",
+ login_session_id="",
+ type="user_input",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `step_id` but received ''"):
+ client.bridges.login_sessions.steps.with_raw_response.submit(
+ step_id="",
+ bridge_id="local-whatsapp",
+ login_session_id="123",
+ type="user_input",
+ )
+
+
+class TestAsyncSteps:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_submit(self, async_client: AsyncBeeperDesktop) -> None:
+ step = await async_client.bridges.login_sessions.steps.submit(
+ step_id="x",
+ bridge_id="local-whatsapp",
+ login_session_id="123",
+ type="user_input",
+ )
+ assert_matches_type(LoginSession, step, path=["response"])
+
+ @parametrize
+ async def test_method_submit_with_all_params(self, async_client: AsyncBeeperDesktop) -> None:
+ step = await async_client.bridges.login_sessions.steps.submit(
+ step_id="x",
+ bridge_id="local-whatsapp",
+ login_session_id="123",
+ type="user_input",
+ fields={"foo": "string"},
+ last_url="lastURL",
+ source="api",
+ )
+ assert_matches_type(LoginSession, step, path=["response"])
+
+ @parametrize
+ async def test_raw_response_submit(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.bridges.login_sessions.steps.with_raw_response.submit(
+ step_id="x",
+ bridge_id="local-whatsapp",
+ login_session_id="123",
+ type="user_input",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ step = await response.parse()
+ assert_matches_type(LoginSession, step, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_submit(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.bridges.login_sessions.steps.with_streaming_response.submit(
+ step_id="x",
+ bridge_id="local-whatsapp",
+ login_session_id="123",
+ type="user_input",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ step = await response.parse()
+ assert_matches_type(LoginSession, step, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_submit(self, async_client: AsyncBeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `bridge_id` but received ''"):
+ await async_client.bridges.login_sessions.steps.with_raw_response.submit(
+ step_id="x",
+ bridge_id="",
+ login_session_id="123",
+ type="user_input",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `login_session_id` but received ''"):
+ await async_client.bridges.login_sessions.steps.with_raw_response.submit(
+ step_id="x",
+ bridge_id="local-whatsapp",
+ login_session_id="",
+ type="user_input",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `step_id` but received ''"):
+ await async_client.bridges.login_sessions.steps.with_raw_response.submit(
+ step_id="",
+ bridge_id="local-whatsapp",
+ login_session_id="123",
+ type="user_input",
+ )
diff --git a/tests/api_resources/bridges/test_login_flows.py b/tests/api_resources/bridges/test_login_flows.py
new file mode 100644
index 0000000..d14a6fb
--- /dev/null
+++ b/tests/api_resources/bridges/test_login_flows.py
@@ -0,0 +1,100 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from tests.utils import assert_matches_type
+from beeper_desktop_api import BeeperDesktop, AsyncBeeperDesktop
+from beeper_desktop_api.types.bridges import LoginFlowListResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestLoginFlows:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_list(self, client: BeeperDesktop) -> None:
+ login_flow = client.bridges.login_flows.list(
+ "local-whatsapp",
+ )
+ assert_matches_type(LoginFlowListResponse, login_flow, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: BeeperDesktop) -> None:
+ response = client.bridges.login_flows.with_raw_response.list(
+ "local-whatsapp",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ login_flow = response.parse()
+ assert_matches_type(LoginFlowListResponse, login_flow, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: BeeperDesktop) -> None:
+ with client.bridges.login_flows.with_streaming_response.list(
+ "local-whatsapp",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ login_flow = response.parse()
+ assert_matches_type(LoginFlowListResponse, login_flow, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_list(self, client: BeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `bridge_id` but received ''"):
+ client.bridges.login_flows.with_raw_response.list(
+ "",
+ )
+
+
+class TestAsyncLoginFlows:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncBeeperDesktop) -> None:
+ login_flow = await async_client.bridges.login_flows.list(
+ "local-whatsapp",
+ )
+ assert_matches_type(LoginFlowListResponse, login_flow, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.bridges.login_flows.with_raw_response.list(
+ "local-whatsapp",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ login_flow = await response.parse()
+ assert_matches_type(LoginFlowListResponse, login_flow, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.bridges.login_flows.with_streaming_response.list(
+ "local-whatsapp",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ login_flow = await response.parse()
+ assert_matches_type(LoginFlowListResponse, login_flow, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_list(self, async_client: AsyncBeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `bridge_id` but received ''"):
+ await async_client.bridges.login_flows.with_raw_response.list(
+ "",
+ )
diff --git a/tests/api_resources/bridges/test_login_sessions.py b/tests/api_resources/bridges/test_login_sessions.py
new file mode 100644
index 0000000..adf87f8
--- /dev/null
+++ b/tests/api_resources/bridges/test_login_sessions.py
@@ -0,0 +1,313 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from tests.utils import assert_matches_type
+from beeper_desktop_api import BeeperDesktop, AsyncBeeperDesktop
+from beeper_desktop_api.types import LoginSession
+from beeper_desktop_api.types.bridges import LoginSessionCancelResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestLoginSessions:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_create(self, client: BeeperDesktop) -> None:
+ login_session = client.bridges.login_sessions.create(
+ bridge_id="local-whatsapp",
+ )
+ assert_matches_type(LoginSession, login_session, path=["response"])
+
+ @parametrize
+ def test_method_create_with_all_params(self, client: BeeperDesktop) -> None:
+ login_session = client.bridges.login_sessions.create(
+ bridge_id="local-whatsapp",
+ account_id="x",
+ flow_id="x",
+ login_id="x",
+ )
+ assert_matches_type(LoginSession, login_session, path=["response"])
+
+ @parametrize
+ def test_raw_response_create(self, client: BeeperDesktop) -> None:
+ response = client.bridges.login_sessions.with_raw_response.create(
+ bridge_id="local-whatsapp",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ login_session = response.parse()
+ assert_matches_type(LoginSession, login_session, path=["response"])
+
+ @parametrize
+ def test_streaming_response_create(self, client: BeeperDesktop) -> None:
+ with client.bridges.login_sessions.with_streaming_response.create(
+ bridge_id="local-whatsapp",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ login_session = response.parse()
+ assert_matches_type(LoginSession, login_session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_create(self, client: BeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `bridge_id` but received ''"):
+ client.bridges.login_sessions.with_raw_response.create(
+ bridge_id="",
+ )
+
+ @parametrize
+ def test_method_retrieve(self, client: BeeperDesktop) -> None:
+ login_session = client.bridges.login_sessions.retrieve(
+ login_session_id="123",
+ bridge_id="local-whatsapp",
+ )
+ assert_matches_type(LoginSession, login_session, path=["response"])
+
+ @parametrize
+ def test_raw_response_retrieve(self, client: BeeperDesktop) -> None:
+ response = client.bridges.login_sessions.with_raw_response.retrieve(
+ login_session_id="123",
+ bridge_id="local-whatsapp",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ login_session = response.parse()
+ assert_matches_type(LoginSession, login_session, path=["response"])
+
+ @parametrize
+ def test_streaming_response_retrieve(self, client: BeeperDesktop) -> None:
+ with client.bridges.login_sessions.with_streaming_response.retrieve(
+ login_session_id="123",
+ bridge_id="local-whatsapp",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ login_session = response.parse()
+ assert_matches_type(LoginSession, login_session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_retrieve(self, client: BeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `bridge_id` but received ''"):
+ client.bridges.login_sessions.with_raw_response.retrieve(
+ login_session_id="123",
+ bridge_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `login_session_id` but received ''"):
+ client.bridges.login_sessions.with_raw_response.retrieve(
+ login_session_id="",
+ bridge_id="local-whatsapp",
+ )
+
+ @parametrize
+ def test_method_cancel(self, client: BeeperDesktop) -> None:
+ login_session = client.bridges.login_sessions.cancel(
+ login_session_id="123",
+ bridge_id="local-whatsapp",
+ )
+ assert_matches_type(LoginSessionCancelResponse, login_session, path=["response"])
+
+ @parametrize
+ def test_raw_response_cancel(self, client: BeeperDesktop) -> None:
+ response = client.bridges.login_sessions.with_raw_response.cancel(
+ login_session_id="123",
+ bridge_id="local-whatsapp",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ login_session = response.parse()
+ assert_matches_type(LoginSessionCancelResponse, login_session, path=["response"])
+
+ @parametrize
+ def test_streaming_response_cancel(self, client: BeeperDesktop) -> None:
+ with client.bridges.login_sessions.with_streaming_response.cancel(
+ login_session_id="123",
+ bridge_id="local-whatsapp",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ login_session = response.parse()
+ assert_matches_type(LoginSessionCancelResponse, login_session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_cancel(self, client: BeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `bridge_id` but received ''"):
+ client.bridges.login_sessions.with_raw_response.cancel(
+ login_session_id="123",
+ bridge_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `login_session_id` but received ''"):
+ client.bridges.login_sessions.with_raw_response.cancel(
+ login_session_id="",
+ bridge_id="local-whatsapp",
+ )
+
+
+class TestAsyncLoginSessions:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_create(self, async_client: AsyncBeeperDesktop) -> None:
+ login_session = await async_client.bridges.login_sessions.create(
+ bridge_id="local-whatsapp",
+ )
+ assert_matches_type(LoginSession, login_session, path=["response"])
+
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncBeeperDesktop) -> None:
+ login_session = await async_client.bridges.login_sessions.create(
+ bridge_id="local-whatsapp",
+ account_id="x",
+ flow_id="x",
+ login_id="x",
+ )
+ assert_matches_type(LoginSession, login_session, path=["response"])
+
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.bridges.login_sessions.with_raw_response.create(
+ bridge_id="local-whatsapp",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ login_session = await response.parse()
+ assert_matches_type(LoginSession, login_session, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.bridges.login_sessions.with_streaming_response.create(
+ bridge_id="local-whatsapp",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ login_session = await response.parse()
+ assert_matches_type(LoginSession, login_session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_create(self, async_client: AsyncBeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `bridge_id` but received ''"):
+ await async_client.bridges.login_sessions.with_raw_response.create(
+ bridge_id="",
+ )
+
+ @parametrize
+ async def test_method_retrieve(self, async_client: AsyncBeeperDesktop) -> None:
+ login_session = await async_client.bridges.login_sessions.retrieve(
+ login_session_id="123",
+ bridge_id="local-whatsapp",
+ )
+ assert_matches_type(LoginSession, login_session, path=["response"])
+
+ @parametrize
+ async def test_raw_response_retrieve(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.bridges.login_sessions.with_raw_response.retrieve(
+ login_session_id="123",
+ bridge_id="local-whatsapp",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ login_session = await response.parse()
+ assert_matches_type(LoginSession, login_session, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_retrieve(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.bridges.login_sessions.with_streaming_response.retrieve(
+ login_session_id="123",
+ bridge_id="local-whatsapp",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ login_session = await response.parse()
+ assert_matches_type(LoginSession, login_session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_retrieve(self, async_client: AsyncBeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `bridge_id` but received ''"):
+ await async_client.bridges.login_sessions.with_raw_response.retrieve(
+ login_session_id="123",
+ bridge_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `login_session_id` but received ''"):
+ await async_client.bridges.login_sessions.with_raw_response.retrieve(
+ login_session_id="",
+ bridge_id="local-whatsapp",
+ )
+
+ @parametrize
+ async def test_method_cancel(self, async_client: AsyncBeeperDesktop) -> None:
+ login_session = await async_client.bridges.login_sessions.cancel(
+ login_session_id="123",
+ bridge_id="local-whatsapp",
+ )
+ assert_matches_type(LoginSessionCancelResponse, login_session, path=["response"])
+
+ @parametrize
+ async def test_raw_response_cancel(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.bridges.login_sessions.with_raw_response.cancel(
+ login_session_id="123",
+ bridge_id="local-whatsapp",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ login_session = await response.parse()
+ assert_matches_type(LoginSessionCancelResponse, login_session, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_cancel(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.bridges.login_sessions.with_streaming_response.cancel(
+ login_session_id="123",
+ bridge_id="local-whatsapp",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ login_session = await response.parse()
+ assert_matches_type(LoginSessionCancelResponse, login_session, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_cancel(self, async_client: AsyncBeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `bridge_id` but received ''"):
+ await async_client.bridges.login_sessions.with_raw_response.cancel(
+ login_session_id="123",
+ bridge_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `login_session_id` but received ''"):
+ await async_client.bridges.login_sessions.with_raw_response.cancel(
+ login_session_id="",
+ bridge_id="local-whatsapp",
+ )
diff --git a/tests/api_resources/bridges/test_logins.py b/tests/api_resources/bridges/test_logins.py
new file mode 100644
index 0000000..25996fe
--- /dev/null
+++ b/tests/api_resources/bridges/test_logins.py
@@ -0,0 +1,303 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from tests.utils import assert_matches_type
+from beeper_desktop_api import BeeperDesktop, AsyncBeeperDesktop
+from beeper_desktop_api.types import BridgeLogin
+from beeper_desktop_api.types.bridges import LoginListResponse, LoginRemoveResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestLogins:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_retrieve(self, client: BeeperDesktop) -> None:
+ login = client.bridges.logins.retrieve(
+ login_id="ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc",
+ bridge_id="local-whatsapp",
+ )
+ assert_matches_type(BridgeLogin, login, path=["response"])
+
+ @parametrize
+ def test_raw_response_retrieve(self, client: BeeperDesktop) -> None:
+ response = client.bridges.logins.with_raw_response.retrieve(
+ login_id="ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc",
+ bridge_id="local-whatsapp",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ login = response.parse()
+ assert_matches_type(BridgeLogin, login, path=["response"])
+
+ @parametrize
+ def test_streaming_response_retrieve(self, client: BeeperDesktop) -> None:
+ with client.bridges.logins.with_streaming_response.retrieve(
+ login_id="ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc",
+ bridge_id="local-whatsapp",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ login = response.parse()
+ assert_matches_type(BridgeLogin, login, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_retrieve(self, client: BeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `bridge_id` but received ''"):
+ client.bridges.logins.with_raw_response.retrieve(
+ login_id="ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc",
+ bridge_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `login_id` but received ''"):
+ client.bridges.logins.with_raw_response.retrieve(
+ login_id="",
+ bridge_id="local-whatsapp",
+ )
+
+ @parametrize
+ def test_method_list(self, client: BeeperDesktop) -> None:
+ login = client.bridges.logins.list(
+ "local-whatsapp",
+ )
+ assert_matches_type(LoginListResponse, login, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: BeeperDesktop) -> None:
+ response = client.bridges.logins.with_raw_response.list(
+ "local-whatsapp",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ login = response.parse()
+ assert_matches_type(LoginListResponse, login, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: BeeperDesktop) -> None:
+ with client.bridges.logins.with_streaming_response.list(
+ "local-whatsapp",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ login = response.parse()
+ assert_matches_type(LoginListResponse, login, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_list(self, client: BeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `bridge_id` but received ''"):
+ client.bridges.logins.with_raw_response.list(
+ "",
+ )
+
+ @parametrize
+ def test_method_remove(self, client: BeeperDesktop) -> None:
+ login = client.bridges.logins.remove(
+ login_id="ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc",
+ bridge_id="local-whatsapp",
+ scope="current-device",
+ )
+ assert_matches_type(LoginRemoveResponse, login, path=["response"])
+
+ @parametrize
+ def test_raw_response_remove(self, client: BeeperDesktop) -> None:
+ response = client.bridges.logins.with_raw_response.remove(
+ login_id="ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc",
+ bridge_id="local-whatsapp",
+ scope="current-device",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ login = response.parse()
+ assert_matches_type(LoginRemoveResponse, login, path=["response"])
+
+ @parametrize
+ def test_streaming_response_remove(self, client: BeeperDesktop) -> None:
+ with client.bridges.logins.with_streaming_response.remove(
+ login_id="ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc",
+ bridge_id="local-whatsapp",
+ scope="current-device",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ login = response.parse()
+ assert_matches_type(LoginRemoveResponse, login, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_remove(self, client: BeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `bridge_id` but received ''"):
+ client.bridges.logins.with_raw_response.remove(
+ login_id="ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc",
+ bridge_id="",
+ scope="current-device",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `login_id` but received ''"):
+ client.bridges.logins.with_raw_response.remove(
+ login_id="",
+ bridge_id="local-whatsapp",
+ scope="current-device",
+ )
+
+
+class TestAsyncLogins:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_retrieve(self, async_client: AsyncBeeperDesktop) -> None:
+ login = await async_client.bridges.logins.retrieve(
+ login_id="ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc",
+ bridge_id="local-whatsapp",
+ )
+ assert_matches_type(BridgeLogin, login, path=["response"])
+
+ @parametrize
+ async def test_raw_response_retrieve(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.bridges.logins.with_raw_response.retrieve(
+ login_id="ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc",
+ bridge_id="local-whatsapp",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ login = await response.parse()
+ assert_matches_type(BridgeLogin, login, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_retrieve(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.bridges.logins.with_streaming_response.retrieve(
+ login_id="ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc",
+ bridge_id="local-whatsapp",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ login = await response.parse()
+ assert_matches_type(BridgeLogin, login, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_retrieve(self, async_client: AsyncBeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `bridge_id` but received ''"):
+ await async_client.bridges.logins.with_raw_response.retrieve(
+ login_id="ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc",
+ bridge_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `login_id` but received ''"):
+ await async_client.bridges.logins.with_raw_response.retrieve(
+ login_id="",
+ bridge_id="local-whatsapp",
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncBeeperDesktop) -> None:
+ login = await async_client.bridges.logins.list(
+ "local-whatsapp",
+ )
+ assert_matches_type(LoginListResponse, login, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.bridges.logins.with_raw_response.list(
+ "local-whatsapp",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ login = await response.parse()
+ assert_matches_type(LoginListResponse, login, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.bridges.logins.with_streaming_response.list(
+ "local-whatsapp",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ login = await response.parse()
+ assert_matches_type(LoginListResponse, login, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_list(self, async_client: AsyncBeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `bridge_id` but received ''"):
+ await async_client.bridges.logins.with_raw_response.list(
+ "",
+ )
+
+ @parametrize
+ async def test_method_remove(self, async_client: AsyncBeeperDesktop) -> None:
+ login = await async_client.bridges.logins.remove(
+ login_id="ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc",
+ bridge_id="local-whatsapp",
+ scope="current-device",
+ )
+ assert_matches_type(LoginRemoveResponse, login, path=["response"])
+
+ @parametrize
+ async def test_raw_response_remove(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.bridges.logins.with_raw_response.remove(
+ login_id="ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc",
+ bridge_id="local-whatsapp",
+ scope="current-device",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ login = await response.parse()
+ assert_matches_type(LoginRemoveResponse, login, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_remove(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.bridges.logins.with_streaming_response.remove(
+ login_id="ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc",
+ bridge_id="local-whatsapp",
+ scope="current-device",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ login = await response.parse()
+ assert_matches_type(LoginRemoveResponse, login, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_remove(self, async_client: AsyncBeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `bridge_id` but received ''"):
+ await async_client.bridges.logins.with_raw_response.remove(
+ login_id="ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc",
+ bridge_id="",
+ scope="current-device",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `login_id` but received ''"):
+ await async_client.bridges.logins.with_raw_response.remove(
+ login_id="",
+ bridge_id="local-whatsapp",
+ scope="current-device",
+ )
diff --git a/tests/api_resources/test_accounts.py b/tests/api_resources/test_accounts.py
index 46ac702..dfe06af 100644
--- a/tests/api_resources/test_accounts.py
+++ b/tests/api_resources/test_accounts.py
@@ -9,7 +9,7 @@
from tests.utils import assert_matches_type
from beeper_desktop_api import BeeperDesktop, AsyncBeeperDesktop
-from beeper_desktop_api.types import AccountListResponse
+from beeper_desktop_api.types import AccountListResponse, AccountRetrieveResponse
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -17,6 +17,44 @@
class TestAccounts:
parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+ @parametrize
+ def test_method_retrieve(self, client: BeeperDesktop) -> None:
+ account = client.accounts.retrieve(
+ "accountID",
+ )
+ assert_matches_type(AccountRetrieveResponse, account, path=["response"])
+
+ @parametrize
+ def test_raw_response_retrieve(self, client: BeeperDesktop) -> None:
+ response = client.accounts.with_raw_response.retrieve(
+ "accountID",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ account = response.parse()
+ assert_matches_type(AccountRetrieveResponse, account, path=["response"])
+
+ @parametrize
+ def test_streaming_response_retrieve(self, client: BeeperDesktop) -> None:
+ with client.accounts.with_streaming_response.retrieve(
+ "accountID",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ account = response.parse()
+ assert_matches_type(AccountRetrieveResponse, account, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_retrieve(self, client: BeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_id` but received ''"):
+ client.accounts.with_raw_response.retrieve(
+ "",
+ )
+
@parametrize
def test_method_list(self, client: BeeperDesktop) -> None:
account = client.accounts.list()
@@ -48,6 +86,44 @@ class TestAsyncAccounts:
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
+ @parametrize
+ async def test_method_retrieve(self, async_client: AsyncBeeperDesktop) -> None:
+ account = await async_client.accounts.retrieve(
+ "accountID",
+ )
+ assert_matches_type(AccountRetrieveResponse, account, path=["response"])
+
+ @parametrize
+ async def test_raw_response_retrieve(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.accounts.with_raw_response.retrieve(
+ "accountID",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ account = await response.parse()
+ assert_matches_type(AccountRetrieveResponse, account, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_retrieve(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.accounts.with_streaming_response.retrieve(
+ "accountID",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ account = await response.parse()
+ assert_matches_type(AccountRetrieveResponse, account, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_retrieve(self, async_client: AsyncBeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `account_id` but received ''"):
+ await async_client.accounts.with_raw_response.retrieve(
+ "",
+ )
+
@parametrize
async def test_method_list(self, async_client: AsyncBeeperDesktop) -> None:
account = await async_client.accounts.list()
diff --git a/tests/api_resources/test_bridges.py b/tests/api_resources/test_bridges.py
new file mode 100644
index 0000000..91654f1
--- /dev/null
+++ b/tests/api_resources/test_bridges.py
@@ -0,0 +1,226 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from tests.utils import assert_matches_type
+from beeper_desktop_api import BeeperDesktop, AsyncBeeperDesktop
+from beeper_desktop_api.types import BridgeListResponse, BridgeRetrieveResponse, ProvisioningCapabilities
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestBridges:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_retrieve(self, client: BeeperDesktop) -> None:
+ bridge = client.bridges.retrieve(
+ "local-whatsapp",
+ )
+ assert_matches_type(BridgeRetrieveResponse, bridge, path=["response"])
+
+ @parametrize
+ def test_raw_response_retrieve(self, client: BeeperDesktop) -> None:
+ response = client.bridges.with_raw_response.retrieve(
+ "local-whatsapp",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ bridge = response.parse()
+ assert_matches_type(BridgeRetrieveResponse, bridge, path=["response"])
+
+ @parametrize
+ def test_streaming_response_retrieve(self, client: BeeperDesktop) -> None:
+ with client.bridges.with_streaming_response.retrieve(
+ "local-whatsapp",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ bridge = response.parse()
+ assert_matches_type(BridgeRetrieveResponse, bridge, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_retrieve(self, client: BeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `bridge_id` but received ''"):
+ client.bridges.with_raw_response.retrieve(
+ "",
+ )
+
+ @parametrize
+ def test_method_list(self, client: BeeperDesktop) -> None:
+ bridge = client.bridges.list()
+ assert_matches_type(BridgeListResponse, bridge, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: BeeperDesktop) -> None:
+ response = client.bridges.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ bridge = response.parse()
+ assert_matches_type(BridgeListResponse, bridge, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: BeeperDesktop) -> None:
+ with client.bridges.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ bridge = response.parse()
+ assert_matches_type(BridgeListResponse, bridge, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_retrieve_capabilities(self, client: BeeperDesktop) -> None:
+ bridge = client.bridges.retrieve_capabilities(
+ "local-whatsapp",
+ )
+ assert_matches_type(ProvisioningCapabilities, bridge, path=["response"])
+
+ @parametrize
+ def test_raw_response_retrieve_capabilities(self, client: BeeperDesktop) -> None:
+ response = client.bridges.with_raw_response.retrieve_capabilities(
+ "local-whatsapp",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ bridge = response.parse()
+ assert_matches_type(ProvisioningCapabilities, bridge, path=["response"])
+
+ @parametrize
+ def test_streaming_response_retrieve_capabilities(self, client: BeeperDesktop) -> None:
+ with client.bridges.with_streaming_response.retrieve_capabilities(
+ "local-whatsapp",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ bridge = response.parse()
+ assert_matches_type(ProvisioningCapabilities, bridge, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_retrieve_capabilities(self, client: BeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `bridge_id` but received ''"):
+ client.bridges.with_raw_response.retrieve_capabilities(
+ "",
+ )
+
+
+class TestAsyncBridges:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_retrieve(self, async_client: AsyncBeeperDesktop) -> None:
+ bridge = await async_client.bridges.retrieve(
+ "local-whatsapp",
+ )
+ assert_matches_type(BridgeRetrieveResponse, bridge, path=["response"])
+
+ @parametrize
+ async def test_raw_response_retrieve(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.bridges.with_raw_response.retrieve(
+ "local-whatsapp",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ bridge = await response.parse()
+ assert_matches_type(BridgeRetrieveResponse, bridge, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_retrieve(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.bridges.with_streaming_response.retrieve(
+ "local-whatsapp",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ bridge = await response.parse()
+ assert_matches_type(BridgeRetrieveResponse, bridge, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_retrieve(self, async_client: AsyncBeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `bridge_id` but received ''"):
+ await async_client.bridges.with_raw_response.retrieve(
+ "",
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncBeeperDesktop) -> None:
+ bridge = await async_client.bridges.list()
+ assert_matches_type(BridgeListResponse, bridge, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.bridges.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ bridge = await response.parse()
+ assert_matches_type(BridgeListResponse, bridge, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.bridges.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ bridge = await response.parse()
+ assert_matches_type(BridgeListResponse, bridge, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_retrieve_capabilities(self, async_client: AsyncBeeperDesktop) -> None:
+ bridge = await async_client.bridges.retrieve_capabilities(
+ "local-whatsapp",
+ )
+ assert_matches_type(ProvisioningCapabilities, bridge, path=["response"])
+
+ @parametrize
+ async def test_raw_response_retrieve_capabilities(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.bridges.with_raw_response.retrieve_capabilities(
+ "local-whatsapp",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ bridge = await response.parse()
+ assert_matches_type(ProvisioningCapabilities, bridge, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_retrieve_capabilities(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.bridges.with_streaming_response.retrieve_capabilities(
+ "local-whatsapp",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ bridge = await response.parse()
+ assert_matches_type(ProvisioningCapabilities, bridge, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_retrieve_capabilities(self, async_client: AsyncBeeperDesktop) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `bridge_id` but received ''"):
+ await async_client.bridges.with_raw_response.retrieve_capabilities(
+ "",
+ )
diff --git a/tests/api_resources/test_chats.py b/tests/api_resources/test_chats.py
index f161ea6..26deb23 100644
--- a/tests/api_resources/test_chats.py
+++ b/tests/api_resources/test_chats.py
@@ -199,6 +199,7 @@ def test_method_list_with_all_params(self, client: BeeperDesktop) -> None:
account_ids=["matrix", "discordgo", "local-whatsapp_ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc"],
cursor="1725489123456|c29tZUltc2dQYWdl",
direction="before",
+ limit=1,
)
assert_matches_type(SyncCursorNoLimit[ChatListResponse], chat, path=["response"])
@@ -411,6 +412,7 @@ def test_method_search_with_all_params(self, client: BeeperDesktop) -> None:
direction="before",
inbox="primary",
include_muted=True,
+ label_id="labelID",
last_activity_after=parse_datetime("2019-12-27T18:11:19.117Z"),
last_activity_before=parse_datetime("2019-12-27T18:11:19.117Z"),
limit=1,
@@ -672,6 +674,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncBeeperDeskto
account_ids=["matrix", "discordgo", "local-whatsapp_ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc"],
cursor="1725489123456|c29tZUltc2dQYWdl",
direction="before",
+ limit=1,
)
assert_matches_type(AsyncCursorNoLimit[ChatListResponse], chat, path=["response"])
@@ -884,6 +887,7 @@ async def test_method_search_with_all_params(self, async_client: AsyncBeeperDesk
direction="before",
inbox="primary",
include_muted=True,
+ label_id="labelID",
last_activity_after=parse_datetime("2019-12-27T18:11:19.117Z"),
last_activity_before=parse_datetime("2019-12-27T18:11:19.117Z"),
limit=1,
diff --git a/tests/api_resources/test_labels.py b/tests/api_resources/test_labels.py
new file mode 100644
index 0000000..cc5d204
--- /dev/null
+++ b/tests/api_resources/test_labels.py
@@ -0,0 +1,74 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from tests.utils import assert_matches_type
+from beeper_desktop_api import BeeperDesktop, AsyncBeeperDesktop
+from beeper_desktop_api.types import LabelListResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestLabels:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_list(self, client: BeeperDesktop) -> None:
+ label = client.labels.list()
+ assert_matches_type(LabelListResponse, label, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: BeeperDesktop) -> None:
+ response = client.labels.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ label = response.parse()
+ assert_matches_type(LabelListResponse, label, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: BeeperDesktop) -> None:
+ with client.labels.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ label = response.parse()
+ assert_matches_type(LabelListResponse, label, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncLabels:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncBeeperDesktop) -> None:
+ label = await async_client.labels.list()
+ assert_matches_type(LabelListResponse, label, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncBeeperDesktop) -> None:
+ response = await async_client.labels.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ label = await response.parse()
+ assert_matches_type(LabelListResponse, label, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncBeeperDesktop) -> None:
+ async with async_client.labels.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ label = await response.parse()
+ assert_matches_type(LabelListResponse, label, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/test_models.py b/tests/test_models.py
index e71da85..d228ea1 100644
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -1,7 +1,8 @@
import json
-from typing import TYPE_CHECKING, Any, Dict, List, Union, Optional, cast
+from typing import TYPE_CHECKING, Any, Dict, List, Union, Iterable, Optional, cast
from datetime import datetime, timezone
-from typing_extensions import Literal, Annotated, TypeAliasType
+from collections import deque
+from typing_extensions import Literal, Annotated, TypedDict, TypeAliasType
import pytest
import pydantic
@@ -9,7 +10,7 @@
from beeper_desktop_api._utils import PropertyInfo
from beeper_desktop_api._compat import PYDANTIC_V1, parse_obj, model_dump, model_json
-from beeper_desktop_api._models import DISCRIMINATOR_CACHE, BaseModel, construct_type
+from beeper_desktop_api._models import DISCRIMINATOR_CACHE, BaseModel, EagerIterable, construct_type
class BasicModel(BaseModel):
@@ -961,3 +962,56 @@ def __getattr__(self, attr: str) -> Item: ...
assert model.a.prop == 1
assert isinstance(model.a, Item)
assert model.other == "foo"
+
+
+# NOTE: Workaround for Pydantic Iterable behavior.
+# Iterable fields are replaced with a ValidatorIterator and may be consumed
+# during serialization, which can cause subsequent dumps to return empty data.
+# See: https://github.com/pydantic/pydantic/issues/9541
+@pytest.mark.parametrize(
+ "data, expected_validated",
+ [
+ ([1, 2, 3], [1, 2, 3]),
+ ((1, 2, 3), (1, 2, 3)),
+ (set([1, 2, 3]), set([1, 2, 3])),
+ (iter([1, 2, 3]), [1, 2, 3]),
+ ([], []),
+ ((x for x in [1, 2, 3]), [1, 2, 3]),
+ (map(lambda x: x, [1, 2, 3]), [1, 2, 3]),
+ (frozenset([1, 2, 3]), frozenset([1, 2, 3])),
+ (deque([1, 2, 3]), deque([1, 2, 3])),
+ ],
+ ids=["list", "tuple", "set", "iterator", "empty", "generator", "map", "frozenset", "deque"],
+)
+@pytest.mark.skipif(PYDANTIC_V1, reason="this is only supported in pydantic v2")
+def test_iterable_construction(data: Iterable[int], expected_validated: Iterable[int]) -> None:
+ class TypeWithIterable(TypedDict):
+ items: EagerIterable[int]
+
+ class Model(BaseModel):
+ data: TypeWithIterable
+
+ m = Model.model_validate({"data": {"items": data}})
+ assert m.data["items"] == expected_validated
+
+ # Verify repeated dumps don't lose data (the original bug)
+ assert m.model_dump()["data"]["items"] == list(expected_validated)
+ assert m.model_dump()["data"]["items"] == list(expected_validated)
+
+
+@pytest.mark.skipif(PYDANTIC_V1, reason="this is only supported in pydantic v2")
+def test_iterable_construction_str_falls_back_to_list() -> None:
+ # str is iterable (over chars), but str(list_of_chars) produces the list's repr
+ # rather than reconstructing a string from items. We special-case str to fall
+ # back to list instead of attempting reconstruction.
+ class TypeWithIterable(TypedDict):
+ items: EagerIterable[str]
+
+ class Model(BaseModel):
+ data: TypeWithIterable
+
+ m = Model.model_validate({"data": {"items": "hello"}})
+
+ # falls back to list of chars rather than calling str(["h", "e", "l", "l", "o"])
+ assert m.data["items"] == ["h", "e", "l", "l", "o"]
+ assert m.model_dump()["data"]["items"] == ["h", "e", "l", "l", "o"]