diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index afe4ebf..821a1fc 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "7.22.0"
+ ".": "7.23.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index cf69b29..29ea7c7 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 136
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/courier/courier-47bd7f89519b4aa68b039b9a03888b689fac9d6ee763d26be0b267bbb0b2ddaa.yml
-openapi_spec_hash: 67ff63b23cbfb19beac58d6e471836d3
-config_hash: b30c4b3086cd9f3da06a63b256d8c189
+configured_endpoints: 147
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/courier/courier-ca32540df5bca42c8113de4004e7751b74bb440e3d73267e7cbad84dd3c3e82a.yml
+openapi_spec_hash: 5576c8cf8e5be1f250b5dd1eb6d30571
+config_hash: c6cefb51165221f01d442a42ac4a13b9
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 468bcb9..112e207 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
# Changelog
+## 7.23.0 (2026-07-29)
+
+Full Changelog: [v7.22.0...v7.23.0](https://github.com/trycourier/courier-python/compare/v7.22.0...v7.23.0)
+
+### Features
+
+* **broadcasts:** document Broadcasts CRUD REST API ([#170](https://github.com/trycourier/courier-python/issues/170)) ([8e63975](https://github.com/trycourier/courier-python/commit/8e639757661144e35f43d31923a7d6190874253e))
+
## 7.22.0 (2026-07-29)
Full Changelog: [v7.21.0...v7.22.0](https://github.com/trycourier/courier-python/compare/v7.21.0...v7.22.0)
diff --git a/api.md b/api.md
index 1783645..4603c9d 100644
--- a/api.md
+++ b/api.md
@@ -264,6 +264,36 @@ Methods:
- client.journeys.templates.replace(notification_id, \*, template_id, \*\*params) -> JourneyTemplateGetResponse
- client.journeys.templates.retrieve_content(notification_id, \*, template_id, \*\*params) -> NotificationContentGetResponse
+# Broadcasts
+
+Types:
+
+```python
+from courier.types import (
+ Broadcast,
+ BroadcastListResponse,
+ BroadcastSchedule,
+ CreateBroadcastRequest,
+ ScheduleBroadcastRequest,
+ SendBroadcastRequest,
+ UpdateBroadcastRequest,
+)
+```
+
+Methods:
+
+- client.broadcasts.create(\*\*params) -> Broadcast
+- client.broadcasts.retrieve(broadcast_id) -> Broadcast
+- client.broadcasts.update(broadcast_id, \*\*params) -> Broadcast
+- client.broadcasts.list(\*\*params) -> BroadcastListResponse
+- client.broadcasts.archive(broadcast_id) -> Broadcast
+- client.broadcasts.cancel(broadcast_id) -> Broadcast
+- client.broadcasts.duplicate(broadcast_id) -> Broadcast
+- client.broadcasts.put_content(broadcast_id, \*\*params) -> NotificationContentMutationResponse
+- client.broadcasts.retrieve_content(broadcast_id, \*\*params) -> NotificationContentGetResponse
+- client.broadcasts.schedule(broadcast_id, \*\*params) -> Broadcast
+- client.broadcasts.send(broadcast_id, \*\*params) -> Broadcast
+
# Brands
Types:
diff --git a/pyproject.toml b/pyproject.toml
index 5573410..1b672bf 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "trycourier"
-version = "7.22.0"
+version = "7.23.0"
description = "The official Python library for the Courier API"
dynamic = ["readme"]
license = "Apache-2.0"
diff --git a/src/courier/_client.py b/src/courier/_client.py
index 1e024eb..b6797bf 100644
--- a/src/courier/_client.py
+++ b/src/courier/_client.py
@@ -51,6 +51,7 @@
requests,
audiences,
providers,
+ broadcasts,
automations,
audit_events,
translations,
@@ -65,6 +66,7 @@
from .resources.messages import MessagesResource, AsyncMessagesResource
from .resources.requests import RequestsResource, AsyncRequestsResource
from .resources.audiences import AudiencesResource, AsyncAudiencesResource
+ from .resources.broadcasts import BroadcastsResource, AsyncBroadcastsResource
from .resources.inbox.inbox import InboxResource, AsyncInboxResource
from .resources.lists.lists import ListsResource, AsyncListsResource
from .resources.users.users import UsersResource, AsyncUsersResource
@@ -211,6 +213,12 @@ def journeys(self) -> JourneysResource:
return JourneysResource(self)
+ @cached_property
+ def broadcasts(self) -> BroadcastsResource:
+ from .resources.broadcasts import BroadcastsResource
+
+ return BroadcastsResource(self)
+
@cached_property
def brands(self) -> BrandsResource:
"""
@@ -566,6 +574,12 @@ def journeys(self) -> AsyncJourneysResource:
return AsyncJourneysResource(self)
+ @cached_property
+ def broadcasts(self) -> AsyncBroadcastsResource:
+ from .resources.broadcasts import AsyncBroadcastsResource
+
+ return AsyncBroadcastsResource(self)
+
@cached_property
def brands(self) -> AsyncBrandsResource:
"""
@@ -863,6 +877,12 @@ def journeys(self) -> journeys.JourneysResourceWithRawResponse:
return JourneysResourceWithRawResponse(self._client.journeys)
+ @cached_property
+ def broadcasts(self) -> broadcasts.BroadcastsResourceWithRawResponse:
+ from .resources.broadcasts import BroadcastsResourceWithRawResponse
+
+ return BroadcastsResourceWithRawResponse(self._client.broadcasts)
+
@cached_property
def brands(self) -> brands.BrandsResourceWithRawResponse:
"""
@@ -1048,6 +1068,12 @@ def journeys(self) -> journeys.AsyncJourneysResourceWithRawResponse:
return AsyncJourneysResourceWithRawResponse(self._client.journeys)
+ @cached_property
+ def broadcasts(self) -> broadcasts.AsyncBroadcastsResourceWithRawResponse:
+ from .resources.broadcasts import AsyncBroadcastsResourceWithRawResponse
+
+ return AsyncBroadcastsResourceWithRawResponse(self._client.broadcasts)
+
@cached_property
def brands(self) -> brands.AsyncBrandsResourceWithRawResponse:
"""
@@ -1233,6 +1259,12 @@ def journeys(self) -> journeys.JourneysResourceWithStreamingResponse:
return JourneysResourceWithStreamingResponse(self._client.journeys)
+ @cached_property
+ def broadcasts(self) -> broadcasts.BroadcastsResourceWithStreamingResponse:
+ from .resources.broadcasts import BroadcastsResourceWithStreamingResponse
+
+ return BroadcastsResourceWithStreamingResponse(self._client.broadcasts)
+
@cached_property
def brands(self) -> brands.BrandsResourceWithStreamingResponse:
"""
@@ -1418,6 +1450,12 @@ def journeys(self) -> journeys.AsyncJourneysResourceWithStreamingResponse:
return AsyncJourneysResourceWithStreamingResponse(self._client.journeys)
+ @cached_property
+ def broadcasts(self) -> broadcasts.AsyncBroadcastsResourceWithStreamingResponse:
+ from .resources.broadcasts import AsyncBroadcastsResourceWithStreamingResponse
+
+ return AsyncBroadcastsResourceWithStreamingResponse(self._client.broadcasts)
+
@cached_property
def brands(self) -> brands.AsyncBrandsResourceWithStreamingResponse:
"""
diff --git a/src/courier/_version.py b/src/courier/_version.py
index 6b18c05..3bba92e 100644
--- a/src/courier/_version.py
+++ b/src/courier/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "courier"
-__version__ = "7.22.0" # x-release-please-version
+__version__ = "7.23.0" # x-release-please-version
diff --git a/src/courier/resources/__init__.py b/src/courier/resources/__init__.py
index 3c433ca..1528692 100644
--- a/src/courier/resources/__init__.py
+++ b/src/courier/resources/__init__.py
@@ -120,6 +120,14 @@
ProvidersResourceWithStreamingResponse,
AsyncProvidersResourceWithStreamingResponse,
)
+from .broadcasts import (
+ BroadcastsResource,
+ AsyncBroadcastsResource,
+ BroadcastsResourceWithRawResponse,
+ AsyncBroadcastsResourceWithRawResponse,
+ BroadcastsResourceWithStreamingResponse,
+ AsyncBroadcastsResourceWithStreamingResponse,
+)
from .automations import (
AutomationsResource,
AsyncAutomationsResource,
@@ -212,6 +220,12 @@
"AsyncJourneysResourceWithRawResponse",
"JourneysResourceWithStreamingResponse",
"AsyncJourneysResourceWithStreamingResponse",
+ "BroadcastsResource",
+ "AsyncBroadcastsResource",
+ "BroadcastsResourceWithRawResponse",
+ "AsyncBroadcastsResourceWithRawResponse",
+ "BroadcastsResourceWithStreamingResponse",
+ "AsyncBroadcastsResourceWithStreamingResponse",
"BrandsResource",
"AsyncBrandsResource",
"BrandsResourceWithRawResponse",
diff --git a/src/courier/resources/broadcasts.py b/src/courier/resources/broadcasts.py
new file mode 100644
index 0000000..0465e10
--- /dev/null
+++ b/src/courier/resources/broadcasts.py
@@ -0,0 +1,1175 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import Literal
+
+import httpx
+
+from ..types import (
+ NotificationTemplateState,
+ broadcast_list_params,
+ broadcast_send_params,
+ broadcast_create_params,
+ broadcast_update_params,
+ broadcast_schedule_params,
+ broadcast_put_content_params,
+ broadcast_retrieve_content_params,
+)
+from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from .._utils import path_template, maybe_transform, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from .._base_client import make_request_options
+from ..types.broadcast import Broadcast
+from ..types.broadcast_list_response import BroadcastListResponse
+from ..types.notification_template_state import NotificationTemplateState
+from ..types.notification_content_get_response import NotificationContentGetResponse
+from ..types.notification_content_mutation_response import NotificationContentMutationResponse
+
+__all__ = ["BroadcastsResource", "AsyncBroadcastsResource"]
+
+
+class BroadcastsResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> BroadcastsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/trycourier/courier-python#accessing-raw-response-data-eg-headers
+ """
+ return BroadcastsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> BroadcastsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/trycourier/courier-python#with_streaming_response
+ """
+ return BroadcastsResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ *,
+ channel: Literal["email", "sms", "push", "inbox", "slack", "msteams"],
+ name: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Broadcast:
+ """Create a broadcast.
+
+ Provisions a private notification template for the broadcast
+ and returns the new broadcast in the draft state. Exactly one channel is
+ required.
+
+ Args:
+ channel: The single delivery channel for this broadcast.
+
+ name: Human-readable name.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/broadcasts",
+ body=maybe_transform(
+ {
+ "channel": channel,
+ "name": name,
+ },
+ broadcast_create_params.BroadcastCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Broadcast,
+ )
+
+ def retrieve(
+ self,
+ broadcast_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Broadcast:
+ """Retrieve a broadcast by ID.
+
+ Archived broadcasts return 404.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not broadcast_id:
+ raise ValueError(f"Expected a non-empty value for `broadcast_id` but received {broadcast_id!r}")
+ return self._get(
+ path_template("/broadcasts/{broadcast_id}", broadcast_id=broadcast_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Broadcast,
+ )
+
+ def update(
+ self,
+ broadcast_id: str,
+ *,
+ name: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Broadcast:
+ """Update a broadcast's name.
+
+ Content is edited via the broadcast's notification
+ template, not this endpoint.
+
+ Args:
+ name: New human-readable name.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not broadcast_id:
+ raise ValueError(f"Expected a non-empty value for `broadcast_id` but received {broadcast_id!r}")
+ return self._put(
+ path_template("/broadcasts/{broadcast_id}", broadcast_id=broadcast_id),
+ body=maybe_transform({"name": name}, broadcast_update_params.BroadcastUpdateParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Broadcast,
+ )
+
+ def list(
+ self,
+ *,
+ cursor: Optional[str] | Omit = omit,
+ limit: int | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> BroadcastListResponse:
+ """List broadcasts in your workspace.
+
+ Cursor-paginated; returns broadcasts
+ newest-first.
+
+ Args:
+ cursor: Opaque pagination cursor from a previous response. Omit for the first page.
+
+ limit: Maximum number of results per page.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get(
+ "/broadcasts",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "cursor": cursor,
+ "limit": limit,
+ },
+ broadcast_list_params.BroadcastListParams,
+ ),
+ ),
+ cast_to=BroadcastListResponse,
+ )
+
+ def archive(
+ self,
+ broadcast_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Broadcast:
+ """Archive a broadcast.
+
+ This is a soft delete — the archived broadcast is returned
+ and no longer appears in list results.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not broadcast_id:
+ raise ValueError(f"Expected a non-empty value for `broadcast_id` but received {broadcast_id!r}")
+ return self._delete(
+ path_template("/broadcasts/{broadcast_id}", broadcast_id=broadcast_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Broadcast,
+ )
+
+ def cancel(
+ self,
+ broadcast_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Broadcast:
+ """Cancel a broadcast's pending schedule, returning it to the draft state.
+
+ Only
+ valid for a scheduled broadcast.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not broadcast_id:
+ raise ValueError(f"Expected a non-empty value for `broadcast_id` but received {broadcast_id!r}")
+ return self._post(
+ path_template("/broadcasts/{broadcast_id}/cancel", broadcast_id=broadcast_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Broadcast,
+ )
+
+ def duplicate(
+ self,
+ broadcast_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Broadcast:
+ """
+ Duplicate a broadcast (and its template) into a new draft named "{source name}
+ (copy)".
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not broadcast_id:
+ raise ValueError(f"Expected a non-empty value for `broadcast_id` but received {broadcast_id!r}")
+ return self._post(
+ path_template("/broadcasts/{broadcast_id}/duplicate", broadcast_id=broadcast_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Broadcast,
+ )
+
+ def put_content(
+ self,
+ broadcast_id: str,
+ *,
+ content: broadcast_put_content_params.Content,
+ state: NotificationTemplateState | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> NotificationContentMutationResponse:
+ """
+ Author the broadcast's content by replacing the draft elemental content of its
+ private notification template. The draft is published automatically when the
+ broadcast is sent or scheduled.
+
+ Args:
+ content: Elemental content payload. The server defaults `version` when omitted.
+
+ state: Template state. Defaults to `DRAFT`.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not broadcast_id:
+ raise ValueError(f"Expected a non-empty value for `broadcast_id` but received {broadcast_id!r}")
+ return self._put(
+ path_template("/broadcasts/{broadcast_id}/content", broadcast_id=broadcast_id),
+ body=maybe_transform(
+ {
+ "content": content,
+ "state": state,
+ },
+ broadcast_put_content_params.BroadcastPutContentParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NotificationContentMutationResponse,
+ )
+
+ def retrieve_content(
+ self,
+ broadcast_id: str,
+ *,
+ version: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> NotificationContentGetResponse:
+ """
+ Retrieve the broadcast's content — the elemental content of its private
+ notification template. Defaults to the working draft, since broadcast content is
+ authored as a draft until the broadcast is sent.
+
+ Args:
+ version: Accepts `draft`, `published`, or a version string (e.g. `v001`). Defaults to
+ `draft`.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not broadcast_id:
+ raise ValueError(f"Expected a non-empty value for `broadcast_id` but received {broadcast_id!r}")
+ return self._get(
+ path_template("/broadcasts/{broadcast_id}/content", broadcast_id=broadcast_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {"version": version}, broadcast_retrieve_content_params.BroadcastRetrieveContentParams
+ ),
+ ),
+ cast_to=NotificationContentGetResponse,
+ )
+
+ def schedule(
+ self,
+ broadcast_id: str,
+ *,
+ recipient_id: str,
+ recipient_type: Literal["list", "audience"],
+ scheduled_to: str,
+ timezone: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Broadcast:
+ """Schedule a broadcast for a future send to a list or audience.
+
+ Publishes the
+ broadcast template first. Not allowed once the broadcast is sending or sent. For
+ an immediate send use POST /broadcasts/{broadcastId}/send.
+
+ Args:
+ recipient_id: ID of the target list or audience.
+
+ recipient_type: Whether the broadcast targets a list or an audience.
+
+ scheduled_to: Wall-clock timestamp of the future send, no timezone offset (e.g.
+ "2026-07-21T20:00:00"). The zone is given by `timezone`.
+
+ timezone: IANA timezone for the scheduled send (e.g. America/New_York).
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not broadcast_id:
+ raise ValueError(f"Expected a non-empty value for `broadcast_id` but received {broadcast_id!r}")
+ return self._post(
+ path_template("/broadcasts/{broadcast_id}/schedule", broadcast_id=broadcast_id),
+ body=maybe_transform(
+ {
+ "recipient_id": recipient_id,
+ "recipient_type": recipient_type,
+ "scheduled_to": scheduled_to,
+ "timezone": timezone,
+ },
+ broadcast_schedule_params.BroadcastScheduleParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Broadcast,
+ )
+
+ def send(
+ self,
+ broadcast_id: str,
+ *,
+ recipient_id: str,
+ recipient_type: Literal["list", "audience"],
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Broadcast:
+ """Send a broadcast immediately to a list or audience.
+
+ Publishes the broadcast
+ template first. Not allowed once the broadcast is sending or sent.
+
+ Args:
+ recipient_id: ID of the target list or audience.
+
+ recipient_type: Whether the broadcast targets a list or an audience.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not broadcast_id:
+ raise ValueError(f"Expected a non-empty value for `broadcast_id` but received {broadcast_id!r}")
+ return self._post(
+ path_template("/broadcasts/{broadcast_id}/send", broadcast_id=broadcast_id),
+ body=maybe_transform(
+ {
+ "recipient_id": recipient_id,
+ "recipient_type": recipient_type,
+ },
+ broadcast_send_params.BroadcastSendParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Broadcast,
+ )
+
+
+class AsyncBroadcastsResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncBroadcastsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/trycourier/courier-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncBroadcastsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncBroadcastsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/trycourier/courier-python#with_streaming_response
+ """
+ return AsyncBroadcastsResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ *,
+ channel: Literal["email", "sms", "push", "inbox", "slack", "msteams"],
+ name: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Broadcast:
+ """Create a broadcast.
+
+ Provisions a private notification template for the broadcast
+ and returns the new broadcast in the draft state. Exactly one channel is
+ required.
+
+ Args:
+ channel: The single delivery channel for this broadcast.
+
+ name: Human-readable name.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/broadcasts",
+ body=await async_maybe_transform(
+ {
+ "channel": channel,
+ "name": name,
+ },
+ broadcast_create_params.BroadcastCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Broadcast,
+ )
+
+ async def retrieve(
+ self,
+ broadcast_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Broadcast:
+ """Retrieve a broadcast by ID.
+
+ Archived broadcasts return 404.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not broadcast_id:
+ raise ValueError(f"Expected a non-empty value for `broadcast_id` but received {broadcast_id!r}")
+ return await self._get(
+ path_template("/broadcasts/{broadcast_id}", broadcast_id=broadcast_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Broadcast,
+ )
+
+ async def update(
+ self,
+ broadcast_id: str,
+ *,
+ name: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Broadcast:
+ """Update a broadcast's name.
+
+ Content is edited via the broadcast's notification
+ template, not this endpoint.
+
+ Args:
+ name: New human-readable name.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not broadcast_id:
+ raise ValueError(f"Expected a non-empty value for `broadcast_id` but received {broadcast_id!r}")
+ return await self._put(
+ path_template("/broadcasts/{broadcast_id}", broadcast_id=broadcast_id),
+ body=await async_maybe_transform({"name": name}, broadcast_update_params.BroadcastUpdateParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Broadcast,
+ )
+
+ async def list(
+ self,
+ *,
+ cursor: Optional[str] | Omit = omit,
+ limit: int | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> BroadcastListResponse:
+ """List broadcasts in your workspace.
+
+ Cursor-paginated; returns broadcasts
+ newest-first.
+
+ Args:
+ cursor: Opaque pagination cursor from a previous response. Omit for the first page.
+
+ limit: Maximum number of results per page.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._get(
+ "/broadcasts",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "cursor": cursor,
+ "limit": limit,
+ },
+ broadcast_list_params.BroadcastListParams,
+ ),
+ ),
+ cast_to=BroadcastListResponse,
+ )
+
+ async def archive(
+ self,
+ broadcast_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Broadcast:
+ """Archive a broadcast.
+
+ This is a soft delete — the archived broadcast is returned
+ and no longer appears in list results.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not broadcast_id:
+ raise ValueError(f"Expected a non-empty value for `broadcast_id` but received {broadcast_id!r}")
+ return await self._delete(
+ path_template("/broadcasts/{broadcast_id}", broadcast_id=broadcast_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Broadcast,
+ )
+
+ async def cancel(
+ self,
+ broadcast_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Broadcast:
+ """Cancel a broadcast's pending schedule, returning it to the draft state.
+
+ Only
+ valid for a scheduled broadcast.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not broadcast_id:
+ raise ValueError(f"Expected a non-empty value for `broadcast_id` but received {broadcast_id!r}")
+ return await self._post(
+ path_template("/broadcasts/{broadcast_id}/cancel", broadcast_id=broadcast_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Broadcast,
+ )
+
+ async def duplicate(
+ self,
+ broadcast_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Broadcast:
+ """
+ Duplicate a broadcast (and its template) into a new draft named "{source name}
+ (copy)".
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not broadcast_id:
+ raise ValueError(f"Expected a non-empty value for `broadcast_id` but received {broadcast_id!r}")
+ return await self._post(
+ path_template("/broadcasts/{broadcast_id}/duplicate", broadcast_id=broadcast_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Broadcast,
+ )
+
+ async def put_content(
+ self,
+ broadcast_id: str,
+ *,
+ content: broadcast_put_content_params.Content,
+ state: NotificationTemplateState | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> NotificationContentMutationResponse:
+ """
+ Author the broadcast's content by replacing the draft elemental content of its
+ private notification template. The draft is published automatically when the
+ broadcast is sent or scheduled.
+
+ Args:
+ content: Elemental content payload. The server defaults `version` when omitted.
+
+ state: Template state. Defaults to `DRAFT`.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not broadcast_id:
+ raise ValueError(f"Expected a non-empty value for `broadcast_id` but received {broadcast_id!r}")
+ return await self._put(
+ path_template("/broadcasts/{broadcast_id}/content", broadcast_id=broadcast_id),
+ body=await async_maybe_transform(
+ {
+ "content": content,
+ "state": state,
+ },
+ broadcast_put_content_params.BroadcastPutContentParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NotificationContentMutationResponse,
+ )
+
+ async def retrieve_content(
+ self,
+ broadcast_id: str,
+ *,
+ version: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> NotificationContentGetResponse:
+ """
+ Retrieve the broadcast's content — the elemental content of its private
+ notification template. Defaults to the working draft, since broadcast content is
+ authored as a draft until the broadcast is sent.
+
+ Args:
+ version: Accepts `draft`, `published`, or a version string (e.g. `v001`). Defaults to
+ `draft`.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not broadcast_id:
+ raise ValueError(f"Expected a non-empty value for `broadcast_id` but received {broadcast_id!r}")
+ return await self._get(
+ path_template("/broadcasts/{broadcast_id}/content", broadcast_id=broadcast_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {"version": version}, broadcast_retrieve_content_params.BroadcastRetrieveContentParams
+ ),
+ ),
+ cast_to=NotificationContentGetResponse,
+ )
+
+ async def schedule(
+ self,
+ broadcast_id: str,
+ *,
+ recipient_id: str,
+ recipient_type: Literal["list", "audience"],
+ scheduled_to: str,
+ timezone: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Broadcast:
+ """Schedule a broadcast for a future send to a list or audience.
+
+ Publishes the
+ broadcast template first. Not allowed once the broadcast is sending or sent. For
+ an immediate send use POST /broadcasts/{broadcastId}/send.
+
+ Args:
+ recipient_id: ID of the target list or audience.
+
+ recipient_type: Whether the broadcast targets a list or an audience.
+
+ scheduled_to: Wall-clock timestamp of the future send, no timezone offset (e.g.
+ "2026-07-21T20:00:00"). The zone is given by `timezone`.
+
+ timezone: IANA timezone for the scheduled send (e.g. America/New_York).
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not broadcast_id:
+ raise ValueError(f"Expected a non-empty value for `broadcast_id` but received {broadcast_id!r}")
+ return await self._post(
+ path_template("/broadcasts/{broadcast_id}/schedule", broadcast_id=broadcast_id),
+ body=await async_maybe_transform(
+ {
+ "recipient_id": recipient_id,
+ "recipient_type": recipient_type,
+ "scheduled_to": scheduled_to,
+ "timezone": timezone,
+ },
+ broadcast_schedule_params.BroadcastScheduleParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Broadcast,
+ )
+
+ async def send(
+ self,
+ broadcast_id: str,
+ *,
+ recipient_id: str,
+ recipient_type: Literal["list", "audience"],
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> Broadcast:
+ """Send a broadcast immediately to a list or audience.
+
+ Publishes the broadcast
+ template first. Not allowed once the broadcast is sending or sent.
+
+ Args:
+ recipient_id: ID of the target list or audience.
+
+ recipient_type: Whether the broadcast targets a list or an audience.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not broadcast_id:
+ raise ValueError(f"Expected a non-empty value for `broadcast_id` but received {broadcast_id!r}")
+ return await self._post(
+ path_template("/broadcasts/{broadcast_id}/send", broadcast_id=broadcast_id),
+ body=await async_maybe_transform(
+ {
+ "recipient_id": recipient_id,
+ "recipient_type": recipient_type,
+ },
+ broadcast_send_params.BroadcastSendParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Broadcast,
+ )
+
+
+class BroadcastsResourceWithRawResponse:
+ def __init__(self, broadcasts: BroadcastsResource) -> None:
+ self._broadcasts = broadcasts
+
+ self.create = to_raw_response_wrapper(
+ broadcasts.create,
+ )
+ self.retrieve = to_raw_response_wrapper(
+ broadcasts.retrieve,
+ )
+ self.update = to_raw_response_wrapper(
+ broadcasts.update,
+ )
+ self.list = to_raw_response_wrapper(
+ broadcasts.list,
+ )
+ self.archive = to_raw_response_wrapper(
+ broadcasts.archive,
+ )
+ self.cancel = to_raw_response_wrapper(
+ broadcasts.cancel,
+ )
+ self.duplicate = to_raw_response_wrapper(
+ broadcasts.duplicate,
+ )
+ self.put_content = to_raw_response_wrapper(
+ broadcasts.put_content,
+ )
+ self.retrieve_content = to_raw_response_wrapper(
+ broadcasts.retrieve_content,
+ )
+ self.schedule = to_raw_response_wrapper(
+ broadcasts.schedule,
+ )
+ self.send = to_raw_response_wrapper(
+ broadcasts.send,
+ )
+
+
+class AsyncBroadcastsResourceWithRawResponse:
+ def __init__(self, broadcasts: AsyncBroadcastsResource) -> None:
+ self._broadcasts = broadcasts
+
+ self.create = async_to_raw_response_wrapper(
+ broadcasts.create,
+ )
+ self.retrieve = async_to_raw_response_wrapper(
+ broadcasts.retrieve,
+ )
+ self.update = async_to_raw_response_wrapper(
+ broadcasts.update,
+ )
+ self.list = async_to_raw_response_wrapper(
+ broadcasts.list,
+ )
+ self.archive = async_to_raw_response_wrapper(
+ broadcasts.archive,
+ )
+ self.cancel = async_to_raw_response_wrapper(
+ broadcasts.cancel,
+ )
+ self.duplicate = async_to_raw_response_wrapper(
+ broadcasts.duplicate,
+ )
+ self.put_content = async_to_raw_response_wrapper(
+ broadcasts.put_content,
+ )
+ self.retrieve_content = async_to_raw_response_wrapper(
+ broadcasts.retrieve_content,
+ )
+ self.schedule = async_to_raw_response_wrapper(
+ broadcasts.schedule,
+ )
+ self.send = async_to_raw_response_wrapper(
+ broadcasts.send,
+ )
+
+
+class BroadcastsResourceWithStreamingResponse:
+ def __init__(self, broadcasts: BroadcastsResource) -> None:
+ self._broadcasts = broadcasts
+
+ self.create = to_streamed_response_wrapper(
+ broadcasts.create,
+ )
+ self.retrieve = to_streamed_response_wrapper(
+ broadcasts.retrieve,
+ )
+ self.update = to_streamed_response_wrapper(
+ broadcasts.update,
+ )
+ self.list = to_streamed_response_wrapper(
+ broadcasts.list,
+ )
+ self.archive = to_streamed_response_wrapper(
+ broadcasts.archive,
+ )
+ self.cancel = to_streamed_response_wrapper(
+ broadcasts.cancel,
+ )
+ self.duplicate = to_streamed_response_wrapper(
+ broadcasts.duplicate,
+ )
+ self.put_content = to_streamed_response_wrapper(
+ broadcasts.put_content,
+ )
+ self.retrieve_content = to_streamed_response_wrapper(
+ broadcasts.retrieve_content,
+ )
+ self.schedule = to_streamed_response_wrapper(
+ broadcasts.schedule,
+ )
+ self.send = to_streamed_response_wrapper(
+ broadcasts.send,
+ )
+
+
+class AsyncBroadcastsResourceWithStreamingResponse:
+ def __init__(self, broadcasts: AsyncBroadcastsResource) -> None:
+ self._broadcasts = broadcasts
+
+ self.create = async_to_streamed_response_wrapper(
+ broadcasts.create,
+ )
+ self.retrieve = async_to_streamed_response_wrapper(
+ broadcasts.retrieve,
+ )
+ self.update = async_to_streamed_response_wrapper(
+ broadcasts.update,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ broadcasts.list,
+ )
+ self.archive = async_to_streamed_response_wrapper(
+ broadcasts.archive,
+ )
+ self.cancel = async_to_streamed_response_wrapper(
+ broadcasts.cancel,
+ )
+ self.duplicate = async_to_streamed_response_wrapper(
+ broadcasts.duplicate,
+ )
+ self.put_content = async_to_streamed_response_wrapper(
+ broadcasts.put_content,
+ )
+ self.retrieve_content = async_to_streamed_response_wrapper(
+ broadcasts.retrieve_content,
+ )
+ self.schedule = async_to_streamed_response_wrapper(
+ broadcasts.schedule,
+ )
+ self.send = async_to_streamed_response_wrapper(
+ broadcasts.send,
+ )
diff --git a/src/courier/types/__init__.py b/src/courier/types/__init__.py
index b3c63f2..938f123 100644
--- a/src/courier/types/__init__.py
+++ b/src/courier/types/__init__.py
@@ -102,6 +102,7 @@
from .journey import Journey as Journey
from .audience import Audience as Audience
from .provider import Provider as Provider
+from .broadcast import Broadcast as Broadcast
from .base_check import BaseCheck as BaseCheck
from .email_head import EmailHead as EmailHead
from .logo_param import LogoParam as LogoParam
@@ -131,6 +132,7 @@
from .subscription_list import SubscriptionList as SubscriptionList
from .widget_background import WidgetBackground as WidgetBackground
from .brand_colors_param import BrandColorsParam as BrandColorsParam
+from .broadcast_schedule import BroadcastSchedule as BroadcastSchedule
from .email_footer_param import EmailFooterParam as EmailFooterParam
from .email_header_param import EmailHeaderParam as EmailHeaderParam
from .journey_experiment import JourneyExperiment as JourneyExperiment
@@ -158,6 +160,8 @@
from .tenant_list_response import TenantListResponse as TenantListResponse
from .tenant_update_params import TenantUpdateParams as TenantUpdateParams
from .brand_settings_in_app import BrandSettingsInApp as BrandSettingsInApp
+from .broadcast_list_params import BroadcastListParams as BroadcastListParams
+from .broadcast_send_params import BroadcastSendParams as BroadcastSendParams
from .journey_ai_node_param import JourneyAINodeParam as JourneyAINodeParam
from .journey_cancel_params import JourneyCancelParams as JourneyCancelParams
from .journey_create_params import JourneyCreateParams as JourneyCreateParams
@@ -183,6 +187,9 @@
from .subscription_topic_new import SubscriptionTopicNew as SubscriptionTopicNew
from .audit_event_list_params import AuditEventListParams as AuditEventListParams
from .auth_issue_token_params import AuthIssueTokenParams as AuthIssueTokenParams
+from .broadcast_create_params import BroadcastCreateParams as BroadcastCreateParams
+from .broadcast_list_response import BroadcastListResponse as BroadcastListResponse
+from .broadcast_update_params import BroadcastUpdateParams as BroadcastUpdateParams
from .cancel_journey_response import CancelJourneyResponse as CancelJourneyResponse
from .journey_condition_group import JourneyConditionGroup as JourneyConditionGroup
from .journey_exit_node_param import JourneyExitNodeParam as JourneyExitNodeParam
@@ -208,6 +215,7 @@
from .tenant_list_users_params import TenantListUsersParams as TenantListUsersParams
from .audit_event_list_response import AuditEventListResponse as AuditEventListResponse
from .auth_issue_token_response import AuthIssueTokenResponse as AuthIssueTokenResponse
+from .broadcast_schedule_params import BroadcastScheduleParams as BroadcastScheduleParams
from .default_preferences_param import DefaultPreferencesParam as DefaultPreferencesParam
from .message_retrieve_response import MessageRetrieveResponse as MessageRetrieveResponse
from .profile_retrieve_response import ProfileRetrieveResponse as ProfileRetrieveResponse
@@ -227,6 +235,7 @@
from .notification_template_state import NotificationTemplateState as NotificationTemplateState
from .tenant_template_input_param import TenantTemplateInputParam as TenantTemplateInputParam
from .audience_list_members_params import AudienceListMembersParams as AudienceListMembersParams
+from .broadcast_put_content_params import BroadcastPutContentParams as BroadcastPutContentParams
from .cancel_journey_request_param import CancelJourneyRequestParam as CancelJourneyRequestParam
from .inbound_track_event_response import InboundTrackEventResponse as InboundTrackEventResponse
from .journey_condition_atom_param import JourneyConditionAtomParam as JourneyConditionAtomParam
@@ -263,6 +272,7 @@
from .base_template_tenant_association import BaseTemplateTenantAssociation as BaseTemplateTenantAssociation
from .journey_experiment_variant_param import JourneyExperimentVariantParam as JourneyExperimentVariantParam
from .automation_template_list_response import AutomationTemplateListResponse as AutomationTemplateListResponse
+from .broadcast_retrieve_content_params import BroadcastRetrieveContentParams as BroadcastRetrieveContentParams
from .journey_delay_duration_node_param import JourneyDelayDurationNodeParam as JourneyDelayDurationNodeParam
from .journey_fetch_post_put_node_param import JourneyFetchPostPutNodeParam as JourneyFetchPostPutNodeParam
from .notification_content_get_response import NotificationContentGetResponse as NotificationContentGetResponse
diff --git a/src/courier/types/broadcast.py b/src/courier/types/broadcast.py
new file mode 100644
index 0000000..b736a13
--- /dev/null
+++ b/src/courier/types/broadcast.py
@@ -0,0 +1,48 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from .._models import BaseModel
+from .broadcast_schedule import BroadcastSchedule
+
+__all__ = ["Broadcast"]
+
+
+class Broadcast(BaseModel):
+ """
+ A broadcast — a single-channel message delivered to a known set of recipients (a list or audience).
+ """
+
+ id: str
+ """The broadcast ID (bst\\__ prefix)."""
+
+ channel: Literal["email", "sms", "push", "inbox", "slack", "msteams"]
+ """The broadcast's delivery channel."""
+
+ created_at: str
+ """ISO 8601 timestamp when the broadcast was created."""
+
+ created_by: str
+ """Actor that created the broadcast."""
+
+ name: str
+ """Human-readable name."""
+
+ status: Literal["draft", "scheduled", "sending", "sent"]
+ """Lifecycle status of the broadcast."""
+
+ updated_at: str
+ """ISO 8601 timestamp of the last update."""
+
+ updated_by: str
+ """Actor that last updated the broadcast."""
+
+ archived_at: Optional[str] = None
+ """ISO 8601 timestamp when the broadcast was archived, if archived."""
+
+ archived_by: Optional[str] = None
+ """Actor that archived the broadcast, if archived."""
+
+ schedule: Optional[BroadcastSchedule] = None
+ """The delivery schedule and recipient targeting for a broadcast."""
diff --git a/src/courier/types/broadcast_create_params.py b/src/courier/types/broadcast_create_params.py
new file mode 100644
index 0000000..330ab8b
--- /dev/null
+++ b/src/courier/types/broadcast_create_params.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["BroadcastCreateParams"]
+
+
+class BroadcastCreateParams(TypedDict, total=False):
+ channel: Required[Literal["email", "sms", "push", "inbox", "slack", "msteams"]]
+ """The single delivery channel for this broadcast."""
+
+ name: Required[str]
+ """Human-readable name."""
diff --git a/src/courier/types/broadcast_list_params.py b/src/courier/types/broadcast_list_params.py
new file mode 100644
index 0000000..9d89f6c
--- /dev/null
+++ b/src/courier/types/broadcast_list_params.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Optional
+from typing_extensions import TypedDict
+
+__all__ = ["BroadcastListParams"]
+
+
+class BroadcastListParams(TypedDict, total=False):
+ cursor: Optional[str]
+ """Opaque pagination cursor from a previous response. Omit for the first page."""
+
+ limit: int
+ """Maximum number of results per page."""
diff --git a/src/courier/types/broadcast_list_response.py b/src/courier/types/broadcast_list_response.py
new file mode 100644
index 0000000..acaa032
--- /dev/null
+++ b/src/courier/types/broadcast_list_response.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+
+from .._models import BaseModel
+from .broadcast import Broadcast
+from .shared.paging import Paging
+
+__all__ = ["BroadcastListResponse"]
+
+
+class BroadcastListResponse(BaseModel):
+ """Paginated list of broadcasts."""
+
+ paging: Paging
+
+ results: List[Broadcast]
diff --git a/src/courier/types/broadcast_put_content_params.py b/src/courier/types/broadcast_put_content_params.py
new file mode 100644
index 0000000..8825088
--- /dev/null
+++ b/src/courier/types/broadcast_put_content_params.py
@@ -0,0 +1,31 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable
+from typing_extensions import Required, TypedDict
+
+from .notification_template_state import NotificationTemplateState
+from .shared_params.elemental_node import ElementalNode
+
+__all__ = ["BroadcastPutContentParams", "Content"]
+
+
+class BroadcastPutContentParams(TypedDict, total=False):
+ content: Required[Content]
+ """Elemental content payload. The server defaults `version` when omitted."""
+
+ state: NotificationTemplateState
+ """Template state. Defaults to `DRAFT`."""
+
+
+class Content(TypedDict, total=False):
+ """Elemental content payload. The server defaults `version` when omitted."""
+
+ elements: Required[Iterable[ElementalNode]]
+
+ version: str
+ """Content version identifier (e.g., `2022-01-01`).
+
+ Optional; server defaults when omitted.
+ """
diff --git a/src/courier/types/broadcast_retrieve_content_params.py b/src/courier/types/broadcast_retrieve_content_params.py
new file mode 100644
index 0000000..c3192a4
--- /dev/null
+++ b/src/courier/types/broadcast_retrieve_content_params.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["BroadcastRetrieveContentParams"]
+
+
+class BroadcastRetrieveContentParams(TypedDict, total=False):
+ version: str
+ """Accepts `draft`, `published`, or a version string (e.g.
+
+ `v001`). Defaults to `draft`.
+ """
diff --git a/src/courier/types/broadcast_schedule.py b/src/courier/types/broadcast_schedule.py
new file mode 100644
index 0000000..f907320
--- /dev/null
+++ b/src/courier/types/broadcast_schedule.py
@@ -0,0 +1,27 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from .._models import BaseModel
+
+__all__ = ["BroadcastSchedule"]
+
+
+class BroadcastSchedule(BaseModel):
+ """The delivery schedule and recipient targeting for a broadcast."""
+
+ recipient_id: str
+ """ID of the target list or audience."""
+
+ recipient_type: Literal["list", "audience"]
+ """Whether the broadcast targets a list or an audience."""
+
+ scheduled_to: Optional[str] = None
+ """Wall-clock timestamp of the scheduled send, no timezone offset (e.g.
+
+ "2026-07-21T20:00:00").
+ """
+
+ timezone: Optional[str] = None
+ """IANA timezone for the scheduled send (e.g. America/New_York)."""
diff --git a/src/courier/types/broadcast_schedule_params.py b/src/courier/types/broadcast_schedule_params.py
new file mode 100644
index 0000000..4cc7f3e
--- /dev/null
+++ b/src/courier/types/broadcast_schedule_params.py
@@ -0,0 +1,24 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["BroadcastScheduleParams"]
+
+
+class BroadcastScheduleParams(TypedDict, total=False):
+ recipient_id: Required[str]
+ """ID of the target list or audience."""
+
+ recipient_type: Required[Literal["list", "audience"]]
+ """Whether the broadcast targets a list or an audience."""
+
+ scheduled_to: Required[str]
+ """Wall-clock timestamp of the future send, no timezone offset (e.g.
+
+ "2026-07-21T20:00:00"). The zone is given by `timezone`.
+ """
+
+ timezone: str
+ """IANA timezone for the scheduled send (e.g. America/New_York)."""
diff --git a/src/courier/types/broadcast_send_params.py b/src/courier/types/broadcast_send_params.py
new file mode 100644
index 0000000..07fa67b
--- /dev/null
+++ b/src/courier/types/broadcast_send_params.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["BroadcastSendParams"]
+
+
+class BroadcastSendParams(TypedDict, total=False):
+ recipient_id: Required[str]
+ """ID of the target list or audience."""
+
+ recipient_type: Required[Literal["list", "audience"]]
+ """Whether the broadcast targets a list or an audience."""
diff --git a/src/courier/types/broadcast_update_params.py b/src/courier/types/broadcast_update_params.py
new file mode 100644
index 0000000..7d4da50
--- /dev/null
+++ b/src/courier/types/broadcast_update_params.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["BroadcastUpdateParams"]
+
+
+class BroadcastUpdateParams(TypedDict, total=False):
+ name: Required[str]
+ """New human-readable name."""
diff --git a/tests/api_resources/test_broadcasts.py b/tests/api_resources/test_broadcasts.py
new file mode 100644
index 0000000..cc57f88
--- /dev/null
+++ b/tests/api_resources/test_broadcasts.py
@@ -0,0 +1,1087 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from courier import Courier, AsyncCourier
+from tests.utils import assert_matches_type
+from courier.types import (
+ Broadcast,
+ BroadcastListResponse,
+ NotificationContentGetResponse,
+ NotificationContentMutationResponse,
+)
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestBroadcasts:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_create(self, client: Courier) -> None:
+ broadcast = client.broadcasts.create(
+ channel="email",
+ name="Spring Sale Announcement",
+ )
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_create(self, client: Courier) -> None:
+ response = client.broadcasts.with_raw_response.create(
+ channel="email",
+ name="Spring Sale Announcement",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ broadcast = response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_create(self, client: Courier) -> None:
+ with client.broadcasts.with_streaming_response.create(
+ channel="email",
+ name="Spring Sale Announcement",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ broadcast = response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_retrieve(self, client: Courier) -> None:
+ broadcast = client.broadcasts.retrieve(
+ "broadcastId",
+ )
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve(self, client: Courier) -> None:
+ response = client.broadcasts.with_raw_response.retrieve(
+ "broadcastId",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ broadcast = response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve(self, client: Courier) -> None:
+ with client.broadcasts.with_streaming_response.retrieve(
+ "broadcastId",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ broadcast = response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_retrieve(self, client: Courier) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `broadcast_id` but received ''"):
+ client.broadcasts.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_update(self, client: Courier) -> None:
+ broadcast = client.broadcasts.update(
+ broadcast_id="broadcastId",
+ name="Spring Sale Announcement (v2)",
+ )
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_update(self, client: Courier) -> None:
+ response = client.broadcasts.with_raw_response.update(
+ broadcast_id="broadcastId",
+ name="Spring Sale Announcement (v2)",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ broadcast = response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_update(self, client: Courier) -> None:
+ with client.broadcasts.with_streaming_response.update(
+ broadcast_id="broadcastId",
+ name="Spring Sale Announcement (v2)",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ broadcast = response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_update(self, client: Courier) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `broadcast_id` but received ''"):
+ client.broadcasts.with_raw_response.update(
+ broadcast_id="",
+ name="Spring Sale Announcement (v2)",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list(self, client: Courier) -> None:
+ broadcast = client.broadcasts.list()
+ assert_matches_type(BroadcastListResponse, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_with_all_params(self, client: Courier) -> None:
+ broadcast = client.broadcasts.list(
+ cursor="cursor",
+ limit=1,
+ )
+ assert_matches_type(BroadcastListResponse, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: Courier) -> None:
+ response = client.broadcasts.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ broadcast = response.parse()
+ assert_matches_type(BroadcastListResponse, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: Courier) -> None:
+ with client.broadcasts.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ broadcast = response.parse()
+ assert_matches_type(BroadcastListResponse, broadcast, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_archive(self, client: Courier) -> None:
+ broadcast = client.broadcasts.archive(
+ "broadcastId",
+ )
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_archive(self, client: Courier) -> None:
+ response = client.broadcasts.with_raw_response.archive(
+ "broadcastId",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ broadcast = response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_archive(self, client: Courier) -> None:
+ with client.broadcasts.with_streaming_response.archive(
+ "broadcastId",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ broadcast = response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_archive(self, client: Courier) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `broadcast_id` but received ''"):
+ client.broadcasts.with_raw_response.archive(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_cancel(self, client: Courier) -> None:
+ broadcast = client.broadcasts.cancel(
+ "broadcastId",
+ )
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_cancel(self, client: Courier) -> None:
+ response = client.broadcasts.with_raw_response.cancel(
+ "broadcastId",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ broadcast = response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_cancel(self, client: Courier) -> None:
+ with client.broadcasts.with_streaming_response.cancel(
+ "broadcastId",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ broadcast = response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_cancel(self, client: Courier) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `broadcast_id` but received ''"):
+ client.broadcasts.with_raw_response.cancel(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_duplicate(self, client: Courier) -> None:
+ broadcast = client.broadcasts.duplicate(
+ "broadcastId",
+ )
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_duplicate(self, client: Courier) -> None:
+ response = client.broadcasts.with_raw_response.duplicate(
+ "broadcastId",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ broadcast = response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_duplicate(self, client: Courier) -> None:
+ with client.broadcasts.with_streaming_response.duplicate(
+ "broadcastId",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ broadcast = response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_duplicate(self, client: Courier) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `broadcast_id` but received ''"):
+ client.broadcasts.with_raw_response.duplicate(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_put_content(self, client: Courier) -> None:
+ broadcast = client.broadcasts.put_content(
+ broadcast_id="broadcastId",
+ content={"elements": [{}, {}]},
+ )
+ assert_matches_type(NotificationContentMutationResponse, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_put_content_with_all_params(self, client: Courier) -> None:
+ broadcast = client.broadcasts.put_content(
+ broadcast_id="broadcastId",
+ content={
+ "elements": [
+ {
+ "channels": ["string"],
+ "if": "if",
+ "loop": "loop",
+ "ref": "ref",
+ "type": "meta",
+ },
+ {
+ "channels": ["string"],
+ "if": "if",
+ "loop": "loop",
+ "ref": "ref",
+ "type": "text",
+ },
+ ],
+ "version": "2022-01-01",
+ },
+ state="DRAFT",
+ )
+ assert_matches_type(NotificationContentMutationResponse, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_put_content(self, client: Courier) -> None:
+ response = client.broadcasts.with_raw_response.put_content(
+ broadcast_id="broadcastId",
+ content={"elements": [{}, {}]},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ broadcast = response.parse()
+ assert_matches_type(NotificationContentMutationResponse, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_put_content(self, client: Courier) -> None:
+ with client.broadcasts.with_streaming_response.put_content(
+ broadcast_id="broadcastId",
+ content={"elements": [{}, {}]},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ broadcast = response.parse()
+ assert_matches_type(NotificationContentMutationResponse, broadcast, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_put_content(self, client: Courier) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `broadcast_id` but received ''"):
+ client.broadcasts.with_raw_response.put_content(
+ broadcast_id="",
+ content={"elements": [{}, {}]},
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_retrieve_content(self, client: Courier) -> None:
+ broadcast = client.broadcasts.retrieve_content(
+ broadcast_id="broadcastId",
+ )
+ assert_matches_type(NotificationContentGetResponse, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_retrieve_content_with_all_params(self, client: Courier) -> None:
+ broadcast = client.broadcasts.retrieve_content(
+ broadcast_id="broadcastId",
+ version="version",
+ )
+ assert_matches_type(NotificationContentGetResponse, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve_content(self, client: Courier) -> None:
+ response = client.broadcasts.with_raw_response.retrieve_content(
+ broadcast_id="broadcastId",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ broadcast = response.parse()
+ assert_matches_type(NotificationContentGetResponse, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve_content(self, client: Courier) -> None:
+ with client.broadcasts.with_streaming_response.retrieve_content(
+ broadcast_id="broadcastId",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ broadcast = response.parse()
+ assert_matches_type(NotificationContentGetResponse, broadcast, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_retrieve_content(self, client: Courier) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `broadcast_id` but received ''"):
+ client.broadcasts.with_raw_response.retrieve_content(
+ broadcast_id="",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_schedule(self, client: Courier) -> None:
+ broadcast = client.broadcasts.schedule(
+ broadcast_id="broadcastId",
+ recipient_id="aud_01kx4h2jdafq8bk9amzvy6hbv0",
+ recipient_type="audience",
+ scheduled_to="2026-08-01T15:00:00",
+ )
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_schedule_with_all_params(self, client: Courier) -> None:
+ broadcast = client.broadcasts.schedule(
+ broadcast_id="broadcastId",
+ recipient_id="aud_01kx4h2jdafq8bk9amzvy6hbv0",
+ recipient_type="audience",
+ scheduled_to="2026-08-01T15:00:00",
+ timezone="America/New_York",
+ )
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_schedule(self, client: Courier) -> None:
+ response = client.broadcasts.with_raw_response.schedule(
+ broadcast_id="broadcastId",
+ recipient_id="aud_01kx4h2jdafq8bk9amzvy6hbv0",
+ recipient_type="audience",
+ scheduled_to="2026-08-01T15:00:00",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ broadcast = response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_schedule(self, client: Courier) -> None:
+ with client.broadcasts.with_streaming_response.schedule(
+ broadcast_id="broadcastId",
+ recipient_id="aud_01kx4h2jdafq8bk9amzvy6hbv0",
+ recipient_type="audience",
+ scheduled_to="2026-08-01T15:00:00",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ broadcast = response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_schedule(self, client: Courier) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `broadcast_id` but received ''"):
+ client.broadcasts.with_raw_response.schedule(
+ broadcast_id="",
+ recipient_id="aud_01kx4h2jdafq8bk9amzvy6hbv0",
+ recipient_type="audience",
+ scheduled_to="2026-08-01T15:00:00",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_send(self, client: Courier) -> None:
+ broadcast = client.broadcasts.send(
+ broadcast_id="broadcastId",
+ recipient_id="cool-customers",
+ recipient_type="list",
+ )
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_send(self, client: Courier) -> None:
+ response = client.broadcasts.with_raw_response.send(
+ broadcast_id="broadcastId",
+ recipient_id="cool-customers",
+ recipient_type="list",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ broadcast = response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_send(self, client: Courier) -> None:
+ with client.broadcasts.with_streaming_response.send(
+ broadcast_id="broadcastId",
+ recipient_id="cool-customers",
+ recipient_type="list",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ broadcast = response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_send(self, client: Courier) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `broadcast_id` but received ''"):
+ client.broadcasts.with_raw_response.send(
+ broadcast_id="",
+ recipient_id="cool-customers",
+ recipient_type="list",
+ )
+
+
+class TestAsyncBroadcasts:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_create(self, async_client: AsyncCourier) -> None:
+ broadcast = await async_client.broadcasts.create(
+ channel="email",
+ name="Spring Sale Announcement",
+ )
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncCourier) -> None:
+ response = await async_client.broadcasts.with_raw_response.create(
+ channel="email",
+ name="Spring Sale Announcement",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ broadcast = await response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncCourier) -> None:
+ async with async_client.broadcasts.with_streaming_response.create(
+ channel="email",
+ name="Spring Sale Announcement",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ broadcast = await response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_retrieve(self, async_client: AsyncCourier) -> None:
+ broadcast = await async_client.broadcasts.retrieve(
+ "broadcastId",
+ )
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_retrieve(self, async_client: AsyncCourier) -> None:
+ response = await async_client.broadcasts.with_raw_response.retrieve(
+ "broadcastId",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ broadcast = await response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_retrieve(self, async_client: AsyncCourier) -> None:
+ async with async_client.broadcasts.with_streaming_response.retrieve(
+ "broadcastId",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ broadcast = await response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_retrieve(self, async_client: AsyncCourier) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `broadcast_id` but received ''"):
+ await async_client.broadcasts.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_update(self, async_client: AsyncCourier) -> None:
+ broadcast = await async_client.broadcasts.update(
+ broadcast_id="broadcastId",
+ name="Spring Sale Announcement (v2)",
+ )
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_update(self, async_client: AsyncCourier) -> None:
+ response = await async_client.broadcasts.with_raw_response.update(
+ broadcast_id="broadcastId",
+ name="Spring Sale Announcement (v2)",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ broadcast = await response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_update(self, async_client: AsyncCourier) -> None:
+ async with async_client.broadcasts.with_streaming_response.update(
+ broadcast_id="broadcastId",
+ name="Spring Sale Announcement (v2)",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ broadcast = await response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_update(self, async_client: AsyncCourier) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `broadcast_id` but received ''"):
+ await async_client.broadcasts.with_raw_response.update(
+ broadcast_id="",
+ name="Spring Sale Announcement (v2)",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list(self, async_client: AsyncCourier) -> None:
+ broadcast = await async_client.broadcasts.list()
+ assert_matches_type(BroadcastListResponse, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncCourier) -> None:
+ broadcast = await async_client.broadcasts.list(
+ cursor="cursor",
+ limit=1,
+ )
+ assert_matches_type(BroadcastListResponse, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncCourier) -> None:
+ response = await async_client.broadcasts.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ broadcast = await response.parse()
+ assert_matches_type(BroadcastListResponse, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncCourier) -> None:
+ async with async_client.broadcasts.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ broadcast = await response.parse()
+ assert_matches_type(BroadcastListResponse, broadcast, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_archive(self, async_client: AsyncCourier) -> None:
+ broadcast = await async_client.broadcasts.archive(
+ "broadcastId",
+ )
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_archive(self, async_client: AsyncCourier) -> None:
+ response = await async_client.broadcasts.with_raw_response.archive(
+ "broadcastId",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ broadcast = await response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_archive(self, async_client: AsyncCourier) -> None:
+ async with async_client.broadcasts.with_streaming_response.archive(
+ "broadcastId",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ broadcast = await response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_archive(self, async_client: AsyncCourier) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `broadcast_id` but received ''"):
+ await async_client.broadcasts.with_raw_response.archive(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_cancel(self, async_client: AsyncCourier) -> None:
+ broadcast = await async_client.broadcasts.cancel(
+ "broadcastId",
+ )
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_cancel(self, async_client: AsyncCourier) -> None:
+ response = await async_client.broadcasts.with_raw_response.cancel(
+ "broadcastId",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ broadcast = await response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_cancel(self, async_client: AsyncCourier) -> None:
+ async with async_client.broadcasts.with_streaming_response.cancel(
+ "broadcastId",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ broadcast = await response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_cancel(self, async_client: AsyncCourier) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `broadcast_id` but received ''"):
+ await async_client.broadcasts.with_raw_response.cancel(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_duplicate(self, async_client: AsyncCourier) -> None:
+ broadcast = await async_client.broadcasts.duplicate(
+ "broadcastId",
+ )
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_duplicate(self, async_client: AsyncCourier) -> None:
+ response = await async_client.broadcasts.with_raw_response.duplicate(
+ "broadcastId",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ broadcast = await response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_duplicate(self, async_client: AsyncCourier) -> None:
+ async with async_client.broadcasts.with_streaming_response.duplicate(
+ "broadcastId",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ broadcast = await response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_duplicate(self, async_client: AsyncCourier) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `broadcast_id` but received ''"):
+ await async_client.broadcasts.with_raw_response.duplicate(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_put_content(self, async_client: AsyncCourier) -> None:
+ broadcast = await async_client.broadcasts.put_content(
+ broadcast_id="broadcastId",
+ content={"elements": [{}, {}]},
+ )
+ assert_matches_type(NotificationContentMutationResponse, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_put_content_with_all_params(self, async_client: AsyncCourier) -> None:
+ broadcast = await async_client.broadcasts.put_content(
+ broadcast_id="broadcastId",
+ content={
+ "elements": [
+ {
+ "channels": ["string"],
+ "if": "if",
+ "loop": "loop",
+ "ref": "ref",
+ "type": "meta",
+ },
+ {
+ "channels": ["string"],
+ "if": "if",
+ "loop": "loop",
+ "ref": "ref",
+ "type": "text",
+ },
+ ],
+ "version": "2022-01-01",
+ },
+ state="DRAFT",
+ )
+ assert_matches_type(NotificationContentMutationResponse, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_put_content(self, async_client: AsyncCourier) -> None:
+ response = await async_client.broadcasts.with_raw_response.put_content(
+ broadcast_id="broadcastId",
+ content={"elements": [{}, {}]},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ broadcast = await response.parse()
+ assert_matches_type(NotificationContentMutationResponse, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_put_content(self, async_client: AsyncCourier) -> None:
+ async with async_client.broadcasts.with_streaming_response.put_content(
+ broadcast_id="broadcastId",
+ content={"elements": [{}, {}]},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ broadcast = await response.parse()
+ assert_matches_type(NotificationContentMutationResponse, broadcast, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_put_content(self, async_client: AsyncCourier) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `broadcast_id` but received ''"):
+ await async_client.broadcasts.with_raw_response.put_content(
+ broadcast_id="",
+ content={"elements": [{}, {}]},
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_retrieve_content(self, async_client: AsyncCourier) -> None:
+ broadcast = await async_client.broadcasts.retrieve_content(
+ broadcast_id="broadcastId",
+ )
+ assert_matches_type(NotificationContentGetResponse, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_retrieve_content_with_all_params(self, async_client: AsyncCourier) -> None:
+ broadcast = await async_client.broadcasts.retrieve_content(
+ broadcast_id="broadcastId",
+ version="version",
+ )
+ assert_matches_type(NotificationContentGetResponse, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_retrieve_content(self, async_client: AsyncCourier) -> None:
+ response = await async_client.broadcasts.with_raw_response.retrieve_content(
+ broadcast_id="broadcastId",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ broadcast = await response.parse()
+ assert_matches_type(NotificationContentGetResponse, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_retrieve_content(self, async_client: AsyncCourier) -> None:
+ async with async_client.broadcasts.with_streaming_response.retrieve_content(
+ broadcast_id="broadcastId",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ broadcast = await response.parse()
+ assert_matches_type(NotificationContentGetResponse, broadcast, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_retrieve_content(self, async_client: AsyncCourier) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `broadcast_id` but received ''"):
+ await async_client.broadcasts.with_raw_response.retrieve_content(
+ broadcast_id="",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_schedule(self, async_client: AsyncCourier) -> None:
+ broadcast = await async_client.broadcasts.schedule(
+ broadcast_id="broadcastId",
+ recipient_id="aud_01kx4h2jdafq8bk9amzvy6hbv0",
+ recipient_type="audience",
+ scheduled_to="2026-08-01T15:00:00",
+ )
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_schedule_with_all_params(self, async_client: AsyncCourier) -> None:
+ broadcast = await async_client.broadcasts.schedule(
+ broadcast_id="broadcastId",
+ recipient_id="aud_01kx4h2jdafq8bk9amzvy6hbv0",
+ recipient_type="audience",
+ scheduled_to="2026-08-01T15:00:00",
+ timezone="America/New_York",
+ )
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_schedule(self, async_client: AsyncCourier) -> None:
+ response = await async_client.broadcasts.with_raw_response.schedule(
+ broadcast_id="broadcastId",
+ recipient_id="aud_01kx4h2jdafq8bk9amzvy6hbv0",
+ recipient_type="audience",
+ scheduled_to="2026-08-01T15:00:00",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ broadcast = await response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_schedule(self, async_client: AsyncCourier) -> None:
+ async with async_client.broadcasts.with_streaming_response.schedule(
+ broadcast_id="broadcastId",
+ recipient_id="aud_01kx4h2jdafq8bk9amzvy6hbv0",
+ recipient_type="audience",
+ scheduled_to="2026-08-01T15:00:00",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ broadcast = await response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_schedule(self, async_client: AsyncCourier) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `broadcast_id` but received ''"):
+ await async_client.broadcasts.with_raw_response.schedule(
+ broadcast_id="",
+ recipient_id="aud_01kx4h2jdafq8bk9amzvy6hbv0",
+ recipient_type="audience",
+ scheduled_to="2026-08-01T15:00:00",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_send(self, async_client: AsyncCourier) -> None:
+ broadcast = await async_client.broadcasts.send(
+ broadcast_id="broadcastId",
+ recipient_id="cool-customers",
+ recipient_type="list",
+ )
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_send(self, async_client: AsyncCourier) -> None:
+ response = await async_client.broadcasts.with_raw_response.send(
+ broadcast_id="broadcastId",
+ recipient_id="cool-customers",
+ recipient_type="list",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ broadcast = await response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_send(self, async_client: AsyncCourier) -> None:
+ async with async_client.broadcasts.with_streaming_response.send(
+ broadcast_id="broadcastId",
+ recipient_id="cool-customers",
+ recipient_type="list",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ broadcast = await response.parse()
+ assert_matches_type(Broadcast, broadcast, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_send(self, async_client: AsyncCourier) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `broadcast_id` but received ''"):
+ await async_client.broadcasts.with_raw_response.send(
+ broadcast_id="",
+ recipient_id="cool-customers",
+ recipient_type="list",
+ )