From bb8074b817bc550cba91e705ebbd710dee71caba Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 22 May 2026 23:20:25 +0000 Subject: [PATCH 01/18] feat(api): api update --- .stats.yml | 4 +- src/blooio/resources/chats/messages.py | 46 +++++++++++++++++++ .../types/chats/message_list_response.py | 33 ++++++++++++- .../types/chats/message_retrieve_response.py | 33 ++++++++++++- src/blooio/types/chats/message_send_params.py | 41 ++++++++++++++++- .../types/chats/message_send_response.py | 8 ++++ tests/api_resources/chats/test_messages.py | 10 ++++ 7 files changed, 170 insertions(+), 5 deletions(-) diff --git a/.stats.yml b/.stats.yml index 000c763..8729928 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-23ac4ce198a5e8bffa1e5b9454a35bd0ca35a31d844f681d60e67cf4ff542411.yml +openapi_spec_hash: e35cf6f966a4c18c696d1c453e40a809 config_hash: 839e191496287972d18d39dcab8b19b7 diff --git a/src/blooio/resources/chats/messages.py b/src/blooio/resources/chats/messages.py index 8f844d3..2770375 100644 --- a/src/blooio/resources/chats/messages.py +++ b/src/blooio/resources/chats/messages.py @@ -278,6 +278,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 +305,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,6 +378,13 @@ 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. @@ -391,6 +413,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, @@ -653,6 +676,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 +703,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,6 +776,13 @@ 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. @@ -766,6 +811,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/types/chats/message_list_response.py b/src/blooio/types/chats/message_list_response.py index 88001a7..b31467d 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): @@ -30,6 +54,13 @@ class Message(BaseModel): 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. diff --git a/src/blooio/types/chats/message_retrieve_response.py b/src/blooio/types/chats/message_retrieve_response.py index a2c61ad..aee571a 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 @@ -39,6 +63,13 @@ class MessageRetrieveResponse(BaseModel): 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. diff --git a/src/blooio/types/chats/message_send_params.py b/src/blooio/types/chats/message_send_params.py index b2b935b..fd31ffd 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,6 +97,16 @@ 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. @@ -149,3 +159,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..04f8395 100644 --- a/src/blooio/types/chats/message_send_response.py +++ b/src/blooio/types/chats/message_send_response.py @@ -30,6 +30,14 @@ 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)""" 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, From e4708cb01510e75961ac6a058308140cf69424ee Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 23 May 2026 10:20:21 +0000 Subject: [PATCH 02/18] feat(api): api update --- .stats.yml | 4 ++-- src/blooio/types/chat_list_response.py | 6 ++++++ src/blooio/types/chat_retrieve_response.py | 6 ++++++ src/blooio/types/chats/chat_background_response.py | 8 ++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 8729928..77ef01e 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-23ac4ce198a5e8bffa1e5b9454a35bd0ca35a31d844f681d60e67cf4ff542411.yml -openapi_spec_hash: e35cf6f966a4c18c696d1c453e40a809 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/blooio/blooio-81b1f2ba01495b54ab7b259e5cc92a6d6f114b26b8d41ffc4fee80d9f9b3db20.yml +openapi_spec_hash: 173a216fb869614c676de670dfa814b5 config_hash: 839e191496287972d18d39dcab8b19b7 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/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)""" From 03aecefb518d40f36c37d71cfa7637cefb0575c4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 23 May 2026 20:20:20 +0000 Subject: [PATCH 03/18] feat(api): api update --- .stats.yml | 4 +- src/blooio/resources/chats/background.py | 50 +++++++++++++++---- src/blooio/resources/chats/chats.py | 12 ++--- src/blooio/resources/chats/messages.py | 46 ----------------- src/blooio/types/chat_list_response.py | 6 --- src/blooio/types/chat_retrieve_response.py | 6 --- .../types/chats/background_set_params.py | 7 ++- .../types/chats/chat_background_response.py | 8 --- .../types/chats/message_list_response.py | 33 +----------- .../types/chats/message_retrieve_response.py | 33 +----------- src/blooio/types/chats/message_send_params.py | 41 +-------------- .../types/chats/message_send_response.py | 8 --- tests/api_resources/chats/test_messages.py | 10 ---- 13 files changed, 57 insertions(+), 207 deletions(-) diff --git a/.stats.yml b/.stats.yml index 77ef01e..36f5ef8 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-81b1f2ba01495b54ab7b259e5cc92a6d6f114b26b8d41ffc4fee80d9f9b3db20.yml -openapi_spec_hash: 173a216fb869614c676de670dfa814b5 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/blooio/blooio-164b95920b6f8c3bcf47d73fb582375e0e40e72a5d01a19ada82c19431b5853e.yml +openapi_spec_hash: 988aee0a657a2411a646e306ed9e2e22 config_hash: 839e191496287972d18d39dcab8b19b7 diff --git a/src/blooio/resources/chats/background.py b/src/blooio/resources/chats/background.py index a0c1121..52fa8ba 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://backend.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://backend.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..fb775fb 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 @@ -279,7 +279,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 @@ -496,7 +496,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 +536,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 +576,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 +616,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 2770375..8f844d3 100644 --- a/src/blooio/resources/chats/messages.py +++ b/src/blooio/resources/chats/messages.py @@ -278,7 +278,6 @@ 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, @@ -305,20 +304,6 @@ 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 @@ -378,13 +363,6 @@ 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. @@ -413,7 +391,6 @@ 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, @@ -676,7 +653,6 @@ 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, @@ -703,20 +679,6 @@ 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 @@ -776,13 +738,6 @@ 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. @@ -811,7 +766,6 @@ 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/types/chat_list_response.py b/src/blooio/types/chat_list_response.py index ebc1194..6ec4a82 100644 --- a/src/blooio/types/chat_list_response.py +++ b/src/blooio/types/chat_list_response.py @@ -24,12 +24,6 @@ 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 676d1f1..14f98a5 100644 --- a/src/blooio/types/chat_retrieve_response.py +++ b/src/blooio/types/chat_retrieve_response.py @@ -23,12 +23,6 @@ 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 26ffacd..48c1348 100644 --- a/src/blooio/types/chats/chat_background_response.py +++ b/src/blooio/types/chats/chat_background_response.py @@ -13,14 +13,6 @@ 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_list_response.py b/src/blooio/types/chats/message_list_response.py index b31467d..88001a7 100644 --- a/src/blooio/types/chats/message_list_response.py +++ b/src/blooio/types/chats/message_list_response.py @@ -7,31 +7,7 @@ from ..._models import BaseModel from ..pagination import Pagination -__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.""" +__all__ = ["MessageListResponse", "Message"] class Message(BaseModel): @@ -54,13 +30,6 @@ class Message(BaseModel): 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. diff --git a/src/blooio/types/chats/message_retrieve_response.py b/src/blooio/types/chats/message_retrieve_response.py index aee571a..a2c61ad 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", "ReplyTo"] +__all__ = ["MessageRetrieveResponse", "Contact"] class Contact(BaseModel): @@ -18,30 +18,6 @@ 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 @@ -63,13 +39,6 @@ class MessageRetrieveResponse(BaseModel): 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. diff --git a/src/blooio/types/chats/message_send_params.py b/src/blooio/types/chats/message_send_params.py index fd31ffd..b2b935b 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", "ReplyTo"] +__all__ = ["MessageSendParams", "Attachment", "AttachmentUnionObjectVariant1", "Part"] class MessageSendParams(TypedDict, total=False): @@ -97,16 +97,6 @@ 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. @@ -159,32 +149,3 @@ 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 04f8395..385d693 100644 --- a/src/blooio/types/chats/message_send_response.py +++ b/src/blooio/types/chats/message_send_response.py @@ -30,14 +30,6 @@ 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)""" diff --git a/tests/api_resources/chats/test_messages.py b/tests/api_resources/chats/test_messages.py index fddd22b..1ca5b92 100644 --- a/tests/api_resources/chats/test_messages.py +++ b/tests/api_resources/chats/test_messages.py @@ -283,11 +283,6 @@ 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, @@ -595,11 +590,6 @@ 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, From 178129ec16b48bb7be95b672071147fb1a4bf679 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 28 May 2026 21:20:25 +0000 Subject: [PATCH 04/18] feat(api): api update --- .stats.yml | 4 ++-- src/blooio/resources/chats/messages.py | 8 ++------ src/blooio/types/chats/message_react_params.py | 2 +- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/.stats.yml b/.stats.yml index 36f5ef8..a5d66de 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-164b95920b6f8c3bcf47d73fb582375e0e40e72a5d01a19ada82c19431b5853e.yml -openapi_spec_hash: 988aee0a657a2411a646e306ed9e2e22 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/blooio/blooio-5bfdbbd81896415e7d35d5c75de7ac794321ec5842c37fab5369f9d716837581.yml +openapi_spec_hash: ed104905ecf88a7159efed08b9680fce config_hash: 839e191496287972d18d39dcab8b19b7 diff --git a/src/blooio/resources/chats/messages.py b/src/blooio/resources/chats/messages.py index 8f844d3..e02f570 100644 --- a/src/blooio/resources/chats/messages.py +++ b/src/blooio/resources/chats/messages.py @@ -209,8 +209,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 +218,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) @@ -584,8 +582,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 +591,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) 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"] From 2e075ad61159d021fc31bd6fda9f0152ea703a34 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 29 May 2026 21:20:28 +0000 Subject: [PATCH 05/18] feat(api): api update --- .stats.yml | 4 ++-- src/blooio/resources/chats/chats.py | 8 ++++++++ src/blooio/resources/chats/messages.py | 8 ++++++-- src/blooio/resources/me/numbers/contact_card.py | 8 ++++++++ src/blooio/types/chats/message_send_params.py | 4 +++- 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/.stats.yml b/.stats.yml index a5d66de..d38d411 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-5bfdbbd81896415e7d35d5c75de7ac794321ec5842c37fab5369f9d716837581.yml -openapi_spec_hash: ed104905ecf88a7159efed08b9680fce +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/blooio/blooio-46f59ef78fe3698c34ceea0a3ec36b2a2686471917c7c7d419eb12d128eac297.yml +openapi_spec_hash: aa44af93cee78f8800ac64bae9a578c6 config_hash: 839e191496287972d18d39dcab8b19b7 diff --git a/src/blooio/resources/chats/chats.py b/src/blooio/resources/chats/chats.py index fb775fb..79cb6f8 100644 --- a/src/blooio/resources/chats/chats.py +++ b/src/blooio/resources/chats/chats.py @@ -239,6 +239,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 @@ -440,6 +444,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 diff --git a/src/blooio/resources/chats/messages.py b/src/blooio/resources/chats/messages.py index e02f570..2a16148 100644 --- a/src/blooio/resources/chats/messages.py +++ b/src/blooio/resources/chats/messages.py @@ -362,7 +362,9 @@ def send( `count` instead of `message_id`. 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) @@ -735,7 +737,9 @@ async def send( `count` instead of `message_id`. 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) 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/types/chats/message_send_params.py b/src/blooio/types/chats/message_send_params.py index b2b935b..8de8730 100644 --- a/src/blooio/types/chats/message_send_params.py +++ b/src/blooio/types/chats/message_send_params.py @@ -100,7 +100,9 @@ class MessageSendParams(TypedDict, total=False): 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]] From d9b82ea21659239202fb8a4d63366d54ea60fcbe Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 29 May 2026 22:20:26 +0000 Subject: [PATCH 06/18] feat(api): api update --- .stats.yml | 4 ++-- src/blooio/resources/chats/typing.py | 22 ++++++++++++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/.stats.yml b/.stats.yml index d38d411..52d7b70 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-46f59ef78fe3698c34ceea0a3ec36b2a2686471917c7c7d419eb12d128eac297.yml -openapi_spec_hash: aa44af93cee78f8800ac64bae9a578c6 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/blooio/blooio-89b373f13520ce524ae595019f2d18b83252e41615106e78b56495cb1bac6a17.yml +openapi_spec_hash: 010188e6078b32cb3e43a8f66d6090f7 config_hash: 839e191496287972d18d39dcab8b19b7 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 From ab48a0e1b84d2e4576714fc10688667f5841f36d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 31 May 2026 06:20:24 +0000 Subject: [PATCH 07/18] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 52d7b70..8a65bf9 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-89b373f13520ce524ae595019f2d18b83252e41615106e78b56495cb1bac6a17.yml -openapi_spec_hash: 010188e6078b32cb3e43a8f66d6090f7 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/blooio/blooio-ad8942369c6c1d1ff02b4d71ab75d12c73c8895157ffc7ebde0932964b0bb3d0.yml +openapi_spec_hash: 841aacf1e549afd1218f172eca40457e config_hash: 839e191496287972d18d39dcab8b19b7 From b3ba7951d022e732ae0e3a458520ed3f42a49a5f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 19:20:28 +0000 Subject: [PATCH 08/18] feat(api): api update --- .stats.yml | 4 +- src/blooio/resources/chats/messages.py | 46 +++++++++++++++++++ src/blooio/types/chat_list_response.py | 6 +++ src/blooio/types/chat_retrieve_response.py | 6 +++ .../types/chats/chat_background_response.py | 8 ++++ .../types/chats/message_list_response.py | 33 ++++++++++++- .../types/chats/message_retrieve_response.py | 33 ++++++++++++- src/blooio/types/chats/message_send_params.py | 41 ++++++++++++++++- .../types/chats/message_send_response.py | 8 ++++ tests/api_resources/chats/test_messages.py | 10 ++++ 10 files changed, 190 insertions(+), 5 deletions(-) diff --git a/.stats.yml b/.stats.yml index 8a65bf9..2d2f694 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-ad8942369c6c1d1ff02b4d71ab75d12c73c8895157ffc7ebde0932964b0bb3d0.yml -openapi_spec_hash: 841aacf1e549afd1218f172eca40457e +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/blooio/blooio-11ec3fc93ebdd4d2d0699c400c1369866e3a892e8fb5270eaa409347a333b8c0.yml +openapi_spec_hash: 7461099a5a1699f3082bb99147a2399d config_hash: 839e191496287972d18d39dcab8b19b7 diff --git a/src/blooio/resources/chats/messages.py b/src/blooio/resources/chats/messages.py index 2a16148..96dcf93 100644 --- a/src/blooio/resources/chats/messages.py +++ b/src/blooio/resources/chats/messages.py @@ -276,6 +276,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, @@ -302,6 +303,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 @@ -361,6 +376,13 @@ 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. ⚠️ Only available on **Dedicated Commercial** and **Dedicated Enterprise** plans — @@ -391,6 +413,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, @@ -651,6 +674,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, @@ -677,6 +701,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 @@ -736,6 +774,13 @@ 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. ⚠️ Only available on **Dedicated Commercial** and **Dedicated Enterprise** plans — @@ -766,6 +811,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/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/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_list_response.py b/src/blooio/types/chats/message_list_response.py index 88001a7..b31467d 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): @@ -30,6 +54,13 @@ class Message(BaseModel): 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. diff --git a/src/blooio/types/chats/message_retrieve_response.py b/src/blooio/types/chats/message_retrieve_response.py index a2c61ad..aee571a 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 @@ -39,6 +63,13 @@ class MessageRetrieveResponse(BaseModel): 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. diff --git a/src/blooio/types/chats/message_send_params.py b/src/blooio/types/chats/message_send_params.py index 8de8730..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,6 +97,16 @@ 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. @@ -151,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..04f8395 100644 --- a/src/blooio/types/chats/message_send_response.py +++ b/src/blooio/types/chats/message_send_response.py @@ -30,6 +30,14 @@ 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)""" 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, From d692d48a74bebaa8b86c6e645941c85ebab88c2e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 18 Jun 2026 02:17:59 +0000 Subject: [PATCH 09/18] fix(auth): prioritize first auth header --- src/blooio/_client.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) 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]: From f6caf8e2ab1630818c57ab8142a5107cc534c57c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 19 Jun 2026 08:20:28 +0000 Subject: [PATCH 10/18] feat(api): api update --- .stats.yml | 4 ++-- src/blooio/resources/chats/background.py | 4 ++-- src/blooio/resources/chats/chats.py | 16 ++++++++++++---- src/blooio/resources/chats/messages.py | 16 ++++++++++++---- src/blooio/resources/contacts/contacts.py | 16 ++++++++++++---- src/blooio/resources/groups/groups.py | 16 ++++++++++++---- src/blooio/resources/groups/members.py | 16 ++++++++++++---- src/blooio/resources/webhooks/logs.py | 16 ++++++++++++---- src/blooio/types/chat_list_params.py | 12 ++++++++++-- src/blooio/types/chats/message_list_params.py | 12 ++++++++++-- src/blooio/types/contact_list_params.py | 12 ++++++++++-- src/blooio/types/group_list_params.py | 12 ++++++++++-- src/blooio/types/groups/member_list_params.py | 12 ++++++++++-- src/blooio/types/webhooks/log_list_params.py | 12 ++++++++++-- 14 files changed, 136 insertions(+), 40 deletions(-) diff --git a/.stats.yml b/.stats.yml index 2d2f694..29f6b09 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-11ec3fc93ebdd4d2d0699c400c1369866e3a892e8fb5270eaa409347a333b8c0.yml -openapi_spec_hash: 7461099a5a1699f3082bb99147a2399d +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/blooio/blooio-fbf531ca47a2361a9adec0b9845e3569e800f7add519b96776c047cc227e90cc.yml +openapi_spec_hash: 636c8fd0d0264e47b02eecb86fc9b22d config_hash: 839e191496287972d18d39dcab8b19b7 diff --git a/src/blooio/resources/chats/background.py b/src/blooio/resources/chats/background.py index 52fa8ba..e76ec76 100644 --- a/src/blooio/resources/chats/background.py +++ b/src/blooio/resources/chats/background.py @@ -140,7 +140,7 @@ def set( from disk: ```bash - curl -X PUT "https://backend.blooio.com/v2/api/chats/%2B15551234567/background" \\ + 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" ``` @@ -297,7 +297,7 @@ async def set( from disk: ```bash - curl -X PUT "https://backend.blooio.com/v2/api/chats/%2B15551234567/background" \\ + 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" ``` diff --git a/src/blooio/resources/chats/chats.py b/src/blooio/resources/chats/chats.py index 79cb6f8..5c77eec 100644 --- a/src/blooio/resources/chats/chats.py +++ b/src/blooio/resources/chats/chats.py @@ -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) @@ -357,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) diff --git a/src/blooio/resources/chats/messages.py b/src/blooio/resources/chats/messages.py index 96dcf93..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) @@ -506,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) 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/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/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/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""" From 12fb122bc963ca30ba0d2767352d2f3fde733bfb Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 14 Jul 2026 02:08:43 +0000 Subject: [PATCH 11/18] fix(internal): resolve build failures --- scripts/lint | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 . From 30e8dd13b8e2ca5988573a2cc482c122ea1650ae Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 15 Jul 2026 01:20:29 +0000 Subject: [PATCH 12/18] feat(api): api update --- .stats.yml | 4 ++-- .../chats/message_get_status_response.py | 20 ++++++++++++++++++- .../types/chats/message_list_response.py | 20 ++++++++++++++++++- .../types/chats/message_retrieve_response.py | 20 ++++++++++++++++++- .../types/chats/message_send_response.py | 7 ++++++- .../types/webhooks/log_list_response.py | 20 ++++++++++++++++--- 6 files changed, 82 insertions(+), 9 deletions(-) diff --git a/.stats.yml b/.stats.yml index 29f6b09..4573161 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-fbf531ca47a2361a9adec0b9845e3569e800f7add519b96776c047cc227e90cc.yml -openapi_spec_hash: 636c8fd0d0264e47b02eecb86fc9b22d +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/blooio/blooio-97fe57f345af4c53c5065cac2521795c0594eacd6bd094effeef5ecf72fb4dde.yml +openapi_spec_hash: e087a558db51de03fbe6e0b2faa1d8b3 config_hash: 839e191496287972d18d39dcab8b19b7 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_response.py b/src/blooio/types/chats/message_list_response.py index b31467d..c24c43c 100644 --- a/src/blooio/types/chats/message_list_response.py +++ b/src/blooio/types/chats/message_list_response.py @@ -49,7 +49,15 @@ 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)""" @@ -70,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_retrieve_response.py b/src/blooio/types/chats/message_retrieve_response.py index aee571a..384d78b 100644 --- a/src/blooio/types/chats/message_retrieve_response.py +++ b/src/blooio/types/chats/message_retrieve_response.py @@ -58,7 +58,15 @@ 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)""" @@ -79,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_response.py b/src/blooio/types/chats/message_send_response.py index 04f8395..13efb90 100644 --- a/src/blooio/types/chats/message_send_response.py +++ b/src/blooio/types/chats/message_send_response.py @@ -42,4 +42,9 @@ class MessageSendResponse(BaseModel): """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/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""" From 33ac236b6e204193681f2db4822517a0de75b182 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 18 Jul 2026 02:09:39 +0000 Subject: [PATCH 13/18] feat(stlc): configurable CI runner and private-production-repo support in workflow templates --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 From b52b04046662a8d6d49ec97f8be23b00810a9d1f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 18 Jul 2026 18:20:29 +0000 Subject: [PATCH 14/18] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 4573161..9fefe1e 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-97fe57f345af4c53c5065cac2521795c0594eacd6bd094effeef5ecf72fb4dde.yml -openapi_spec_hash: e087a558db51de03fbe6e0b2faa1d8b3 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/blooio/blooio-48672bb20ddabae9ba6fe497480d26fd4c03079b27e0cb93c3f0decd773bbebc.yml +openapi_spec_hash: 3064147b2273936d7a3922f6778c64f7 config_hash: 839e191496287972d18d39dcab8b19b7 From 2d3528b6d08de32f67146eafdf965a21557aceeb Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 08:20:30 +0000 Subject: [PATCH 15/18] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 9fefe1e..4236de9 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-48672bb20ddabae9ba6fe497480d26fd4c03079b27e0cb93c3f0decd773bbebc.yml -openapi_spec_hash: 3064147b2273936d7a3922f6778c64f7 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/blooio/blooio-1cd5a817a6e1e1b6fcd4a9775ce688843f231161a23ad60d545e97c60df304b6.yml +openapi_spec_hash: 115398aaf337063929c7d212deaaa730 config_hash: 839e191496287972d18d39dcab8b19b7 From 2530e21813960ee0406f9cc30bead171daad693c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 20:20:25 +0000 Subject: [PATCH 16/18] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 4236de9..3b51066 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-1cd5a817a6e1e1b6fcd4a9775ce688843f231161a23ad60d545e97c60df304b6.yml -openapi_spec_hash: 115398aaf337063929c7d212deaaa730 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/blooio/blooio-c60428133ba2fc28b472f6f628637fd0acba87e0213bbb85a0e8bf8c1ef3ba79.yml +openapi_spec_hash: 51ceab56aca9165d221f1294b27c140e config_hash: 839e191496287972d18d39dcab8b19b7 From 19df5720c1b7945f41e813be1d3a5bebe51e5cbd Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 25 Jul 2026 21:20:29 +0000 Subject: [PATCH 17/18] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 3b51066..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-c60428133ba2fc28b472f6f628637fd0acba87e0213bbb85a0e8bf8c1ef3ba79.yml -openapi_spec_hash: 51ceab56aca9165d221f1294b27c140e +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/blooio/blooio-4cfd8aadba2015ef9746f076452ef853f194ce40602854c29ce76842eff021a9.yml +openapi_spec_hash: 8137fd23f3a30da82f83a8ac74a433f8 config_hash: 839e191496287972d18d39dcab8b19b7 From 68a70b1229371a1b7ba5774e2cb6eadb74b33bb2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 25 Jul 2026 21:20:51 +0000 Subject: [PATCH 18/18] release: 1.3.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 23 +++++++++++++++++++++++ pyproject.toml | 2 +- src/blooio/_version.py | 2 +- 4 files changed, 26 insertions(+), 3 deletions(-) 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/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/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