diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3e3308d..1baf5c0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: lint: timeout-minutes: 10 name: lint - runs-on: ${{ github.repository == 'stainless-sdks/blooio-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && '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') steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -41,7 +41,7 @@ jobs: permissions: contents: read id-token: write - runs-on: ${{ github.repository == 'stainless-sdks/blooio-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -78,7 +78,7 @@ jobs: test: timeout-minutes: 10 name: test - runs-on: ${{ github.repository == 'stainless-sdks/blooio-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + runs-on: ${{ startsWith(github.repository, 'stainless-sdks/') && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/.release-please-manifest.json b/.release-please-manifest.json index d0ab664..2a8f4ff 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.2.0" + ".": "1.3.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 000c763..fa485b0 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 54 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/blooio/blooio-bce90b321b6782841d7834e0e0689b1812ef1f10f8f2823cd9908185d9f306e6.yml -openapi_spec_hash: a90c7586ed5e83a938d72a286efa9f8e +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/blooio/blooio-4cfd8aadba2015ef9746f076452ef853f194ce40602854c29ce76842eff021a9.yml +openapi_spec_hash: 8137fd23f3a30da82f83a8ac74a433f8 config_hash: 839e191496287972d18d39dcab8b19b7 diff --git a/CHANGELOG.md b/CHANGELOG.md index ba8d78c..3278c02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog +## 1.3.0 (2026-07-25) + +Full Changelog: [v1.2.0...v1.3.0](https://github.com/Blooio/blooio-python-sdk/compare/v1.2.0...v1.3.0) + +### Features + +* **api:** api update ([30e8dd1](https://github.com/Blooio/blooio-python-sdk/commit/30e8dd13b8e2ca5988573a2cc482c122ea1650ae)) +* **api:** api update ([f6caf8e](https://github.com/Blooio/blooio-python-sdk/commit/f6caf8e2ab1630818c57ab8142a5107cc534c57c)) +* **api:** api update ([b3ba795](https://github.com/Blooio/blooio-python-sdk/commit/b3ba7951d022e732ae0e3a458520ed3f42a49a5f)) +* **api:** api update ([d9b82ea](https://github.com/Blooio/blooio-python-sdk/commit/d9b82ea21659239202fb8a4d63366d54ea60fcbe)) +* **api:** api update ([2e075ad](https://github.com/Blooio/blooio-python-sdk/commit/2e075ad61159d021fc31bd6fda9f0152ea703a34)) +* **api:** api update ([178129e](https://github.com/Blooio/blooio-python-sdk/commit/178129ec16b48bb7be95b672071147fb1a4bf679)) +* **api:** api update ([03aecef](https://github.com/Blooio/blooio-python-sdk/commit/03aecefb518d40f36c37d71cfa7637cefb0575c4)) +* **api:** api update ([e4708cb](https://github.com/Blooio/blooio-python-sdk/commit/e4708cb01510e75961ac6a058308140cf69424ee)) +* **api:** api update ([bb8074b](https://github.com/Blooio/blooio-python-sdk/commit/bb8074b817bc550cba91e705ebbd710dee71caba)) +* **stlc:** configurable CI runner and private-production-repo support in workflow templates ([33ac236](https://github.com/Blooio/blooio-python-sdk/commit/33ac236b6e204193681f2db4822517a0de75b182)) + + +### Bug Fixes + +* **auth:** prioritize first auth header ([d692d48](https://github.com/Blooio/blooio-python-sdk/commit/d692d48a74bebaa8b86c6e645941c85ebab88c2e)) +* **internal:** resolve build failures ([12fb122](https://github.com/Blooio/blooio-python-sdk/commit/12fb122bc963ca30ba0d2767352d2f3fde733bfb)) + ## 1.2.0 (2026-05-14) Full Changelog: [v1.1.1...v1.2.0](https://github.com/Blooio/blooio-python-sdk/compare/v1.1.1...v1.2.0) diff --git a/pyproject.toml b/pyproject.toml index 4c6c403..4735d59 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "blooio" -version = "1.2.0" +version = "1.3.0" description = "The official Python library for the blooio API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/scripts/lint b/scripts/lint index dea1dbb..ce63bbb 100755 --- a/scripts/lint +++ b/scripts/lint @@ -13,7 +13,7 @@ else fi echo "==> Running pyright" -uv run pyright +uv run pyright -p . echo "==> Running mypy" uv run mypy . diff --git a/src/blooio/_client.py b/src/blooio/_client.py index 9557526..5b0f012 100644 --- a/src/blooio/_client.py +++ b/src/blooio/_client.py @@ -185,9 +185,11 @@ def qs(self) -> Querystring: @override def _auth_headers(self, security: SecurityOptions) -> dict[str, str]: - return { - **(self._bearer_auth if security.get("bearer_auth", False) else {}), - } + headers: dict[str, str] = {} + if security.get("bearer_auth", False): + for key, value in self._bearer_auth.items(): + headers.setdefault(key, value) + return headers @property def _bearer_auth(self) -> dict[str, str]: @@ -424,9 +426,11 @@ def qs(self) -> Querystring: @override def _auth_headers(self, security: SecurityOptions) -> dict[str, str]: - return { - **(self._bearer_auth if security.get("bearer_auth", False) else {}), - } + headers: dict[str, str] = {} + if security.get("bearer_auth", False): + for key, value in self._bearer_auth.items(): + headers.setdefault(key, value) + return headers @property def _bearer_auth(self) -> dict[str, str]: diff --git a/src/blooio/_version.py b/src/blooio/_version.py index 2fd6bd0..b50668b 100644 --- a/src/blooio/_version.py +++ b/src/blooio/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "blooio" -__version__ = "1.2.0" # x-release-please-version +__version__ = "1.3.0" # x-release-please-version diff --git a/src/blooio/resources/chats/background.py b/src/blooio/resources/chats/background.py index a0c1121..e76ec76 100644 --- a/src/blooio/resources/chats/background.py +++ b/src/blooio/resources/chats/background.py @@ -25,7 +25,7 @@ class BackgroundResource(SyncAPIResource): - """Set, get, and remove conversation backgrounds""" + """View conversations and messages""" @cached_property def with_raw_response(self) -> BackgroundResourceWithRawResponse: @@ -132,12 +132,27 @@ def set( Works for both 1-on-1 and group chats. - The uploaded image is converted into a PosterKit-compatible archive and applied - to the iMessage conversation on the linked device. Supported formats: JPEG, PNG, - GIF, WebP, HEIC/HEIF. Maximum file size: 10 MB. + The request body must be `multipart/form-data` with a single `background` field + containing the **raw image file bytes** (not a URL or base64 string). Supported + formats: JPEG, PNG, GIF, WebP, HEIC/HEIF. Maximum file size: 10 MB. + + **Example with curl** — note the `@` prefix that tells curl to read the file + from disk: + + ```bash + curl -X PUT "https://api.blooio.com/v2/api/chats/%2B15551234567/background" \\ + -H "Authorization: Bearer YOUR_API_KEY" \\ + -F "background=@/path/to/image.jpg;type=image/jpeg" + ``` + + When the chat id is a phone number, percent-encode the leading `+` as `%2B` in + the URL path. Args: - background: The image file to set as the chat background + background: Binary image file upload (JPEG, PNG, GIF, WebP, HEIC/HEIF, max 10 MB). Send as a + file field in `multipart/form-data` — e.g. `-F "background=@/path/to/image.jpg"` + with curl, or a `File`/`Blob` appended to `FormData` in JavaScript. Do NOT send + a URL or base64 string. extra_headers: Send extra headers @@ -167,7 +182,7 @@ def set( class AsyncBackgroundResource(AsyncAPIResource): - """Set, get, and remove conversation backgrounds""" + """View conversations and messages""" @cached_property def with_raw_response(self) -> AsyncBackgroundResourceWithRawResponse: @@ -274,12 +289,27 @@ async def set( Works for both 1-on-1 and group chats. - The uploaded image is converted into a PosterKit-compatible archive and applied - to the iMessage conversation on the linked device. Supported formats: JPEG, PNG, - GIF, WebP, HEIC/HEIF. Maximum file size: 10 MB. + The request body must be `multipart/form-data` with a single `background` field + containing the **raw image file bytes** (not a URL or base64 string). Supported + formats: JPEG, PNG, GIF, WebP, HEIC/HEIF. Maximum file size: 10 MB. + + **Example with curl** — note the `@` prefix that tells curl to read the file + from disk: + + ```bash + curl -X PUT "https://api.blooio.com/v2/api/chats/%2B15551234567/background" \\ + -H "Authorization: Bearer YOUR_API_KEY" \\ + -F "background=@/path/to/image.jpg;type=image/jpeg" + ``` + + When the chat id is a phone number, percent-encode the leading `+` as `%2B` in + the URL path. Args: - background: The image file to set as the chat background + background: Binary image file upload (JPEG, PNG, GIF, WebP, HEIC/HEIF, max 10 MB). Send as a + file field in `multipart/form-data` — e.g. `-F "background=@/path/to/image.jpg"` + with curl, or a `File`/`Blob` appended to `FormData` in JavaScript. Do NOT send + a URL or base64 string. extra_headers: Send extra headers diff --git a/src/blooio/resources/chats/chats.py b/src/blooio/resources/chats/chats.py index 95f2bce..5c77eec 100644 --- a/src/blooio/resources/chats/chats.py +++ b/src/blooio/resources/chats/chats.py @@ -78,7 +78,7 @@ def typing(self) -> TypingResource: @cached_property def background(self) -> BackgroundResource: - """Set, get, and remove conversation backgrounds""" + """View conversations and messages""" return BackgroundResource(self._client) @cached_property @@ -152,9 +152,13 @@ def list( message. Args: - limit: Maximum number of items to return (1-200) + limit: Maximum number of items to return in a single response. Must be between 1 and + 200; defaults to 50. Use together with `offset` to page through large result + sets. - offset: Number of items to skip + offset: Number of items to skip before returning results. Combine with `limit` for + page-based pagination (e.g. `offset=50&limit=50` returns the second page). + Defaults to 0. q: Search query (matches phone/email or contact name) @@ -239,6 +243,10 @@ def share_contact_card( will be piggybacked onto the next outgoing message (text or attachment) sent to this chat. This is idempotent — calling it multiple times is harmless. + ⚠️ **Plan requirement:** Contact card sharing is only available on **Dedicated + Commercial** and **Dedicated Enterprise** plans. Numbers on other plans receive + a `403`. + Args: extra_headers: Send extra headers @@ -279,7 +287,7 @@ def typing(self) -> AsyncTypingResource: @cached_property def background(self) -> AsyncBackgroundResource: - """Set, get, and remove conversation backgrounds""" + """View conversations and messages""" return AsyncBackgroundResource(self._client) @cached_property @@ -353,9 +361,13 @@ async def list( message. Args: - limit: Maximum number of items to return (1-200) + limit: Maximum number of items to return in a single response. Must be between 1 and + 200; defaults to 50. Use together with `offset` to page through large result + sets. - offset: Number of items to skip + offset: Number of items to skip before returning results. Combine with `limit` for + page-based pagination (e.g. `offset=50&limit=50` returns the second page). + Defaults to 0. q: Search query (matches phone/email or contact name) @@ -440,6 +452,10 @@ async def share_contact_card( will be piggybacked onto the next outgoing message (text or attachment) sent to this chat. This is idempotent — calling it multiple times is harmless. + ⚠️ **Plan requirement:** Contact card sharing is only available on **Dedicated + Commercial** and **Dedicated Enterprise** plans. Numbers on other plans receive + a `403`. + Args: extra_headers: Send extra headers @@ -496,7 +512,7 @@ def typing(self) -> TypingResourceWithRawResponse: @cached_property def background(self) -> BackgroundResourceWithRawResponse: - """Set, get, and remove conversation backgrounds""" + """View conversations and messages""" return BackgroundResourceWithRawResponse(self._chats.background) @@ -536,7 +552,7 @@ def typing(self) -> AsyncTypingResourceWithRawResponse: @cached_property def background(self) -> AsyncBackgroundResourceWithRawResponse: - """Set, get, and remove conversation backgrounds""" + """View conversations and messages""" return AsyncBackgroundResourceWithRawResponse(self._chats.background) @@ -576,7 +592,7 @@ def typing(self) -> TypingResourceWithStreamingResponse: @cached_property def background(self) -> BackgroundResourceWithStreamingResponse: - """Set, get, and remove conversation backgrounds""" + """View conversations and messages""" return BackgroundResourceWithStreamingResponse(self._chats.background) @@ -616,5 +632,5 @@ def typing(self) -> AsyncTypingResourceWithStreamingResponse: @cached_property def background(self) -> AsyncBackgroundResourceWithStreamingResponse: - """Set, get, and remove conversation backgrounds""" + """View conversations and messages""" return AsyncBackgroundResourceWithStreamingResponse(self._chats.background) diff --git a/src/blooio/resources/chats/messages.py b/src/blooio/resources/chats/messages.py index 8f844d3..c60585e 100644 --- a/src/blooio/resources/chats/messages.py +++ b/src/blooio/resources/chats/messages.py @@ -108,9 +108,13 @@ def list( Args: direction: Filter by message direction - limit: Maximum number of items to return (1-200) + limit: Maximum number of items to return in a single response. Must be between 1 and + 200; defaults to 50. Use together with `offset` to page through large result + sets. - offset: Number of items to skip + offset: Number of items to skip before returning results. Combine with `limit` for + page-based pagination (e.g. `offset=50&limit=50` returns the second page). + Defaults to 0. since: Only messages sent after this timestamp (ms) @@ -209,8 +213,6 @@ def react( (-1 for last message, -2 for second-to-last, etc.). When using relative indices, you can optionally filter by message direction (inbound/outbound only). - Emoji reactions require macOS 14 (Sonoma) or later on the device. - Args: reaction: The reaction to add or remove. Must be prefixed with `+` to add or `-` to remove. @@ -220,7 +222,7 @@ def react( `-question` **Emoji reactions:** Any emoji prefixed with `+` or `-` (e.g. `+😂`, `-😂`, - `+👍`, `-🔥`). Emoji reactions require macOS 14 (Sonoma) or later on the device. + `+👍`, `-🔥`). direction: Filter by message direction (only used when messageId is a relative index like -1, -2) @@ -278,6 +280,7 @@ def send( from_number: str | Omit = omit, link_preview: Optional[LinkPreviewParam] | Omit = omit, parts: Iterable[message_send_params.Part] | Omit = omit, + reply_to: Optional[message_send_params.ReplyTo] | Omit = omit, share_contact: bool | Omit = omit, text: Union[str, SequenceNotStr[str]] | Omit = omit, use_typing_indicator: bool | Omit = omit, @@ -304,6 +307,20 @@ def send( message is delivered without the animation. Effects are not supported in multipart (`parts`) mode. + **Threaded replies (iMessage inline reply):** set the optional `reply_to` field + to send the outgoing message as a reply to a specific earlier message. Two + shapes are accepted: `{ "message_id": "msg_…" }` references a Blooio-minted + message in the same chat (most common — the message*id returned by an earlier + send or surfaced on a `message.received` webhook), or + `{ "guid": "…", "part_index": 0 }` references the raw iMessage GUID for the rare + case where the parent wasn't recorded by Blooio. The reply must target the same + chat and the same from-number as the new send, and the parent must be no older + than 30 days (the iMessage on-device retention horizon). Reply support is + iMessage-only and is rejected on Twilio, dashboard-Twilio, and hybrid send + paths; it's also rejected on multi-message fan-outs (`text` array or per-part + URL-balloon batch). See the `400` responses for the full set of + `reply_target*\\**` error codes. + Args: attachments: Array of attachment URLs or objects with url/name @@ -363,8 +380,17 @@ def send( `text` being a single http(s) URL. Response contains `message_ids[]` + `count` instead of `message_id`. + reply_to: Inline-reply target on `POST /chats/{chatId}/messages`. Pass either `message_id` + (preferred — references a Blooio-minted message) or `guid` (raw iMessage GUID, + useful for replying to messages received before the row was minted in Blooio). + The new send is dispatched to Lava with the resolved `selectedMessageGuid` + + `partIndex`, which iMessage renders as an inline reply on the recipient's + device. + share_contact: If true, the contact card (Name & Photo) will be shared with this message. The - contact card is piggybacked onto the outgoing message. Defaults to false. + contact card is piggybacked onto the outgoing message. Defaults to false. ⚠️ + Only available on **Dedicated Commercial** and **Dedicated Enterprise** plans — + other plans receive a `403`. text: Message text. Can be a single string or array of strings (each becomes a separate message) @@ -391,6 +417,7 @@ def send( "from_number": from_number, "link_preview": link_preview, "parts": parts, + "reply_to": reply_to, "share_contact": share_contact, "text": text, "use_typing_indicator": use_typing_indicator, @@ -483,9 +510,13 @@ async def list( Args: direction: Filter by message direction - limit: Maximum number of items to return (1-200) + limit: Maximum number of items to return in a single response. Must be between 1 and + 200; defaults to 50. Use together with `offset` to page through large result + sets. - offset: Number of items to skip + offset: Number of items to skip before returning results. Combine with `limit` for + page-based pagination (e.g. `offset=50&limit=50` returns the second page). + Defaults to 0. since: Only messages sent after this timestamp (ms) @@ -584,8 +615,6 @@ async def react( (-1 for last message, -2 for second-to-last, etc.). When using relative indices, you can optionally filter by message direction (inbound/outbound only). - Emoji reactions require macOS 14 (Sonoma) or later on the device. - Args: reaction: The reaction to add or remove. Must be prefixed with `+` to add or `-` to remove. @@ -595,7 +624,7 @@ async def react( `-question` **Emoji reactions:** Any emoji prefixed with `+` or `-` (e.g. `+😂`, `-😂`, - `+👍`, `-🔥`). Emoji reactions require macOS 14 (Sonoma) or later on the device. + `+👍`, `-🔥`). direction: Filter by message direction (only used when messageId is a relative index like -1, -2) @@ -653,6 +682,7 @@ async def send( from_number: str | Omit = omit, link_preview: Optional[LinkPreviewParam] | Omit = omit, parts: Iterable[message_send_params.Part] | Omit = omit, + reply_to: Optional[message_send_params.ReplyTo] | Omit = omit, share_contact: bool | Omit = omit, text: Union[str, SequenceNotStr[str]] | Omit = omit, use_typing_indicator: bool | Omit = omit, @@ -679,6 +709,20 @@ async def send( message is delivered without the animation. Effects are not supported in multipart (`parts`) mode. + **Threaded replies (iMessage inline reply):** set the optional `reply_to` field + to send the outgoing message as a reply to a specific earlier message. Two + shapes are accepted: `{ "message_id": "msg_…" }` references a Blooio-minted + message in the same chat (most common — the message*id returned by an earlier + send or surfaced on a `message.received` webhook), or + `{ "guid": "…", "part_index": 0 }` references the raw iMessage GUID for the rare + case where the parent wasn't recorded by Blooio. The reply must target the same + chat and the same from-number as the new send, and the parent must be no older + than 30 days (the iMessage on-device retention horizon). Reply support is + iMessage-only and is rejected on Twilio, dashboard-Twilio, and hybrid send + paths; it's also rejected on multi-message fan-outs (`text` array or per-part + URL-balloon batch). See the `400` responses for the full set of + `reply_target*\\**` error codes. + Args: attachments: Array of attachment URLs or objects with url/name @@ -738,8 +782,17 @@ async def send( `text` being a single http(s) URL. Response contains `message_ids[]` + `count` instead of `message_id`. + reply_to: Inline-reply target on `POST /chats/{chatId}/messages`. Pass either `message_id` + (preferred — references a Blooio-minted message) or `guid` (raw iMessage GUID, + useful for replying to messages received before the row was minted in Blooio). + The new send is dispatched to Lava with the resolved `selectedMessageGuid` + + `partIndex`, which iMessage renders as an inline reply on the recipient's + device. + share_contact: If true, the contact card (Name & Photo) will be shared with this message. The - contact card is piggybacked onto the outgoing message. Defaults to false. + contact card is piggybacked onto the outgoing message. Defaults to false. ⚠️ + Only available on **Dedicated Commercial** and **Dedicated Enterprise** plans — + other plans receive a `403`. text: Message text. Can be a single string or array of strings (each becomes a separate message) @@ -766,6 +819,7 @@ async def send( "from_number": from_number, "link_preview": link_preview, "parts": parts, + "reply_to": reply_to, "share_contact": share_contact, "text": text, "use_typing_indicator": use_typing_indicator, diff --git a/src/blooio/resources/chats/typing.py b/src/blooio/resources/chats/typing.py index f9576e2..17a4b37 100644 --- a/src/blooio/resources/chats/typing.py +++ b/src/blooio/resources/chats/typing.py @@ -56,7 +56,9 @@ def start( """Start the typing indicator for a chat. The indicator shows the recipient that - you are typing. + you are typing. Works for both 1:1 chats (pass a phone number or email as + `chatId`) and group chats (pass the group ID, e.g. `grp_...`); in a group every + participant sees the indicator. **RCS limitation:** typing indicators are only delivered for iMessage chats — the RCS protocol does not carry composing state. Calls against RCS-routed chats @@ -92,8 +94,11 @@ def stop( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TypingResponse: - """ - Stop the typing indicator for a chat. + """Stop the typing indicator for a chat. + + Works for both 1:1 chats (pass a phone + number or email as `chatId`) and group chats (pass the group ID, e.g. + `grp_...`). **RCS limitation:** typing indicators are only delivered for iMessage chats — the RCS protocol does not carry composing state. Calls against RCS-routed chats @@ -155,7 +160,9 @@ async def start( """Start the typing indicator for a chat. The indicator shows the recipient that - you are typing. + you are typing. Works for both 1:1 chats (pass a phone number or email as + `chatId`) and group chats (pass the group ID, e.g. `grp_...`); in a group every + participant sees the indicator. **RCS limitation:** typing indicators are only delivered for iMessage chats — the RCS protocol does not carry composing state. Calls against RCS-routed chats @@ -191,8 +198,11 @@ async def stop( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> TypingResponse: - """ - Stop the typing indicator for a chat. + """Stop the typing indicator for a chat. + + Works for both 1:1 chats (pass a phone + number or email as `chatId`) and group chats (pass the group ID, e.g. + `grp_...`). **RCS limitation:** typing indicators are only delivered for iMessage chats — the RCS protocol does not carry composing state. Calls against RCS-routed chats diff --git a/src/blooio/resources/contacts/contacts.py b/src/blooio/resources/contacts/contacts.py index 3eed99f..12b2c26 100644 --- a/src/blooio/resources/contacts/contacts.py +++ b/src/blooio/resources/contacts/contacts.py @@ -193,9 +193,13 @@ def list( List all contacts for the organization with optional search and pagination. Args: - limit: Maximum number of items to return (1-200) + limit: Maximum number of items to return in a single response. Must be between 1 and + 200; defaults to 50. Use together with `offset` to page through large result + sets. - offset: Number of items to skip + offset: Number of items to skip before returning results. Combine with `limit` for + page-based pagination (e.g. `offset=50&limit=50` returns the second page). + Defaults to 0. q: Search query (matches identifier or name) @@ -454,9 +458,13 @@ async def list( List all contacts for the organization with optional search and pagination. Args: - limit: Maximum number of items to return (1-200) + limit: Maximum number of items to return in a single response. Must be between 1 and + 200; defaults to 50. Use together with `offset` to page through large result + sets. - offset: Number of items to skip + offset: Number of items to skip before returning results. Combine with `limit` for + page-based pagination (e.g. `offset=50&limit=50` returns the second page). + Defaults to 0. q: Search query (matches identifier or name) diff --git a/src/blooio/resources/groups/groups.py b/src/blooio/resources/groups/groups.py index 5eb4504..bb993e8 100644 --- a/src/blooio/resources/groups/groups.py +++ b/src/blooio/resources/groups/groups.py @@ -228,9 +228,13 @@ def list( List all groups for the organization with optional search and pagination. Args: - limit: Maximum number of items to return (1-200) + limit: Maximum number of items to return in a single response. Must be between 1 and + 200; defaults to 50. Use together with `offset` to page through large result + sets. - offset: Number of items to skip + offset: Number of items to skip before returning results. Combine with `limit` for + page-based pagination (e.g. `offset=50&limit=50` returns the second page). + Defaults to 0. q: Search query (matches group name) @@ -485,9 +489,13 @@ async def list( List all groups for the organization with optional search and pagination. Args: - limit: Maximum number of items to return (1-200) + limit: Maximum number of items to return in a single response. Must be between 1 and + 200; defaults to 50. Use together with `offset` to page through large result + sets. - offset: Number of items to skip + offset: Number of items to skip before returning results. Combine with `limit` for + page-based pagination (e.g. `offset=50&limit=50` returns the second page). + Defaults to 0. q: Search query (matches group name) diff --git a/src/blooio/resources/groups/members.py b/src/blooio/resources/groups/members.py index c14ef2b..e02cd26 100644 --- a/src/blooio/resources/groups/members.py +++ b/src/blooio/resources/groups/members.py @@ -62,9 +62,13 @@ def list( List all members of a group. Args: - limit: Maximum number of items to return (1-200) + limit: Maximum number of items to return in a single response. Must be between 1 and + 200; defaults to 50. Use together with `offset` to page through large result + sets. - offset: Number of items to skip + offset: Number of items to skip before returning results. Combine with `limit` for + page-based pagination (e.g. `offset=50&limit=50` returns the second page). + Defaults to 0. extra_headers: Send extra headers @@ -216,9 +220,13 @@ async def list( List all members of a group. Args: - limit: Maximum number of items to return (1-200) + limit: Maximum number of items to return in a single response. Must be between 1 and + 200; defaults to 50. Use together with `offset` to page through large result + sets. - offset: Number of items to skip + offset: Number of items to skip before returning results. Combine with `limit` for + page-based pagination (e.g. `offset=50&limit=50` returns the second page). + Defaults to 0. extra_headers: Send extra headers diff --git a/src/blooio/resources/me/numbers/contact_card.py b/src/blooio/resources/me/numbers/contact_card.py index fa47ab0..7231a43 100644 --- a/src/blooio/resources/me/numbers/contact_card.py +++ b/src/blooio/resources/me/numbers/contact_card.py @@ -100,6 +100,10 @@ def update( Update the personal contact card (Name & Photo) for the specified phone number. All fields are optional — only provided fields are updated. + ⚠️ **Plan requirement:** Setting the `first_name`, `last_name`, or `avatar` is + only available on **Dedicated Commercial** and **Dedicated Enterprise** plans. + Numbers on other plans receive a `403`. + Args: avatar: Profile photo as base64-encoded JPEG/PNG @@ -213,6 +217,10 @@ async def update( Update the personal contact card (Name & Photo) for the specified phone number. All fields are optional — only provided fields are updated. + ⚠️ **Plan requirement:** Setting the `first_name`, `last_name`, or `avatar` is + only available on **Dedicated Commercial** and **Dedicated Enterprise** plans. + Numbers on other plans receive a `403`. + Args: avatar: Profile photo as base64-encoded JPEG/PNG diff --git a/src/blooio/resources/webhooks/logs.py b/src/blooio/resources/webhooks/logs.py index c45621e..69e8f89 100644 --- a/src/blooio/resources/webhooks/logs.py +++ b/src/blooio/resources/webhooks/logs.py @@ -67,13 +67,17 @@ def list( List delivery logs for a specific webhook. Args: - limit: Maximum number of items to return (1-200) + limit: Maximum number of items to return in a single response. Must be between 1 and + 200; defaults to 50. Use together with `offset` to page through large result + sets. max_status: Maximum HTTP status code min_status: Minimum HTTP status code - offset: Number of items to skip + offset: Number of items to skip before returning results. Combine with `limit` for + page-based pagination (e.g. `offset=50&limit=50` returns the second page). + Defaults to 0. sort: Sort order by attempted time @@ -191,13 +195,17 @@ async def list( List delivery logs for a specific webhook. Args: - limit: Maximum number of items to return (1-200) + limit: Maximum number of items to return in a single response. Must be between 1 and + 200; defaults to 50. Use together with `offset` to page through large result + sets. max_status: Maximum HTTP status code min_status: Minimum HTTP status code - offset: Number of items to skip + offset: Number of items to skip before returning results. Combine with `limit` for + page-based pagination (e.g. `offset=50&limit=50` returns the second page). + Defaults to 0. sort: Sort order by attempted time diff --git a/src/blooio/types/chat_list_params.py b/src/blooio/types/chat_list_params.py index 3a6dbab..c51e343 100644 --- a/src/blooio/types/chat_list_params.py +++ b/src/blooio/types/chat_list_params.py @@ -9,10 +9,18 @@ class ChatListParams(TypedDict, total=False): limit: int - """Maximum number of items to return (1-200)""" + """Maximum number of items to return in a single response. + + Must be between 1 and 200; defaults to 50. Use together with `offset` to page + through large result sets. + """ offset: int - """Number of items to skip""" + """Number of items to skip before returning results. + + Combine with `limit` for page-based pagination (e.g. `offset=50&limit=50` + returns the second page). Defaults to 0. + """ q: str """Search query (matches phone/email or contact name)""" diff --git a/src/blooio/types/chat_list_response.py b/src/blooio/types/chat_list_response.py index 6ec4a82..ebc1194 100644 --- a/src/blooio/types/chat_list_response.py +++ b/src/blooio/types/chat_list_response.py @@ -24,6 +24,12 @@ class Chat(BaseModel): id: Optional[str] = None """Chat identifier (phone number, email, or group ID)""" + background_id: Optional[str] = None + """Identifier for the active chat background""" + + background_url: Optional[str] = None + """Public URL of the chat background image (if one has been set via the API)""" + contact: Optional[ChatContact] = None """Contact info (only for non-group chats)""" diff --git a/src/blooio/types/chat_retrieve_response.py b/src/blooio/types/chat_retrieve_response.py index 14f98a5..676d1f1 100644 --- a/src/blooio/types/chat_retrieve_response.py +++ b/src/blooio/types/chat_retrieve_response.py @@ -23,6 +23,12 @@ class ChatRetrieveResponse(BaseModel): id: Optional[str] = None """Chat identifier (phone number, email, or group ID)""" + background_id: Optional[str] = None + """Identifier for the active chat background""" + + background_url: Optional[str] = None + """Public URL of the chat background image (if one has been set via the API)""" + contact: Optional[Contact] = None """Contact info (only for non-group chats)""" diff --git a/src/blooio/types/chats/background_set_params.py b/src/blooio/types/chats/background_set_params.py index 315fc10..5820f62 100644 --- a/src/blooio/types/chats/background_set_params.py +++ b/src/blooio/types/chats/background_set_params.py @@ -11,4 +11,9 @@ class BackgroundSetParams(TypedDict, total=False): background: Required[FileTypes] - """The image file to set as the chat background""" + """Binary image file upload (JPEG, PNG, GIF, WebP, HEIC/HEIF, max 10 MB). + + Send as a file field in `multipart/form-data` — e.g. + `-F "background=@/path/to/image.jpg"` with curl, or a `File`/`Blob` appended to + `FormData` in JavaScript. Do NOT send a URL or base64 string. + """ diff --git a/src/blooio/types/chats/chat_background_response.py b/src/blooio/types/chats/chat_background_response.py index 48c1348..26ffacd 100644 --- a/src/blooio/types/chats/chat_background_response.py +++ b/src/blooio/types/chats/chat_background_response.py @@ -13,6 +13,14 @@ class ChatBackgroundResponse(BaseModel): background_id: Optional[str] = None """Unique identifier for the current background, or null if none""" + background_url: Optional[str] = None + """Public URL of the persisted background image stored in R2. + + Returned after a successful PUT and on GET when a background has been set + through the API. May be null if persistence failed or the background was set + outside of the API. + """ + background_version: Optional[int] = None """Version number of the background (for cache invalidation)""" diff --git a/src/blooio/types/chats/message_get_status_response.py b/src/blooio/types/chats/message_get_status_response.py index da637b4..669d08f 100644 --- a/src/blooio/types/chats/message_get_status_response.py +++ b/src/blooio/types/chats/message_get_status_response.py @@ -17,11 +17,29 @@ class MessageGetStatusResponse(BaseModel): message_id: Optional[str] = None - protocol: Optional[Literal["imessage", "sms", "rcs", "non-imessage"]] = None + protocol: Optional[Literal["pending", "unknown", "imessage", "sms", "rcs"]] = None + """Transport used to carry the message; never null. + + `pending` = accepted and dispatched, wire service not resolved yet (settles + within seconds of send); `imessage` = delivered over iMessage (blue bubble); + `rcs` = delivered over RCS; `sms` = fell back to SMS/MMS (green bubble); + `unknown` = accepted by the carrier but the wire service could not be resolved + before the tracking window closed (see `error`). + """ status: Optional[ Literal["pending", "queued", "sent", "delivered", "failed", "cancellation_requested", "cancelled"] ] = None + """Delivery lifecycle state. + + `pending` = persisted and being prepared for dispatch; `queued` = accepted and + waiting to be handed to Apple/the carrier; `sent` = handed off to Apple/the + carrier (protocol resolution happens around here); `delivered` = a delivery + receipt was received; `failed` = could not be delivered (see `error`); + `cancellation_requested` = a cancel was requested for a still-queued message + (best-effort); `cancelled` = cancelled before dispatch. Inbound messages are + surfaced via webhooks with `received`; read receipts arrive as a `read` event. + """ time_delivered: Optional[int] = None diff --git a/src/blooio/types/chats/message_list_params.py b/src/blooio/types/chats/message_list_params.py index 0e35f39..6b2ff85 100644 --- a/src/blooio/types/chats/message_list_params.py +++ b/src/blooio/types/chats/message_list_params.py @@ -12,10 +12,18 @@ class MessageListParams(TypedDict, total=False): """Filter by message direction""" limit: int - """Maximum number of items to return (1-200)""" + """Maximum number of items to return in a single response. + + Must be between 1 and 200; defaults to 50. Use together with `offset` to page + through large result sets. + """ offset: int - """Number of items to skip""" + """Number of items to skip before returning results. + + Combine with `limit` for page-based pagination (e.g. `offset=50&limit=50` + returns the second page). Defaults to 0. + """ since: int """Only messages sent after this timestamp (ms)""" diff --git a/src/blooio/types/chats/message_list_response.py b/src/blooio/types/chats/message_list_response.py index 88001a7..c24c43c 100644 --- a/src/blooio/types/chats/message_list_response.py +++ b/src/blooio/types/chats/message_list_response.py @@ -7,7 +7,31 @@ from ..._models import BaseModel from ..pagination import Pagination -__all__ = ["MessageListResponse", "Message"] +__all__ = ["MessageListResponse", "Message", "MessageReplyTo"] + + +class MessageReplyTo(BaseModel): + """Inline-reply parent reference. + + Identical shape on `message.received` webhooks and on every GET endpoint that returns a single message or a list of messages. + """ + + guid: Optional[str] = None + """The raw iMessage GUID of the parent. + + Always populated on real inline replies; the on-device record-of-truth + identifier that survives even when `message_id` cannot be resolved. + """ + + message_id: Optional[str] = None + """The Blooio `message_id` of the parent message. + + NULL when the parent isn't in our `messages` table (e.g., the original was sent + from outside Blooio's pipeline). + """ + + part_index: int + """Which part of the parent was replied to. 0 for the common single-part case.""" class Message(BaseModel): @@ -25,11 +49,26 @@ class Message(BaseModel): message_id: Optional[str] = None - protocol: Optional[Literal["imessage", "sms", "rcs", "non-imessage"]] = None + protocol: Optional[Literal["pending", "unknown", "imessage", "sms", "rcs"]] = None + """Transport used to carry the message; never null. + + `pending` = accepted and dispatched, wire service not resolved yet (settles + within seconds of send); `imessage` = delivered over iMessage (blue bubble); + `rcs` = delivered over RCS; `sms` = fell back to SMS/MMS (green bubble); + `unknown` = accepted by the carrier but the wire service could not be resolved + before the tracking window closed (see `error`). + """ reactions: Optional[List[Reaction]] = None """Reactions on this message (tapbacks and emoji reactions)""" + reply_to: Optional[MessageReplyTo] = None + """Inline-reply parent reference. + + Identical shape on `message.received` webhooks and on every GET endpoint that + returns a single message or a list of messages. + """ + sender: Optional[str] = None """Sender's phone number or email for inbound group messages. @@ -39,6 +78,16 @@ class Message(BaseModel): status: Optional[ Literal["pending", "queued", "sent", "delivered", "failed", "cancellation_requested", "cancelled"] ] = None + """Delivery lifecycle state. + + `pending` = persisted and being prepared for dispatch; `queued` = accepted and + waiting to be handed to Apple/the carrier; `sent` = handed off to Apple/the + carrier (protocol resolution happens around here); `delivered` = a delivery + receipt was received; `failed` = could not be delivered (see `error`); + `cancellation_requested` = a cancel was requested for a still-queued message + (best-effort); `cancelled` = cancelled before dispatch. Inbound messages are + surfaced via webhooks with `received`; read receipts arrive as a `read` event. + """ text: Optional[str] = None diff --git a/src/blooio/types/chats/message_react_params.py b/src/blooio/types/chats/message_react_params.py index 9c6a6ed..5330e7c 100644 --- a/src/blooio/types/chats/message_react_params.py +++ b/src/blooio/types/chats/message_react_params.py @@ -22,7 +22,7 @@ class MessageReactParams(TypedDict, total=False): `-question` **Emoji reactions:** Any emoji prefixed with `+` or `-` (e.g. `+😂`, `-😂`, - `+👍`, `-🔥`). Emoji reactions require macOS 14 (Sonoma) or later on the device. + `+👍`, `-🔥`). """ direction: Literal["inbound", "outbound"] diff --git a/src/blooio/types/chats/message_retrieve_response.py b/src/blooio/types/chats/message_retrieve_response.py index a2c61ad..384d78b 100644 --- a/src/blooio/types/chats/message_retrieve_response.py +++ b/src/blooio/types/chats/message_retrieve_response.py @@ -6,7 +6,7 @@ from .reaction import Reaction from ..._models import BaseModel -__all__ = ["MessageRetrieveResponse", "Contact"] +__all__ = ["MessageRetrieveResponse", "Contact", "ReplyTo"] class Contact(BaseModel): @@ -18,6 +18,30 @@ class Contact(BaseModel): name: Optional[str] = None +class ReplyTo(BaseModel): + """Inline-reply parent reference. + + Identical shape on `message.received` webhooks and on every GET endpoint that returns a single message or a list of messages. + """ + + guid: Optional[str] = None + """The raw iMessage GUID of the parent. + + Always populated on real inline replies; the on-device record-of-truth + identifier that survives even when `message_id` cannot be resolved. + """ + + message_id: Optional[str] = None + """The Blooio `message_id` of the parent message. + + NULL when the parent isn't in our `messages` table (e.g., the original was sent + from outside Blooio's pipeline). + """ + + part_index: int + """Which part of the parent was replied to. 0 for the common single-part case.""" + + class MessageRetrieveResponse(BaseModel): attachments: Optional[List[object]] = None @@ -34,11 +58,26 @@ class MessageRetrieveResponse(BaseModel): message_id: Optional[str] = None - protocol: Optional[Literal["imessage", "sms", "rcs", "non-imessage"]] = None + protocol: Optional[Literal["pending", "unknown", "imessage", "sms", "rcs"]] = None + """Transport used to carry the message; never null. + + `pending` = accepted and dispatched, wire service not resolved yet (settles + within seconds of send); `imessage` = delivered over iMessage (blue bubble); + `rcs` = delivered over RCS; `sms` = fell back to SMS/MMS (green bubble); + `unknown` = accepted by the carrier but the wire service could not be resolved + before the tracking window closed (see `error`). + """ reactions: Optional[List[Reaction]] = None """Reactions on this message (tapbacks and emoji reactions)""" + reply_to: Optional[ReplyTo] = None + """Inline-reply parent reference. + + Identical shape on `message.received` webhooks and on every GET endpoint that + returns a single message or a list of messages. + """ + sender: Optional[str] = None """Sender's phone number or email for inbound group messages. @@ -48,6 +87,16 @@ class MessageRetrieveResponse(BaseModel): status: Optional[ Literal["pending", "queued", "sent", "delivered", "failed", "cancellation_requested", "cancelled"] ] = None + """Delivery lifecycle state. + + `pending` = persisted and being prepared for dispatch; `queued` = accepted and + waiting to be handed to Apple/the carrier; `sent` = handed off to Apple/the + carrier (protocol resolution happens around here); `delivered` = a delivery + receipt was received; `failed` = could not be delivered (see `error`); + `cancellation_requested` = a cancel was requested for a still-queued message + (best-effort); `cancelled` = cancelled before dispatch. Inbound messages are + surfaced via webhooks with `received`; read receipts arrive as a `read` event. + """ text: Optional[str] = None diff --git a/src/blooio/types/chats/message_send_params.py b/src/blooio/types/chats/message_send_params.py index b2b935b..84018eb 100644 --- a/src/blooio/types/chats/message_send_params.py +++ b/src/blooio/types/chats/message_send_params.py @@ -9,7 +9,7 @@ from ..._utils import PropertyInfo from .link_preview_param import LinkPreviewParam -__all__ = ["MessageSendParams", "Attachment", "AttachmentUnionObjectVariant1", "Part"] +__all__ = ["MessageSendParams", "Attachment", "AttachmentUnionObjectVariant1", "Part", "ReplyTo"] class MessageSendParams(TypedDict, total=False): @@ -97,10 +97,22 @@ class MessageSendParams(TypedDict, total=False): `count` instead of `message_id`. """ + reply_to: Optional[ReplyTo] + """Inline-reply target on `POST /chats/{chatId}/messages`. + + Pass either `message_id` (preferred — references a Blooio-minted message) or + `guid` (raw iMessage GUID, useful for replying to messages received before the + row was minted in Blooio). The new send is dispatched to Lava with the resolved + `selectedMessageGuid` + `partIndex`, which iMessage renders as an inline reply + on the recipient's device. + """ + share_contact: bool """If true, the contact card (Name & Photo) will be shared with this message. - The contact card is piggybacked onto the outgoing message. Defaults to false. + The contact card is piggybacked onto the outgoing message. Defaults to false. ⚠️ + Only available on **Dedicated Commercial** and **Dedicated Enterprise** plans — + other plans receive a `403`. """ text: Union[str, SequenceNotStr[str]] @@ -149,3 +161,32 @@ class Part(TypedDict, total=False): url: str """URL to an attachment for this part. Mutually exclusive with 'text'.""" + + +class ReplyTo(TypedDict, total=False): + """Inline-reply target on `POST /chats/{chatId}/messages`. + + Pass either `message_id` (preferred — references a Blooio-minted message) or `guid` (raw iMessage GUID, useful for replying to messages received before the row was minted in Blooio). The new send is dispatched to Lava with the resolved `selectedMessageGuid` + `partIndex`, which iMessage renders as an inline reply on the recipient's device. + """ + + guid: str + """Raw iMessage GUID of the parent. + + When supplied without a `message_id`, Blooio attempts to look up the parent via + `provider_message_guid`; if the parent isn't in our table the send still + proceeds (Lava will thread on the device when possible) and the response carries + `parent_unresolved: true`. + """ + + message_id: str + """Blooio `message_id` of the parent. + + Must belong to the same chat, same from-number, and be no older than 30 days. + Returns 404 `reply_target_not_found` if unknown. + """ + + part_index: int + """Which part of the parent to reply to. + + Defaults to 0 (covers the 99% case of replying to a single-part text message). + """ diff --git a/src/blooio/types/chats/message_send_response.py b/src/blooio/types/chats/message_send_response.py index 385d693..13efb90 100644 --- a/src/blooio/types/chats/message_send_response.py +++ b/src/blooio/types/chats/message_send_response.py @@ -30,8 +30,21 @@ class MessageSendResponse(BaseModel): (URL-balloon batch mode). """ + parent_unresolved: Optional[bool] = None + """ + Present (and `true`) only when `reply_to.guid` was supplied without a + `message_id` and the GUID didn't map to any Blooio-minted row. The send still + proceeds and the device may still thread it; this flag signals that Blooio + couldn't link the new message to a known parent. + """ + participants: Optional[List[str]] = None """List of participants (present for multi-recipient)""" status: Optional[Literal["queued", "failed"]] = None - """Initial status of the message(s)""" + """Initial status of the message(s). + + `queued` = accepted for delivery (the normal 202 result); `failed` = rejected + before dispatch. Subsequent transitions (`sent` → `delivered`, or `failed`) are + reported via the status endpoint and `message.status` webhooks. + """ diff --git a/src/blooio/types/contact_list_params.py b/src/blooio/types/contact_list_params.py index 6c109ff..124dfe3 100644 --- a/src/blooio/types/contact_list_params.py +++ b/src/blooio/types/contact_list_params.py @@ -9,10 +9,18 @@ class ContactListParams(TypedDict, total=False): limit: int - """Maximum number of items to return (1-200)""" + """Maximum number of items to return in a single response. + + Must be between 1 and 200; defaults to 50. Use together with `offset` to page + through large result sets. + """ offset: int - """Number of items to skip""" + """Number of items to skip before returning results. + + Combine with `limit` for page-based pagination (e.g. `offset=50&limit=50` + returns the second page). Defaults to 0. + """ q: str """Search query (matches identifier or name)""" diff --git a/src/blooio/types/group_list_params.py b/src/blooio/types/group_list_params.py index 7628e4e..2bb759d 100644 --- a/src/blooio/types/group_list_params.py +++ b/src/blooio/types/group_list_params.py @@ -9,10 +9,18 @@ class GroupListParams(TypedDict, total=False): limit: int - """Maximum number of items to return (1-200)""" + """Maximum number of items to return in a single response. + + Must be between 1 and 200; defaults to 50. Use together with `offset` to page + through large result sets. + """ offset: int - """Number of items to skip""" + """Number of items to skip before returning results. + + Combine with `limit` for page-based pagination (e.g. `offset=50&limit=50` + returns the second page). Defaults to 0. + """ q: str """Search query (matches group name)""" diff --git a/src/blooio/types/groups/member_list_params.py b/src/blooio/types/groups/member_list_params.py index 75b4ade..ed6fb90 100644 --- a/src/blooio/types/groups/member_list_params.py +++ b/src/blooio/types/groups/member_list_params.py @@ -9,7 +9,15 @@ class MemberListParams(TypedDict, total=False): limit: int - """Maximum number of items to return (1-200)""" + """Maximum number of items to return in a single response. + + Must be between 1 and 200; defaults to 50. Use together with `offset` to page + through large result sets. + """ offset: int - """Number of items to skip""" + """Number of items to skip before returning results. + + Combine with `limit` for page-based pagination (e.g. `offset=50&limit=50` + returns the second page). Defaults to 0. + """ diff --git a/src/blooio/types/webhooks/log_list_params.py b/src/blooio/types/webhooks/log_list_params.py index d8aeaaa..c2eda44 100644 --- a/src/blooio/types/webhooks/log_list_params.py +++ b/src/blooio/types/webhooks/log_list_params.py @@ -9,7 +9,11 @@ class LogListParams(TypedDict, total=False): limit: int - """Maximum number of items to return (1-200)""" + """Maximum number of items to return in a single response. + + Must be between 1 and 200; defaults to 50. Use together with `offset` to page + through large result sets. + """ max_status: int """Maximum HTTP status code""" @@ -18,7 +22,11 @@ class LogListParams(TypedDict, total=False): """Minimum HTTP status code""" offset: int - """Number of items to skip""" + """Number of items to skip before returning results. + + Combine with `limit` for page-based pagination (e.g. `offset=50&limit=50` + returns the second page). Defaults to 0. + """ sort: Literal["asc", "desc"] """Sort order by attempted time""" diff --git a/src/blooio/types/webhooks/log_list_response.py b/src/blooio/types/webhooks/log_list_response.py index 386c9d6..a85d793 100644 --- a/src/blooio/types/webhooks/log_list_response.py +++ b/src/blooio/types/webhooks/log_list_response.py @@ -75,8 +75,15 @@ class LogEventBody(BaseModel): participants: Optional[List[LogEventBodyParticipant]] = None """Array of group participants (only present when is_group=true)""" - protocol: Optional[Literal["imessage", "sms", "rcs", "non-imessage"]] = None - """Message protocol""" + protocol: Optional[Literal["pending", "unknown", "imessage", "sms", "rcs"]] = None + """Transport used to carry the message; never null. + + `pending` = accepted and dispatched, wire service not resolved yet (settles + within seconds of send); `imessage` = delivered over iMessage (blue bubble); + `rcs` = delivered over RCS; `sms` = fell back to SMS/MMS (green bubble); + `unknown` = accepted by the carrier but the wire service could not be resolved + before the tracking window closed (see `error`). + """ read_at: Optional[int] = None """Timestamp when message was read (for message.read events)""" @@ -88,7 +95,14 @@ class LogEventBody(BaseModel): """Timestamp when message was sent (for message.sent events)""" status: Optional[Literal["queued", "pending", "sent", "delivered", "failed", "read", "received"]] = None - """Message status""" + """Message status carried by the event. + + `queued` / `pending` = accepted, not yet handed off; `sent` = handed to + Apple/the carrier; `delivered` = a delivery receipt was received; `read` = a + read receipt was received (iMessage, when the recipient has read receipts on); + `failed` = delivery failed (see `error_code` / `error_message`); `received` = an + inbound message arrived. + """ text: Optional[str] = None """Message text content""" diff --git a/tests/api_resources/chats/test_messages.py b/tests/api_resources/chats/test_messages.py index 1ca5b92..fddd22b 100644 --- a/tests/api_resources/chats/test_messages.py +++ b/tests/api_resources/chats/test_messages.py @@ -283,6 +283,11 @@ def test_method_send_with_all_params(self, client: Blooio) -> None: "url": "url", } ], + reply_to={ + "guid": "guid", + "message_id": "message_id", + "part_index": 0, + }, share_contact=True, text="string", use_typing_indicator=True, @@ -590,6 +595,11 @@ async def test_method_send_with_all_params(self, async_client: AsyncBlooio) -> N "url": "url", } ], + reply_to={ + "guid": "guid", + "message_id": "message_id", + "part_index": 0, + }, share_contact=True, text="string", use_typing_indicator=True,