diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 8696062a..afe4ebfd 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "7.21.0"
+ ".": "7.22.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index e2794c72..cf69b295 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 134
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/courier/courier-b053468fefe6d757f86f3233ebbc52d80329ed2a6e7a6740fee01e4028a4c3b9.yml
-openapi_spec_hash: 416835e693de0fe19945be90a787d3f3
-config_hash: 8d28dbeabe9d4dcc7d5b8c021a4cbbd7
+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
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 55b22b49..468bcb90 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,21 @@
# Changelog
+## 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)
+
+### Features
+
+* Document DELETE/PUT restore for inbox messages (C-19268) ([#175](https://github.com/trycourier/courier-python/issues/175)) ([6e32a5c](https://github.com/trycourier/courier-python/commit/6e32a5cb55d541ba89ac260aa62fa0d1d40505d9))
+* spec: rename and reorder the API reference sections ([#179](https://github.com/trycourier/courier-python/issues/179)) ([742efe6](https://github.com/trycourier/courier-python/commit/742efe6045de6ae33afeb780205ea7010d93a98b))
+
+
+### Documentation
+
+* **openapi:** describe user topic-preference fields explicitly ([#172](https://github.com/trycourier/courier-python/issues/172)) ([842699a](https://github.com/trycourier/courier-python/commit/842699a68811d63aca9ef8d8865266623ff3e42c))
+* **openapi:** document Idempotency-Key header on idempotent POST endpoints ([#176](https://github.com/trycourier/courier-python/issues/176)) ([9209315](https://github.com/trycourier/courier-python/commit/92093156c03c0c8a56e7a43fe41b4d4490fcc7b2))
+* **openapi:** rewrite operation descriptions for agents and SEO ([#174](https://github.com/trycourier/courier-python/issues/174)) ([c90b5d3](https://github.com/trycourier/courier-python/commit/c90b5d325da4e542a46a089084205ce145d65ef1))
+
## 7.21.0 (2026-07-23)
Full Changelog: [v7.20.0...v7.21.0](https://github.com/trycourier/courier-python/compare/v7.20.0...v7.21.0)
diff --git a/api.md b/api.md
index 160903ed..17836458 100644
--- a/api.md
+++ b/api.md
@@ -355,6 +355,15 @@ Methods:
- client.lists.subscriptions.subscribe_user(user_id, \*, list_id, \*\*params) -> None
- client.lists.subscriptions.unsubscribe_user(user_id, \*, list_id) -> None
+# Inbox
+
+## Messages
+
+Methods:
+
+- client.inbox.messages.delete(message_id) -> None
+- client.inbox.messages.restore(message_id) -> None
+
# Messages
Types:
diff --git a/pyproject.toml b/pyproject.toml
index caddccb4..55734103 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "trycourier"
-version = "7.21.0"
+version = "7.22.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 8ab22078..1e024eb6 100644
--- a/src/courier/_client.py
+++ b/src/courier/_client.py
@@ -38,6 +38,7 @@
from .resources import (
auth,
send,
+ inbox,
lists,
users,
brands,
@@ -64,6 +65,7 @@
from .resources.messages import MessagesResource, AsyncMessagesResource
from .resources.requests import RequestsResource, AsyncRequestsResource
from .resources.audiences import AudiencesResource, AsyncAudiencesResource
+ from .resources.inbox.inbox import InboxResource, AsyncInboxResource
from .resources.lists.lists import ListsResource, AsyncListsResource
from .resources.users.users import UsersResource, AsyncUsersResource
from .resources.audit_events import AuditEventsResource, AsyncAuditEventsResource
@@ -150,48 +152,70 @@ def __init__(
@cached_property
def send(self) -> SendResource:
+ """
+ Send a message to one or more recipients — users, lists, audiences, or tenants — across every channel you have configured.
+ """
from .resources.send import SendResource
return SendResource(self)
@cached_property
def audiences(self) -> AudiencesResource:
+ """
+ Define filter-based groups whose membership Courier recalculates as user profiles change.
+ """
from .resources.audiences import AudiencesResource
return AudiencesResource(self)
@cached_property
def providers(self) -> ProvidersResource:
+ """
+ Configure the channel providers Courier delivers through, and browse the provider types it supports.
+ """
from .resources.providers import ProvidersResource
return ProvidersResource(self)
@cached_property
def audit_events(self) -> AuditEventsResource:
+ """Read the audit trail of configuration and access changes in your workspace."""
from .resources.audit_events import AuditEventsResource
return AuditEventsResource(self)
@cached_property
def auth(self) -> AuthResource:
+ """
+ Issue scoped, short-lived JWTs so client-side SDKs — Inbox, Preferences, and the embedded designer — can call Courier as a single user. Server-side requests authenticate with your workspace API key instead.
+ """
from .resources.auth import AuthResource
return AuthResource(self)
@cached_property
def automations(self) -> AutomationsResource:
+ """
+ Invoke a stored automation template or an ad hoc automation defined in the request.
+ """
from .resources.automations import AutomationsResource
return AutomationsResource(self)
@cached_property
def journeys(self) -> JourneysResource:
+ """
+ Build, version, publish, invoke, and cancel multi-step notification workflows, along with the templates scoped to them.
+ """
from .resources.journeys import JourneysResource
return JourneysResource(self)
@cached_property
def brands(self) -> BrandsResource:
+ """
+ Manage the logos, colors, and layout that give the templates you send a consistent look.
+ """
from .resources.brands import BrandsResource
return BrandsResource(self)
@@ -204,60 +228,96 @@ def digests(self) -> DigestsResource:
@cached_property
def inbound(self) -> InboundResource:
+ """
+ Record an inbound event that triggers the journeys and automations mapped to it.
+ """
from .resources.inbound import InboundResource
return InboundResource(self)
@cached_property
def lists(self) -> ListsResource:
+ """
+ Manage static groups of users that you subscribe explicitly, and send to them by list id or list pattern.
+ """
from .resources.lists import ListsResource
return ListsResource(self)
+ @cached_property
+ def inbox(self) -> InboxResource:
+ from .resources.inbox import InboxResource
+
+ return InboxResource(self)
+
@cached_property
def messages(self) -> MessagesResource:
+ """
+ Look up the messages Courier has accepted, inspect their delivery history and rendered output, and cancel, resend, or archive them.
+ """
from .resources.messages import MessagesResource
return MessagesResource(self)
@cached_property
def requests(self) -> RequestsResource:
+ """
+ Look up the messages Courier has accepted, inspect their delivery history and rendered output, and cancel, resend, or archive them.
+ """
from .resources.requests import RequestsResource
return RequestsResource(self)
@cached_property
def notifications(self) -> NotificationsResource:
+ """
+ Create, update, version, publish, and localize notification templates and their content.
+ """
from .resources.notifications import NotificationsResource
return NotificationsResource(self)
@cached_property
def routing_strategies(self) -> RoutingStrategiesResource:
+ """
+ Define reusable channel routing and failover strategies, and see which templates use them.
+ """
from .resources.routing_strategies import RoutingStrategiesResource
return RoutingStrategiesResource(self)
@cached_property
def workspace_preferences(self) -> WorkspacePreferencesResource:
+ """
+ Manage the workspace catalog of subscription topics, the sections that group them, and publishing the preference page.
+ """
from .resources.workspace_preferences import WorkspacePreferencesResource
return WorkspacePreferencesResource(self)
@cached_property
def profiles(self) -> ProfilesResource:
+ """
+ Store the contact information Courier delivers to for each user — email, phone number, push tokens, and any custom data you send to.
+ """
from .resources.profiles import ProfilesResource
return ProfilesResource(self)
@cached_property
def tenants(self) -> TenantsResource:
+ """
+ Manage tenants — the organizations, teams, or accounts your users belong to — along with their users and default preferences.
+ """
from .resources.tenants import TenantsResource
return TenantsResource(self)
@cached_property
def translations(self) -> TranslationsResource:
+ """
+ Store and retrieve the translation strings Courier uses to render localized template content.
+ """
from .resources.translations import TranslationsResource
return TranslationsResource(self)
@@ -447,48 +507,70 @@ def __init__(
@cached_property
def send(self) -> AsyncSendResource:
+ """
+ Send a message to one or more recipients — users, lists, audiences, or tenants — across every channel you have configured.
+ """
from .resources.send import AsyncSendResource
return AsyncSendResource(self)
@cached_property
def audiences(self) -> AsyncAudiencesResource:
+ """
+ Define filter-based groups whose membership Courier recalculates as user profiles change.
+ """
from .resources.audiences import AsyncAudiencesResource
return AsyncAudiencesResource(self)
@cached_property
def providers(self) -> AsyncProvidersResource:
+ """
+ Configure the channel providers Courier delivers through, and browse the provider types it supports.
+ """
from .resources.providers import AsyncProvidersResource
return AsyncProvidersResource(self)
@cached_property
def audit_events(self) -> AsyncAuditEventsResource:
+ """Read the audit trail of configuration and access changes in your workspace."""
from .resources.audit_events import AsyncAuditEventsResource
return AsyncAuditEventsResource(self)
@cached_property
def auth(self) -> AsyncAuthResource:
+ """
+ Issue scoped, short-lived JWTs so client-side SDKs — Inbox, Preferences, and the embedded designer — can call Courier as a single user. Server-side requests authenticate with your workspace API key instead.
+ """
from .resources.auth import AsyncAuthResource
return AsyncAuthResource(self)
@cached_property
def automations(self) -> AsyncAutomationsResource:
+ """
+ Invoke a stored automation template or an ad hoc automation defined in the request.
+ """
from .resources.automations import AsyncAutomationsResource
return AsyncAutomationsResource(self)
@cached_property
def journeys(self) -> AsyncJourneysResource:
+ """
+ Build, version, publish, invoke, and cancel multi-step notification workflows, along with the templates scoped to them.
+ """
from .resources.journeys import AsyncJourneysResource
return AsyncJourneysResource(self)
@cached_property
def brands(self) -> AsyncBrandsResource:
+ """
+ Manage the logos, colors, and layout that give the templates you send a consistent look.
+ """
from .resources.brands import AsyncBrandsResource
return AsyncBrandsResource(self)
@@ -501,60 +583,96 @@ def digests(self) -> AsyncDigestsResource:
@cached_property
def inbound(self) -> AsyncInboundResource:
+ """
+ Record an inbound event that triggers the journeys and automations mapped to it.
+ """
from .resources.inbound import AsyncInboundResource
return AsyncInboundResource(self)
@cached_property
def lists(self) -> AsyncListsResource:
+ """
+ Manage static groups of users that you subscribe explicitly, and send to them by list id or list pattern.
+ """
from .resources.lists import AsyncListsResource
return AsyncListsResource(self)
+ @cached_property
+ def inbox(self) -> AsyncInboxResource:
+ from .resources.inbox import AsyncInboxResource
+
+ return AsyncInboxResource(self)
+
@cached_property
def messages(self) -> AsyncMessagesResource:
+ """
+ Look up the messages Courier has accepted, inspect their delivery history and rendered output, and cancel, resend, or archive them.
+ """
from .resources.messages import AsyncMessagesResource
return AsyncMessagesResource(self)
@cached_property
def requests(self) -> AsyncRequestsResource:
+ """
+ Look up the messages Courier has accepted, inspect their delivery history and rendered output, and cancel, resend, or archive them.
+ """
from .resources.requests import AsyncRequestsResource
return AsyncRequestsResource(self)
@cached_property
def notifications(self) -> AsyncNotificationsResource:
+ """
+ Create, update, version, publish, and localize notification templates and their content.
+ """
from .resources.notifications import AsyncNotificationsResource
return AsyncNotificationsResource(self)
@cached_property
def routing_strategies(self) -> AsyncRoutingStrategiesResource:
+ """
+ Define reusable channel routing and failover strategies, and see which templates use them.
+ """
from .resources.routing_strategies import AsyncRoutingStrategiesResource
return AsyncRoutingStrategiesResource(self)
@cached_property
def workspace_preferences(self) -> AsyncWorkspacePreferencesResource:
+ """
+ Manage the workspace catalog of subscription topics, the sections that group them, and publishing the preference page.
+ """
from .resources.workspace_preferences import AsyncWorkspacePreferencesResource
return AsyncWorkspacePreferencesResource(self)
@cached_property
def profiles(self) -> AsyncProfilesResource:
+ """
+ Store the contact information Courier delivers to for each user — email, phone number, push tokens, and any custom data you send to.
+ """
from .resources.profiles import AsyncProfilesResource
return AsyncProfilesResource(self)
@cached_property
def tenants(self) -> AsyncTenantsResource:
+ """
+ Manage tenants — the organizations, teams, or accounts your users belong to — along with their users and default preferences.
+ """
from .resources.tenants import AsyncTenantsResource
return AsyncTenantsResource(self)
@cached_property
def translations(self) -> AsyncTranslationsResource:
+ """
+ Store and retrieve the translation strings Courier uses to render localized template content.
+ """
from .resources.translations import AsyncTranslationsResource
return AsyncTranslationsResource(self)
@@ -686,48 +804,70 @@ def __init__(self, client: Courier) -> None:
@cached_property
def send(self) -> send.SendResourceWithRawResponse:
+ """
+ Send a message to one or more recipients — users, lists, audiences, or tenants — across every channel you have configured.
+ """
from .resources.send import SendResourceWithRawResponse
return SendResourceWithRawResponse(self._client.send)
@cached_property
def audiences(self) -> audiences.AudiencesResourceWithRawResponse:
+ """
+ Define filter-based groups whose membership Courier recalculates as user profiles change.
+ """
from .resources.audiences import AudiencesResourceWithRawResponse
return AudiencesResourceWithRawResponse(self._client.audiences)
@cached_property
def providers(self) -> providers.ProvidersResourceWithRawResponse:
+ """
+ Configure the channel providers Courier delivers through, and browse the provider types it supports.
+ """
from .resources.providers import ProvidersResourceWithRawResponse
return ProvidersResourceWithRawResponse(self._client.providers)
@cached_property
def audit_events(self) -> audit_events.AuditEventsResourceWithRawResponse:
+ """Read the audit trail of configuration and access changes in your workspace."""
from .resources.audit_events import AuditEventsResourceWithRawResponse
return AuditEventsResourceWithRawResponse(self._client.audit_events)
@cached_property
def auth(self) -> auth.AuthResourceWithRawResponse:
+ """
+ Issue scoped, short-lived JWTs so client-side SDKs — Inbox, Preferences, and the embedded designer — can call Courier as a single user. Server-side requests authenticate with your workspace API key instead.
+ """
from .resources.auth import AuthResourceWithRawResponse
return AuthResourceWithRawResponse(self._client.auth)
@cached_property
def automations(self) -> automations.AutomationsResourceWithRawResponse:
+ """
+ Invoke a stored automation template or an ad hoc automation defined in the request.
+ """
from .resources.automations import AutomationsResourceWithRawResponse
return AutomationsResourceWithRawResponse(self._client.automations)
@cached_property
def journeys(self) -> journeys.JourneysResourceWithRawResponse:
+ """
+ Build, version, publish, invoke, and cancel multi-step notification workflows, along with the templates scoped to them.
+ """
from .resources.journeys import JourneysResourceWithRawResponse
return JourneysResourceWithRawResponse(self._client.journeys)
@cached_property
def brands(self) -> brands.BrandsResourceWithRawResponse:
+ """
+ Manage the logos, colors, and layout that give the templates you send a consistent look.
+ """
from .resources.brands import BrandsResourceWithRawResponse
return BrandsResourceWithRawResponse(self._client.brands)
@@ -740,60 +880,96 @@ def digests(self) -> digests.DigestsResourceWithRawResponse:
@cached_property
def inbound(self) -> inbound.InboundResourceWithRawResponse:
+ """
+ Record an inbound event that triggers the journeys and automations mapped to it.
+ """
from .resources.inbound import InboundResourceWithRawResponse
return InboundResourceWithRawResponse(self._client.inbound)
@cached_property
def lists(self) -> lists.ListsResourceWithRawResponse:
+ """
+ Manage static groups of users that you subscribe explicitly, and send to them by list id or list pattern.
+ """
from .resources.lists import ListsResourceWithRawResponse
return ListsResourceWithRawResponse(self._client.lists)
+ @cached_property
+ def inbox(self) -> inbox.InboxResourceWithRawResponse:
+ from .resources.inbox import InboxResourceWithRawResponse
+
+ return InboxResourceWithRawResponse(self._client.inbox)
+
@cached_property
def messages(self) -> messages.MessagesResourceWithRawResponse:
+ """
+ Look up the messages Courier has accepted, inspect their delivery history and rendered output, and cancel, resend, or archive them.
+ """
from .resources.messages import MessagesResourceWithRawResponse
return MessagesResourceWithRawResponse(self._client.messages)
@cached_property
def requests(self) -> requests.RequestsResourceWithRawResponse:
+ """
+ Look up the messages Courier has accepted, inspect their delivery history and rendered output, and cancel, resend, or archive them.
+ """
from .resources.requests import RequestsResourceWithRawResponse
return RequestsResourceWithRawResponse(self._client.requests)
@cached_property
def notifications(self) -> notifications.NotificationsResourceWithRawResponse:
+ """
+ Create, update, version, publish, and localize notification templates and their content.
+ """
from .resources.notifications import NotificationsResourceWithRawResponse
return NotificationsResourceWithRawResponse(self._client.notifications)
@cached_property
def routing_strategies(self) -> routing_strategies.RoutingStrategiesResourceWithRawResponse:
+ """
+ Define reusable channel routing and failover strategies, and see which templates use them.
+ """
from .resources.routing_strategies import RoutingStrategiesResourceWithRawResponse
return RoutingStrategiesResourceWithRawResponse(self._client.routing_strategies)
@cached_property
def workspace_preferences(self) -> workspace_preferences.WorkspacePreferencesResourceWithRawResponse:
+ """
+ Manage the workspace catalog of subscription topics, the sections that group them, and publishing the preference page.
+ """
from .resources.workspace_preferences import WorkspacePreferencesResourceWithRawResponse
return WorkspacePreferencesResourceWithRawResponse(self._client.workspace_preferences)
@cached_property
def profiles(self) -> profiles.ProfilesResourceWithRawResponse:
+ """
+ Store the contact information Courier delivers to for each user — email, phone number, push tokens, and any custom data you send to.
+ """
from .resources.profiles import ProfilesResourceWithRawResponse
return ProfilesResourceWithRawResponse(self._client.profiles)
@cached_property
def tenants(self) -> tenants.TenantsResourceWithRawResponse:
+ """
+ Manage tenants — the organizations, teams, or accounts your users belong to — along with their users and default preferences.
+ """
from .resources.tenants import TenantsResourceWithRawResponse
return TenantsResourceWithRawResponse(self._client.tenants)
@cached_property
def translations(self) -> translations.TranslationsResourceWithRawResponse:
+ """
+ Store and retrieve the translation strings Courier uses to render localized template content.
+ """
from .resources.translations import TranslationsResourceWithRawResponse
return TranslationsResourceWithRawResponse(self._client.translations)
@@ -813,48 +989,70 @@ def __init__(self, client: AsyncCourier) -> None:
@cached_property
def send(self) -> send.AsyncSendResourceWithRawResponse:
+ """
+ Send a message to one or more recipients — users, lists, audiences, or tenants — across every channel you have configured.
+ """
from .resources.send import AsyncSendResourceWithRawResponse
return AsyncSendResourceWithRawResponse(self._client.send)
@cached_property
def audiences(self) -> audiences.AsyncAudiencesResourceWithRawResponse:
+ """
+ Define filter-based groups whose membership Courier recalculates as user profiles change.
+ """
from .resources.audiences import AsyncAudiencesResourceWithRawResponse
return AsyncAudiencesResourceWithRawResponse(self._client.audiences)
@cached_property
def providers(self) -> providers.AsyncProvidersResourceWithRawResponse:
+ """
+ Configure the channel providers Courier delivers through, and browse the provider types it supports.
+ """
from .resources.providers import AsyncProvidersResourceWithRawResponse
return AsyncProvidersResourceWithRawResponse(self._client.providers)
@cached_property
def audit_events(self) -> audit_events.AsyncAuditEventsResourceWithRawResponse:
+ """Read the audit trail of configuration and access changes in your workspace."""
from .resources.audit_events import AsyncAuditEventsResourceWithRawResponse
return AsyncAuditEventsResourceWithRawResponse(self._client.audit_events)
@cached_property
def auth(self) -> auth.AsyncAuthResourceWithRawResponse:
+ """
+ Issue scoped, short-lived JWTs so client-side SDKs — Inbox, Preferences, and the embedded designer — can call Courier as a single user. Server-side requests authenticate with your workspace API key instead.
+ """
from .resources.auth import AsyncAuthResourceWithRawResponse
return AsyncAuthResourceWithRawResponse(self._client.auth)
@cached_property
def automations(self) -> automations.AsyncAutomationsResourceWithRawResponse:
+ """
+ Invoke a stored automation template or an ad hoc automation defined in the request.
+ """
from .resources.automations import AsyncAutomationsResourceWithRawResponse
return AsyncAutomationsResourceWithRawResponse(self._client.automations)
@cached_property
def journeys(self) -> journeys.AsyncJourneysResourceWithRawResponse:
+ """
+ Build, version, publish, invoke, and cancel multi-step notification workflows, along with the templates scoped to them.
+ """
from .resources.journeys import AsyncJourneysResourceWithRawResponse
return AsyncJourneysResourceWithRawResponse(self._client.journeys)
@cached_property
def brands(self) -> brands.AsyncBrandsResourceWithRawResponse:
+ """
+ Manage the logos, colors, and layout that give the templates you send a consistent look.
+ """
from .resources.brands import AsyncBrandsResourceWithRawResponse
return AsyncBrandsResourceWithRawResponse(self._client.brands)
@@ -867,60 +1065,96 @@ def digests(self) -> digests.AsyncDigestsResourceWithRawResponse:
@cached_property
def inbound(self) -> inbound.AsyncInboundResourceWithRawResponse:
+ """
+ Record an inbound event that triggers the journeys and automations mapped to it.
+ """
from .resources.inbound import AsyncInboundResourceWithRawResponse
return AsyncInboundResourceWithRawResponse(self._client.inbound)
@cached_property
def lists(self) -> lists.AsyncListsResourceWithRawResponse:
+ """
+ Manage static groups of users that you subscribe explicitly, and send to them by list id or list pattern.
+ """
from .resources.lists import AsyncListsResourceWithRawResponse
return AsyncListsResourceWithRawResponse(self._client.lists)
+ @cached_property
+ def inbox(self) -> inbox.AsyncInboxResourceWithRawResponse:
+ from .resources.inbox import AsyncInboxResourceWithRawResponse
+
+ return AsyncInboxResourceWithRawResponse(self._client.inbox)
+
@cached_property
def messages(self) -> messages.AsyncMessagesResourceWithRawResponse:
+ """
+ Look up the messages Courier has accepted, inspect their delivery history and rendered output, and cancel, resend, or archive them.
+ """
from .resources.messages import AsyncMessagesResourceWithRawResponse
return AsyncMessagesResourceWithRawResponse(self._client.messages)
@cached_property
def requests(self) -> requests.AsyncRequestsResourceWithRawResponse:
+ """
+ Look up the messages Courier has accepted, inspect their delivery history and rendered output, and cancel, resend, or archive them.
+ """
from .resources.requests import AsyncRequestsResourceWithRawResponse
return AsyncRequestsResourceWithRawResponse(self._client.requests)
@cached_property
def notifications(self) -> notifications.AsyncNotificationsResourceWithRawResponse:
+ """
+ Create, update, version, publish, and localize notification templates and their content.
+ """
from .resources.notifications import AsyncNotificationsResourceWithRawResponse
return AsyncNotificationsResourceWithRawResponse(self._client.notifications)
@cached_property
def routing_strategies(self) -> routing_strategies.AsyncRoutingStrategiesResourceWithRawResponse:
+ """
+ Define reusable channel routing and failover strategies, and see which templates use them.
+ """
from .resources.routing_strategies import AsyncRoutingStrategiesResourceWithRawResponse
return AsyncRoutingStrategiesResourceWithRawResponse(self._client.routing_strategies)
@cached_property
def workspace_preferences(self) -> workspace_preferences.AsyncWorkspacePreferencesResourceWithRawResponse:
+ """
+ Manage the workspace catalog of subscription topics, the sections that group them, and publishing the preference page.
+ """
from .resources.workspace_preferences import AsyncWorkspacePreferencesResourceWithRawResponse
return AsyncWorkspacePreferencesResourceWithRawResponse(self._client.workspace_preferences)
@cached_property
def profiles(self) -> profiles.AsyncProfilesResourceWithRawResponse:
+ """
+ Store the contact information Courier delivers to for each user — email, phone number, push tokens, and any custom data you send to.
+ """
from .resources.profiles import AsyncProfilesResourceWithRawResponse
return AsyncProfilesResourceWithRawResponse(self._client.profiles)
@cached_property
def tenants(self) -> tenants.AsyncTenantsResourceWithRawResponse:
+ """
+ Manage tenants — the organizations, teams, or accounts your users belong to — along with their users and default preferences.
+ """
from .resources.tenants import AsyncTenantsResourceWithRawResponse
return AsyncTenantsResourceWithRawResponse(self._client.tenants)
@cached_property
def translations(self) -> translations.AsyncTranslationsResourceWithRawResponse:
+ """
+ Store and retrieve the translation strings Courier uses to render localized template content.
+ """
from .resources.translations import AsyncTranslationsResourceWithRawResponse
return AsyncTranslationsResourceWithRawResponse(self._client.translations)
@@ -940,48 +1174,70 @@ def __init__(self, client: Courier) -> None:
@cached_property
def send(self) -> send.SendResourceWithStreamingResponse:
+ """
+ Send a message to one or more recipients — users, lists, audiences, or tenants — across every channel you have configured.
+ """
from .resources.send import SendResourceWithStreamingResponse
return SendResourceWithStreamingResponse(self._client.send)
@cached_property
def audiences(self) -> audiences.AudiencesResourceWithStreamingResponse:
+ """
+ Define filter-based groups whose membership Courier recalculates as user profiles change.
+ """
from .resources.audiences import AudiencesResourceWithStreamingResponse
return AudiencesResourceWithStreamingResponse(self._client.audiences)
@cached_property
def providers(self) -> providers.ProvidersResourceWithStreamingResponse:
+ """
+ Configure the channel providers Courier delivers through, and browse the provider types it supports.
+ """
from .resources.providers import ProvidersResourceWithStreamingResponse
return ProvidersResourceWithStreamingResponse(self._client.providers)
@cached_property
def audit_events(self) -> audit_events.AuditEventsResourceWithStreamingResponse:
+ """Read the audit trail of configuration and access changes in your workspace."""
from .resources.audit_events import AuditEventsResourceWithStreamingResponse
return AuditEventsResourceWithStreamingResponse(self._client.audit_events)
@cached_property
def auth(self) -> auth.AuthResourceWithStreamingResponse:
+ """
+ Issue scoped, short-lived JWTs so client-side SDKs — Inbox, Preferences, and the embedded designer — can call Courier as a single user. Server-side requests authenticate with your workspace API key instead.
+ """
from .resources.auth import AuthResourceWithStreamingResponse
return AuthResourceWithStreamingResponse(self._client.auth)
@cached_property
def automations(self) -> automations.AutomationsResourceWithStreamingResponse:
+ """
+ Invoke a stored automation template or an ad hoc automation defined in the request.
+ """
from .resources.automations import AutomationsResourceWithStreamingResponse
return AutomationsResourceWithStreamingResponse(self._client.automations)
@cached_property
def journeys(self) -> journeys.JourneysResourceWithStreamingResponse:
+ """
+ Build, version, publish, invoke, and cancel multi-step notification workflows, along with the templates scoped to them.
+ """
from .resources.journeys import JourneysResourceWithStreamingResponse
return JourneysResourceWithStreamingResponse(self._client.journeys)
@cached_property
def brands(self) -> brands.BrandsResourceWithStreamingResponse:
+ """
+ Manage the logos, colors, and layout that give the templates you send a consistent look.
+ """
from .resources.brands import BrandsResourceWithStreamingResponse
return BrandsResourceWithStreamingResponse(self._client.brands)
@@ -994,60 +1250,96 @@ def digests(self) -> digests.DigestsResourceWithStreamingResponse:
@cached_property
def inbound(self) -> inbound.InboundResourceWithStreamingResponse:
+ """
+ Record an inbound event that triggers the journeys and automations mapped to it.
+ """
from .resources.inbound import InboundResourceWithStreamingResponse
return InboundResourceWithStreamingResponse(self._client.inbound)
@cached_property
def lists(self) -> lists.ListsResourceWithStreamingResponse:
+ """
+ Manage static groups of users that you subscribe explicitly, and send to them by list id or list pattern.
+ """
from .resources.lists import ListsResourceWithStreamingResponse
return ListsResourceWithStreamingResponse(self._client.lists)
+ @cached_property
+ def inbox(self) -> inbox.InboxResourceWithStreamingResponse:
+ from .resources.inbox import InboxResourceWithStreamingResponse
+
+ return InboxResourceWithStreamingResponse(self._client.inbox)
+
@cached_property
def messages(self) -> messages.MessagesResourceWithStreamingResponse:
+ """
+ Look up the messages Courier has accepted, inspect their delivery history and rendered output, and cancel, resend, or archive them.
+ """
from .resources.messages import MessagesResourceWithStreamingResponse
return MessagesResourceWithStreamingResponse(self._client.messages)
@cached_property
def requests(self) -> requests.RequestsResourceWithStreamingResponse:
+ """
+ Look up the messages Courier has accepted, inspect their delivery history and rendered output, and cancel, resend, or archive them.
+ """
from .resources.requests import RequestsResourceWithStreamingResponse
return RequestsResourceWithStreamingResponse(self._client.requests)
@cached_property
def notifications(self) -> notifications.NotificationsResourceWithStreamingResponse:
+ """
+ Create, update, version, publish, and localize notification templates and their content.
+ """
from .resources.notifications import NotificationsResourceWithStreamingResponse
return NotificationsResourceWithStreamingResponse(self._client.notifications)
@cached_property
def routing_strategies(self) -> routing_strategies.RoutingStrategiesResourceWithStreamingResponse:
+ """
+ Define reusable channel routing and failover strategies, and see which templates use them.
+ """
from .resources.routing_strategies import RoutingStrategiesResourceWithStreamingResponse
return RoutingStrategiesResourceWithStreamingResponse(self._client.routing_strategies)
@cached_property
def workspace_preferences(self) -> workspace_preferences.WorkspacePreferencesResourceWithStreamingResponse:
+ """
+ Manage the workspace catalog of subscription topics, the sections that group them, and publishing the preference page.
+ """
from .resources.workspace_preferences import WorkspacePreferencesResourceWithStreamingResponse
return WorkspacePreferencesResourceWithStreamingResponse(self._client.workspace_preferences)
@cached_property
def profiles(self) -> profiles.ProfilesResourceWithStreamingResponse:
+ """
+ Store the contact information Courier delivers to for each user — email, phone number, push tokens, and any custom data you send to.
+ """
from .resources.profiles import ProfilesResourceWithStreamingResponse
return ProfilesResourceWithStreamingResponse(self._client.profiles)
@cached_property
def tenants(self) -> tenants.TenantsResourceWithStreamingResponse:
+ """
+ Manage tenants — the organizations, teams, or accounts your users belong to — along with their users and default preferences.
+ """
from .resources.tenants import TenantsResourceWithStreamingResponse
return TenantsResourceWithStreamingResponse(self._client.tenants)
@cached_property
def translations(self) -> translations.TranslationsResourceWithStreamingResponse:
+ """
+ Store and retrieve the translation strings Courier uses to render localized template content.
+ """
from .resources.translations import TranslationsResourceWithStreamingResponse
return TranslationsResourceWithStreamingResponse(self._client.translations)
@@ -1067,48 +1359,70 @@ def __init__(self, client: AsyncCourier) -> None:
@cached_property
def send(self) -> send.AsyncSendResourceWithStreamingResponse:
+ """
+ Send a message to one or more recipients — users, lists, audiences, or tenants — across every channel you have configured.
+ """
from .resources.send import AsyncSendResourceWithStreamingResponse
return AsyncSendResourceWithStreamingResponse(self._client.send)
@cached_property
def audiences(self) -> audiences.AsyncAudiencesResourceWithStreamingResponse:
+ """
+ Define filter-based groups whose membership Courier recalculates as user profiles change.
+ """
from .resources.audiences import AsyncAudiencesResourceWithStreamingResponse
return AsyncAudiencesResourceWithStreamingResponse(self._client.audiences)
@cached_property
def providers(self) -> providers.AsyncProvidersResourceWithStreamingResponse:
+ """
+ Configure the channel providers Courier delivers through, and browse the provider types it supports.
+ """
from .resources.providers import AsyncProvidersResourceWithStreamingResponse
return AsyncProvidersResourceWithStreamingResponse(self._client.providers)
@cached_property
def audit_events(self) -> audit_events.AsyncAuditEventsResourceWithStreamingResponse:
+ """Read the audit trail of configuration and access changes in your workspace."""
from .resources.audit_events import AsyncAuditEventsResourceWithStreamingResponse
return AsyncAuditEventsResourceWithStreamingResponse(self._client.audit_events)
@cached_property
def auth(self) -> auth.AsyncAuthResourceWithStreamingResponse:
+ """
+ Issue scoped, short-lived JWTs so client-side SDKs — Inbox, Preferences, and the embedded designer — can call Courier as a single user. Server-side requests authenticate with your workspace API key instead.
+ """
from .resources.auth import AsyncAuthResourceWithStreamingResponse
return AsyncAuthResourceWithStreamingResponse(self._client.auth)
@cached_property
def automations(self) -> automations.AsyncAutomationsResourceWithStreamingResponse:
+ """
+ Invoke a stored automation template or an ad hoc automation defined in the request.
+ """
from .resources.automations import AsyncAutomationsResourceWithStreamingResponse
return AsyncAutomationsResourceWithStreamingResponse(self._client.automations)
@cached_property
def journeys(self) -> journeys.AsyncJourneysResourceWithStreamingResponse:
+ """
+ Build, version, publish, invoke, and cancel multi-step notification workflows, along with the templates scoped to them.
+ """
from .resources.journeys import AsyncJourneysResourceWithStreamingResponse
return AsyncJourneysResourceWithStreamingResponse(self._client.journeys)
@cached_property
def brands(self) -> brands.AsyncBrandsResourceWithStreamingResponse:
+ """
+ Manage the logos, colors, and layout that give the templates you send a consistent look.
+ """
from .resources.brands import AsyncBrandsResourceWithStreamingResponse
return AsyncBrandsResourceWithStreamingResponse(self._client.brands)
@@ -1121,60 +1435,96 @@ def digests(self) -> digests.AsyncDigestsResourceWithStreamingResponse:
@cached_property
def inbound(self) -> inbound.AsyncInboundResourceWithStreamingResponse:
+ """
+ Record an inbound event that triggers the journeys and automations mapped to it.
+ """
from .resources.inbound import AsyncInboundResourceWithStreamingResponse
return AsyncInboundResourceWithStreamingResponse(self._client.inbound)
@cached_property
def lists(self) -> lists.AsyncListsResourceWithStreamingResponse:
+ """
+ Manage static groups of users that you subscribe explicitly, and send to them by list id or list pattern.
+ """
from .resources.lists import AsyncListsResourceWithStreamingResponse
return AsyncListsResourceWithStreamingResponse(self._client.lists)
+ @cached_property
+ def inbox(self) -> inbox.AsyncInboxResourceWithStreamingResponse:
+ from .resources.inbox import AsyncInboxResourceWithStreamingResponse
+
+ return AsyncInboxResourceWithStreamingResponse(self._client.inbox)
+
@cached_property
def messages(self) -> messages.AsyncMessagesResourceWithStreamingResponse:
+ """
+ Look up the messages Courier has accepted, inspect their delivery history and rendered output, and cancel, resend, or archive them.
+ """
from .resources.messages import AsyncMessagesResourceWithStreamingResponse
return AsyncMessagesResourceWithStreamingResponse(self._client.messages)
@cached_property
def requests(self) -> requests.AsyncRequestsResourceWithStreamingResponse:
+ """
+ Look up the messages Courier has accepted, inspect their delivery history and rendered output, and cancel, resend, or archive them.
+ """
from .resources.requests import AsyncRequestsResourceWithStreamingResponse
return AsyncRequestsResourceWithStreamingResponse(self._client.requests)
@cached_property
def notifications(self) -> notifications.AsyncNotificationsResourceWithStreamingResponse:
+ """
+ Create, update, version, publish, and localize notification templates and their content.
+ """
from .resources.notifications import AsyncNotificationsResourceWithStreamingResponse
return AsyncNotificationsResourceWithStreamingResponse(self._client.notifications)
@cached_property
def routing_strategies(self) -> routing_strategies.AsyncRoutingStrategiesResourceWithStreamingResponse:
+ """
+ Define reusable channel routing and failover strategies, and see which templates use them.
+ """
from .resources.routing_strategies import AsyncRoutingStrategiesResourceWithStreamingResponse
return AsyncRoutingStrategiesResourceWithStreamingResponse(self._client.routing_strategies)
@cached_property
def workspace_preferences(self) -> workspace_preferences.AsyncWorkspacePreferencesResourceWithStreamingResponse:
+ """
+ Manage the workspace catalog of subscription topics, the sections that group them, and publishing the preference page.
+ """
from .resources.workspace_preferences import AsyncWorkspacePreferencesResourceWithStreamingResponse
return AsyncWorkspacePreferencesResourceWithStreamingResponse(self._client.workspace_preferences)
@cached_property
def profiles(self) -> profiles.AsyncProfilesResourceWithStreamingResponse:
+ """
+ Store the contact information Courier delivers to for each user — email, phone number, push tokens, and any custom data you send to.
+ """
from .resources.profiles import AsyncProfilesResourceWithStreamingResponse
return AsyncProfilesResourceWithStreamingResponse(self._client.profiles)
@cached_property
def tenants(self) -> tenants.AsyncTenantsResourceWithStreamingResponse:
+ """
+ Manage tenants — the organizations, teams, or accounts your users belong to — along with their users and default preferences.
+ """
from .resources.tenants import AsyncTenantsResourceWithStreamingResponse
return AsyncTenantsResourceWithStreamingResponse(self._client.tenants)
@cached_property
def translations(self) -> translations.AsyncTranslationsResourceWithStreamingResponse:
+ """
+ Store and retrieve the translation strings Courier uses to render localized template content.
+ """
from .resources.translations import AsyncTranslationsResourceWithStreamingResponse
return AsyncTranslationsResourceWithStreamingResponse(self._client.translations)
diff --git a/src/courier/_version.py b/src/courier/_version.py
index 04464d32..6b18c056 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.21.0" # x-release-please-version
+__version__ = "7.22.0" # x-release-please-version
diff --git a/src/courier/resources/__init__.py b/src/courier/resources/__init__.py
index 625b8b1d..3c433ca9 100644
--- a/src/courier/resources/__init__.py
+++ b/src/courier/resources/__init__.py
@@ -16,6 +16,14 @@
SendResourceWithStreamingResponse,
AsyncSendResourceWithStreamingResponse,
)
+from .inbox import (
+ InboxResource,
+ AsyncInboxResource,
+ InboxResourceWithRawResponse,
+ AsyncInboxResourceWithRawResponse,
+ InboxResourceWithStreamingResponse,
+ AsyncInboxResourceWithStreamingResponse,
+)
from .lists import (
ListsResource,
AsyncListsResource,
@@ -228,6 +236,12 @@
"AsyncListsResourceWithRawResponse",
"ListsResourceWithStreamingResponse",
"AsyncListsResourceWithStreamingResponse",
+ "InboxResource",
+ "AsyncInboxResource",
+ "InboxResourceWithRawResponse",
+ "AsyncInboxResourceWithRawResponse",
+ "InboxResourceWithStreamingResponse",
+ "AsyncInboxResourceWithStreamingResponse",
"MessagesResource",
"AsyncMessagesResource",
"MessagesResourceWithRawResponse",
diff --git a/src/courier/resources/audiences.py b/src/courier/resources/audiences.py
index 4c930bdb..9a2c2ad7 100644
--- a/src/courier/resources/audiences.py
+++ b/src/courier/resources/audiences.py
@@ -29,6 +29,10 @@
class AudiencesResource(SyncAPIResource):
+ """
+ Define filter-based groups whose membership Courier recalculates as user profiles change.
+ """
+
@cached_property
def with_raw_response(self) -> AudiencesResourceWithRawResponse:
"""
@@ -60,7 +64,8 @@ def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Audience:
"""
- Returns the specified audience by id.
+ Returns one audience with its name, description, and the filter and AND or OR
+ operator that decide which users belong to it.
Args:
extra_headers: Send extra headers
@@ -97,7 +102,8 @@ def update(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AudienceUpdateResponse:
"""
- Creates or updates audience.
+ Creates or replaces an audience from a filter and an AND or OR operator.
+ Membership recalculates automatically as profiles change.
Args:
description: A description of the audience
@@ -148,8 +154,10 @@ def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AudienceListResponse:
- """
- Get the audiences associated with the authorization token.
+ """Returns the audiences in the workspace with paging.
+
+ Audiences are filter-based
+ groups that recalculate as user profiles change.
Args:
cursor: A unique identifier that allows for fetching the next set of audiences
@@ -186,7 +194,8 @@ def delete(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
"""
- Deletes the specified audience.
+ Deletes an audience permanently, so update any caller sending to it by audience
+ id first. Those sends fail once the audience is gone.
Args:
extra_headers: Send extra headers
@@ -220,8 +229,10 @@ def list_members(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AudienceListMembersResponse:
- """
- Get list of members of an audience.
+ """Returns the users currently matching an audience filter, with paging.
+
+ Membership
+ is recalculated, so results shift as profiles change.
Args:
cursor: A unique identifier that allows for fetching the next set of members
@@ -250,6 +261,10 @@ def list_members(
class AsyncAudiencesResource(AsyncAPIResource):
+ """
+ Define filter-based groups whose membership Courier recalculates as user profiles change.
+ """
+
@cached_property
def with_raw_response(self) -> AsyncAudiencesResourceWithRawResponse:
"""
@@ -281,7 +296,8 @@ async def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Audience:
"""
- Returns the specified audience by id.
+ Returns one audience with its name, description, and the filter and AND or OR
+ operator that decide which users belong to it.
Args:
extra_headers: Send extra headers
@@ -318,7 +334,8 @@ async def update(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AudienceUpdateResponse:
"""
- Creates or updates audience.
+ Creates or replaces an audience from a filter and an AND or OR operator.
+ Membership recalculates automatically as profiles change.
Args:
description: A description of the audience
@@ -369,8 +386,10 @@ async def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AudienceListResponse:
- """
- Get the audiences associated with the authorization token.
+ """Returns the audiences in the workspace with paging.
+
+ Audiences are filter-based
+ groups that recalculate as user profiles change.
Args:
cursor: A unique identifier that allows for fetching the next set of audiences
@@ -407,7 +426,8 @@ async def delete(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
"""
- Deletes the specified audience.
+ Deletes an audience permanently, so update any caller sending to it by audience
+ id first. Those sends fail once the audience is gone.
Args:
extra_headers: Send extra headers
@@ -441,8 +461,10 @@ async def list_members(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AudienceListMembersResponse:
- """
- Get list of members of an audience.
+ """Returns the users currently matching an audience filter, with paging.
+
+ Membership
+ is recalculated, so results shift as profiles change.
Args:
cursor: A unique identifier that allows for fetching the next set of members
diff --git a/src/courier/resources/audit_events.py b/src/courier/resources/audit_events.py
index 0323e1fc..042d4b2e 100644
--- a/src/courier/resources/audit_events.py
+++ b/src/courier/resources/audit_events.py
@@ -25,6 +25,8 @@
class AuditEventsResource(SyncAPIResource):
+ """Read the audit trail of configuration and access changes in your workspace."""
+
@cached_property
def with_raw_response(self) -> AuditEventsResourceWithRawResponse:
"""
@@ -56,7 +58,8 @@ def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AuditEvent:
"""
- Fetch a specific audit event by ID.
+ Returns one audit event by id, including the actor who performed it, the target
+ they changed, the source, the event type, and a timestamp.
Args:
extra_headers: Send extra headers
@@ -88,8 +91,10 @@ def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AuditEventListResponse:
- """
- Fetch the list of audit events
+ """Returns the workspace's audit event log with cursor paging.
+
+ Each event records
+ the actor, target, source, type, and timestamp of a change.
Args:
cursor: A unique identifier that allows for fetching the next set of audit events.
@@ -116,6 +121,8 @@ def list(
class AsyncAuditEventsResource(AsyncAPIResource):
+ """Read the audit trail of configuration and access changes in your workspace."""
+
@cached_property
def with_raw_response(self) -> AsyncAuditEventsResourceWithRawResponse:
"""
@@ -147,7 +154,8 @@ async def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AuditEvent:
"""
- Fetch a specific audit event by ID.
+ Returns one audit event by id, including the actor who performed it, the target
+ they changed, the source, the event type, and a timestamp.
Args:
extra_headers: Send extra headers
@@ -179,8 +187,10 @@ async def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AuditEventListResponse:
- """
- Fetch the list of audit events
+ """Returns the workspace's audit event log with cursor paging.
+
+ Each event records
+ the actor, target, source, type, and timestamp of a change.
Args:
cursor: A unique identifier that allows for fetching the next set of audit events.
diff --git a/src/courier/resources/auth.py b/src/courier/resources/auth.py
index b7b00ad1..c3e17fcc 100644
--- a/src/courier/resources/auth.py
+++ b/src/courier/resources/auth.py
@@ -22,6 +22,10 @@
class AuthResource(SyncAPIResource):
+ """
+ Issue scoped, short-lived JWTs so client-side SDKs — Inbox, Preferences, and the embedded designer — can call Courier as a single user. Server-side requests authenticate with your workspace API key instead.
+ """
+
@cached_property
def with_raw_response(self) -> AuthResourceWithRawResponse:
"""
@@ -53,8 +57,10 @@ def issue_token(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AuthIssueTokenResponse:
- """
- Returns a new access token.
+ """Returns a JWT for authenticating client-side SDKs such as the Inbox.
+
+ You supply
+ the scope and an expires_in duration, both required.
Args:
expires_in:
@@ -112,6 +118,10 @@ def issue_token(
class AsyncAuthResource(AsyncAPIResource):
+ """
+ Issue scoped, short-lived JWTs so client-side SDKs — Inbox, Preferences, and the embedded designer — can call Courier as a single user. Server-side requests authenticate with your workspace API key instead.
+ """
+
@cached_property
def with_raw_response(self) -> AsyncAuthResourceWithRawResponse:
"""
@@ -143,8 +153,10 @@ async def issue_token(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AuthIssueTokenResponse:
- """
- Returns a new access token.
+ """Returns a JWT for authenticating client-side SDKs such as the Inbox.
+
+ You supply
+ the scope and an expires_in duration, both required.
Args:
expires_in:
diff --git a/src/courier/resources/automations/automations.py b/src/courier/resources/automations/automations.py
index fd29b8a3..1ae9add2 100644
--- a/src/courier/resources/automations/automations.py
+++ b/src/courier/resources/automations/automations.py
@@ -32,8 +32,15 @@
class AutomationsResource(SyncAPIResource):
+ """
+ Invoke a stored automation template or an ad hoc automation defined in the request.
+ """
+
@cached_property
def invoke(self) -> InvokeResource:
+ """
+ Invoke a stored automation template or an ad hoc automation defined in the request.
+ """
return InvokeResource(self._client)
@cached_property
@@ -67,12 +74,12 @@ def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AutomationTemplateListResponse:
- """Get the list of automations.
+ """
+ Lists the workspace's saved automation templates, each with its id and a cursor
+ for paging to the next page of results.
Args:
- cursor: A cursor token for pagination.
-
- Use the cursor from the previous response to
+ cursor: A cursor token for pagination. Use the cursor from the previous response to
fetch the next page of results.
version: The version of templates to retrieve. Accepted values are published (for
@@ -106,8 +113,15 @@ def list(
class AsyncAutomationsResource(AsyncAPIResource):
+ """
+ Invoke a stored automation template or an ad hoc automation defined in the request.
+ """
+
@cached_property
def invoke(self) -> AsyncInvokeResource:
+ """
+ Invoke a stored automation template or an ad hoc automation defined in the request.
+ """
return AsyncInvokeResource(self._client)
@cached_property
@@ -141,12 +155,12 @@ async def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AutomationTemplateListResponse:
- """Get the list of automations.
+ """
+ Lists the workspace's saved automation templates, each with its id and a cursor
+ for paging to the next page of results.
Args:
- cursor: A cursor token for pagination.
-
- Use the cursor from the previous response to
+ cursor: A cursor token for pagination. Use the cursor from the previous response to
fetch the next page of results.
version: The version of templates to retrieve. Accepted values are published (for
@@ -189,6 +203,9 @@ def __init__(self, automations: AutomationsResource) -> None:
@cached_property
def invoke(self) -> InvokeResourceWithRawResponse:
+ """
+ Invoke a stored automation template or an ad hoc automation defined in the request.
+ """
return InvokeResourceWithRawResponse(self._automations.invoke)
@@ -202,6 +219,9 @@ def __init__(self, automations: AsyncAutomationsResource) -> None:
@cached_property
def invoke(self) -> AsyncInvokeResourceWithRawResponse:
+ """
+ Invoke a stored automation template or an ad hoc automation defined in the request.
+ """
return AsyncInvokeResourceWithRawResponse(self._automations.invoke)
@@ -215,6 +235,9 @@ def __init__(self, automations: AutomationsResource) -> None:
@cached_property
def invoke(self) -> InvokeResourceWithStreamingResponse:
+ """
+ Invoke a stored automation template or an ad hoc automation defined in the request.
+ """
return InvokeResourceWithStreamingResponse(self._automations.invoke)
@@ -228,4 +251,7 @@ def __init__(self, automations: AsyncAutomationsResource) -> None:
@cached_property
def invoke(self) -> AsyncInvokeResourceWithStreamingResponse:
+ """
+ Invoke a stored automation template or an ad hoc automation defined in the request.
+ """
return AsyncInvokeResourceWithStreamingResponse(self._automations.invoke)
diff --git a/src/courier/resources/automations/invoke.py b/src/courier/resources/automations/invoke.py
index 7ec143ec..c1e9cfe5 100644
--- a/src/courier/resources/automations/invoke.py
+++ b/src/courier/resources/automations/invoke.py
@@ -7,7 +7,7 @@
import httpx
from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
-from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import (
@@ -24,6 +24,10 @@
class InvokeResource(SyncAPIResource):
+ """
+ Invoke a stored automation template or an ad hoc automation defined in the request.
+ """
+
@cached_property
def with_raw_response(self) -> InvokeResourceWithRawResponse:
"""
@@ -52,6 +56,8 @@ def invoke_ad_hoc(
profile: Optional[Dict[str, object]] | Omit = omit,
recipient: Optional[str] | Omit = omit,
template: Optional[str] | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -59,12 +65,9 @@ def invoke_ad_hoc(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AutomationInvokeResponse:
- """Invoke an ad hoc automation run.
-
- This endpoint accepts a JSON payload with a
- series of automation steps. For information about what steps are available,
- checkout the ad hoc automation guide
- [here](https://www.courier.com/docs/automations/steps/).
+ """
+ Runs a series of automation steps supplied inline, without a saved template, and
+ returns a runId.
Args:
extra_headers: Send extra headers
@@ -75,6 +78,15 @@ def invoke_ad_hoc(
timeout: Override the client-level default timeout for this request, in seconds
"""
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return self._post(
"/automations/invoke",
body=maybe_transform(
@@ -103,6 +115,8 @@ def invoke_by_template(
data: Optional[Dict[str, object]] | Omit = omit,
profile: Optional[Dict[str, object]] | Omit = omit,
template: Optional[str] | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -111,7 +125,8 @@ def invoke_by_template(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AutomationInvokeResponse:
"""
- Invoke an automation run from an automation template.
+ Starts an automation run from a saved template for one recipient, with optional
+ data and profile, and returns a runId.
Args:
extra_headers: Send extra headers
@@ -124,6 +139,15 @@ def invoke_by_template(
"""
if not template_id:
raise ValueError(f"Expected a non-empty value for `template_id` but received {template_id!r}")
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return self._post(
path_template("/automations/{template_id}/invoke", template_id=template_id),
body=maybe_transform(
@@ -144,6 +168,10 @@ def invoke_by_template(
class AsyncInvokeResource(AsyncAPIResource):
+ """
+ Invoke a stored automation template or an ad hoc automation defined in the request.
+ """
+
@cached_property
def with_raw_response(self) -> AsyncInvokeResourceWithRawResponse:
"""
@@ -172,6 +200,8 @@ async def invoke_ad_hoc(
profile: Optional[Dict[str, object]] | Omit = omit,
recipient: Optional[str] | Omit = omit,
template: Optional[str] | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -179,12 +209,9 @@ async def invoke_ad_hoc(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AutomationInvokeResponse:
- """Invoke an ad hoc automation run.
-
- This endpoint accepts a JSON payload with a
- series of automation steps. For information about what steps are available,
- checkout the ad hoc automation guide
- [here](https://www.courier.com/docs/automations/steps/).
+ """
+ Runs a series of automation steps supplied inline, without a saved template, and
+ returns a runId.
Args:
extra_headers: Send extra headers
@@ -195,6 +222,15 @@ async def invoke_ad_hoc(
timeout: Override the client-level default timeout for this request, in seconds
"""
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return await self._post(
"/automations/invoke",
body=await async_maybe_transform(
@@ -223,6 +259,8 @@ async def invoke_by_template(
data: Optional[Dict[str, object]] | Omit = omit,
profile: Optional[Dict[str, object]] | Omit = omit,
template: Optional[str] | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -231,7 +269,8 @@ async def invoke_by_template(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AutomationInvokeResponse:
"""
- Invoke an automation run from an automation template.
+ Starts an automation run from a saved template for one recipient, with optional
+ data and profile, and returns a runId.
Args:
extra_headers: Send extra headers
@@ -244,6 +283,15 @@ async def invoke_by_template(
"""
if not template_id:
raise ValueError(f"Expected a non-empty value for `template_id` but received {template_id!r}")
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return await self._post(
path_template("/automations/{template_id}/invoke", template_id=template_id),
body=await async_maybe_transform(
diff --git a/src/courier/resources/brands.py b/src/courier/resources/brands.py
index 93754218..ca96b48f 100644
--- a/src/courier/resources/brands.py
+++ b/src/courier/resources/brands.py
@@ -8,7 +8,7 @@
from ..types import brand_list_params, brand_create_params, brand_update_params
from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
-from .._utils import path_template, maybe_transform, async_maybe_transform
+from .._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform
from .._compat import cached_property
from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
@@ -27,6 +27,10 @@
class BrandsResource(SyncAPIResource):
+ """
+ Manage the logos, colors, and layout that give the templates you send a consistent look.
+ """
+
@cached_property
def with_raw_response(self) -> BrandsResourceWithRawResponse:
"""
@@ -53,6 +57,8 @@ def create(
settings: BrandSettingsParam,
id: Optional[str] | Omit = omit,
snippets: Optional[BrandSnippetsParam] | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -60,10 +66,10 @@ def create(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Brand:
- """Create a new brand.
+ """Creates a brand from a name and settings, including primary and secondary
+ colors.
- Requires `name` and `settings` (with at least
- `colors.primary` and `colors.secondary`).
+ Brands supply the logo, colors, and styling that templates render with.
Args:
extra_headers: Send extra headers
@@ -74,6 +80,15 @@ def create(
timeout: Override the client-level default timeout for this request, in seconds
"""
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return self._post(
"/brands",
body=maybe_transform(
@@ -103,7 +118,8 @@ def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Brand:
"""
- Fetch a specific brand by brand ID.
+ Returns one brand by id, including its colors, logo and styling settings,
+ Handlebars snippets, and published version.
Args:
extra_headers: Send extra headers
@@ -139,7 +155,8 @@ def update(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Brand:
"""
- Replace an existing brand with the supplied values.
+ Replaces a brand with the values you supply, so send the complete settings and
+ snippets rather than only the fields you want changed.
Args:
name: The name of the brand.
@@ -181,8 +198,10 @@ def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> BrandListResponse:
- """
- Get the list of brands.
+ """Lists the workspace's brands.
+
+ Every entry carries its name, styling settings,
+ snippets, and published version.
Args:
cursor: A unique identifier that allows for fetching the next set of brands.
@@ -218,8 +237,10 @@ def delete(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """
- Delete a brand by brand ID.
+ """Deletes a brand by id.
+
+ Reassign any template or tenant that references it before
+ deleting to keep their styling intact.
Args:
extra_headers: Send extra headers
@@ -243,6 +264,10 @@ def delete(
class AsyncBrandsResource(AsyncAPIResource):
+ """
+ Manage the logos, colors, and layout that give the templates you send a consistent look.
+ """
+
@cached_property
def with_raw_response(self) -> AsyncBrandsResourceWithRawResponse:
"""
@@ -269,6 +294,8 @@ async def create(
settings: BrandSettingsParam,
id: Optional[str] | Omit = omit,
snippets: Optional[BrandSnippetsParam] | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -276,10 +303,10 @@ async def create(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Brand:
- """Create a new brand.
+ """Creates a brand from a name and settings, including primary and secondary
+ colors.
- Requires `name` and `settings` (with at least
- `colors.primary` and `colors.secondary`).
+ Brands supply the logo, colors, and styling that templates render with.
Args:
extra_headers: Send extra headers
@@ -290,6 +317,15 @@ async def create(
timeout: Override the client-level default timeout for this request, in seconds
"""
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return await self._post(
"/brands",
body=await async_maybe_transform(
@@ -319,7 +355,8 @@ async def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Brand:
"""
- Fetch a specific brand by brand ID.
+ Returns one brand by id, including its colors, logo and styling settings,
+ Handlebars snippets, and published version.
Args:
extra_headers: Send extra headers
@@ -355,7 +392,8 @@ async def update(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Brand:
"""
- Replace an existing brand with the supplied values.
+ Replaces a brand with the values you supply, so send the complete settings and
+ snippets rather than only the fields you want changed.
Args:
name: The name of the brand.
@@ -397,8 +435,10 @@ async def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> BrandListResponse:
- """
- Get the list of brands.
+ """Lists the workspace's brands.
+
+ Every entry carries its name, styling settings,
+ snippets, and published version.
Args:
cursor: A unique identifier that allows for fetching the next set of brands.
@@ -434,8 +474,10 @@ async def delete(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """
- Delete a brand by brand ID.
+ """Deletes a brand by id.
+
+ Reassign any template or tenant that references it before
+ deleting to keep their styling intact.
Args:
extra_headers: Send extra headers
diff --git a/src/courier/resources/digests/digests.py b/src/courier/resources/digests/digests.py
index b50742c5..d2d31d1e 100644
--- a/src/courier/resources/digests/digests.py
+++ b/src/courier/resources/digests/digests.py
@@ -19,6 +19,9 @@
class DigestsResource(SyncAPIResource):
@cached_property
def schedules(self) -> SchedulesResource:
+ """
+ Inspect what has accumulated in a digest schedule and release a digest ahead of its next scheduled delivery.
+ """
return SchedulesResource(self._client)
@cached_property
@@ -44,6 +47,9 @@ def with_streaming_response(self) -> DigestsResourceWithStreamingResponse:
class AsyncDigestsResource(AsyncAPIResource):
@cached_property
def schedules(self) -> AsyncSchedulesResource:
+ """
+ Inspect what has accumulated in a digest schedule and release a digest ahead of its next scheduled delivery.
+ """
return AsyncSchedulesResource(self._client)
@cached_property
@@ -72,6 +78,9 @@ def __init__(self, digests: DigestsResource) -> None:
@cached_property
def schedules(self) -> SchedulesResourceWithRawResponse:
+ """
+ Inspect what has accumulated in a digest schedule and release a digest ahead of its next scheduled delivery.
+ """
return SchedulesResourceWithRawResponse(self._digests.schedules)
@@ -81,6 +90,9 @@ def __init__(self, digests: AsyncDigestsResource) -> None:
@cached_property
def schedules(self) -> AsyncSchedulesResourceWithRawResponse:
+ """
+ Inspect what has accumulated in a digest schedule and release a digest ahead of its next scheduled delivery.
+ """
return AsyncSchedulesResourceWithRawResponse(self._digests.schedules)
@@ -90,6 +102,9 @@ def __init__(self, digests: DigestsResource) -> None:
@cached_property
def schedules(self) -> SchedulesResourceWithStreamingResponse:
+ """
+ Inspect what has accumulated in a digest schedule and release a digest ahead of its next scheduled delivery.
+ """
return SchedulesResourceWithStreamingResponse(self._digests.schedules)
@@ -99,4 +114,7 @@ def __init__(self, digests: AsyncDigestsResource) -> None:
@cached_property
def schedules(self) -> AsyncSchedulesResourceWithStreamingResponse:
+ """
+ Inspect what has accumulated in a digest schedule and release a digest ahead of its next scheduled delivery.
+ """
return AsyncSchedulesResourceWithStreamingResponse(self._digests.schedules)
diff --git a/src/courier/resources/digests/schedules.py b/src/courier/resources/digests/schedules.py
index d92f7653..006e4b5d 100644
--- a/src/courier/resources/digests/schedules.py
+++ b/src/courier/resources/digests/schedules.py
@@ -22,6 +22,10 @@
class SchedulesResource(SyncAPIResource):
+ """
+ Inspect what has accumulated in a digest schedule and release a digest ahead of its next scheduled delivery.
+ """
+
@cached_property
def with_raw_response(self) -> SchedulesResourceWithRawResponse:
"""
@@ -54,11 +58,9 @@ def list_instances(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> DigestInstanceListResponse:
- """List the digest instances for a schedule.
-
- Each instance represents the events
- accumulated for a single user against the schedule, and can be used to monitor
- digest accumulation before the digest is released.
+ """
+ Returns the digest instances for a schedule, one per user, with cursor paging.
+ Use it to see what has accumulated before a digest releases.
Args:
cursor: A cursor token from a previous response, used to fetch the next page of results.
@@ -131,6 +133,10 @@ def release(
class AsyncSchedulesResource(AsyncAPIResource):
+ """
+ Inspect what has accumulated in a digest schedule and release a digest ahead of its next scheduled delivery.
+ """
+
@cached_property
def with_raw_response(self) -> AsyncSchedulesResourceWithRawResponse:
"""
@@ -163,11 +169,9 @@ async def list_instances(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> DigestInstanceListResponse:
- """List the digest instances for a schedule.
-
- Each instance represents the events
- accumulated for a single user against the schedule, and can be used to monitor
- digest accumulation before the digest is released.
+ """
+ Returns the digest instances for a schedule, one per user, with cursor paging.
+ Use it to see what has accumulated before a digest releases.
Args:
cursor: A cursor token from a previous response, used to fetch the next page of results.
diff --git a/src/courier/resources/inbound.py b/src/courier/resources/inbound.py
index 4c1a58c9..d5af1bbc 100644
--- a/src/courier/resources/inbound.py
+++ b/src/courier/resources/inbound.py
@@ -25,6 +25,10 @@
class InboundResource(SyncAPIResource):
+ """
+ Record an inbound event that triggers the journeys and automations mapped to it.
+ """
+
@cached_property
def with_raw_response(self) -> InboundResourceWithRawResponse:
"""
@@ -59,12 +63,13 @@ def track_event(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> InboundTrackEventResponse:
- """Courier Track Event
+ """Records an inbound event that can trigger a journey.
- Args:
- event: A descriptive name of the event.
+ Requires an event name, a
+ messageId you generate, a type, and a properties object.
- This name will appear as a trigger in the
+ Args:
+ event: A descriptive name of the event. This name will appear as a trigger in the
Courier Automation Trigger node.
message_id: A required unique identifier that will be used to de-duplicate requests. If not
@@ -100,6 +105,10 @@ def track_event(
class AsyncInboundResource(AsyncAPIResource):
+ """
+ Record an inbound event that triggers the journeys and automations mapped to it.
+ """
+
@cached_property
def with_raw_response(self) -> AsyncInboundResourceWithRawResponse:
"""
@@ -134,12 +143,13 @@ async def track_event(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> InboundTrackEventResponse:
- """Courier Track Event
+ """Records an inbound event that can trigger a journey.
- Args:
- event: A descriptive name of the event.
+ Requires an event name, a
+ messageId you generate, a type, and a properties object.
- This name will appear as a trigger in the
+ Args:
+ event: A descriptive name of the event. This name will appear as a trigger in the
Courier Automation Trigger node.
message_id: A required unique identifier that will be used to de-duplicate requests. If not
diff --git a/src/courier/resources/inbox/__init__.py b/src/courier/resources/inbox/__init__.py
new file mode 100644
index 00000000..13574c16
--- /dev/null
+++ b/src/courier/resources/inbox/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .inbox import (
+ InboxResource,
+ AsyncInboxResource,
+ InboxResourceWithRawResponse,
+ AsyncInboxResourceWithRawResponse,
+ InboxResourceWithStreamingResponse,
+ AsyncInboxResourceWithStreamingResponse,
+)
+from .messages import (
+ MessagesResource,
+ AsyncMessagesResource,
+ MessagesResourceWithRawResponse,
+ AsyncMessagesResourceWithRawResponse,
+ MessagesResourceWithStreamingResponse,
+ AsyncMessagesResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "MessagesResource",
+ "AsyncMessagesResource",
+ "MessagesResourceWithRawResponse",
+ "AsyncMessagesResourceWithRawResponse",
+ "MessagesResourceWithStreamingResponse",
+ "AsyncMessagesResourceWithStreamingResponse",
+ "InboxResource",
+ "AsyncInboxResource",
+ "InboxResourceWithRawResponse",
+ "AsyncInboxResourceWithRawResponse",
+ "InboxResourceWithStreamingResponse",
+ "AsyncInboxResourceWithStreamingResponse",
+]
diff --git a/src/courier/resources/inbox/inbox.py b/src/courier/resources/inbox/inbox.py
new file mode 100644
index 00000000..4ab70842
--- /dev/null
+++ b/src/courier/resources/inbox/inbox.py
@@ -0,0 +1,108 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .messages import (
+ MessagesResource,
+ AsyncMessagesResource,
+ MessagesResourceWithRawResponse,
+ AsyncMessagesResourceWithRawResponse,
+ MessagesResourceWithStreamingResponse,
+ AsyncMessagesResourceWithStreamingResponse,
+)
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+
+__all__ = ["InboxResource", "AsyncInboxResource"]
+
+
+class InboxResource(SyncAPIResource):
+ @cached_property
+ def messages(self) -> MessagesResource:
+ """Manage the messages in a user's in-app inbox."""
+ return MessagesResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> InboxResourceWithRawResponse:
+ """
+ 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 InboxResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> InboxResourceWithStreamingResponse:
+ """
+ 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 InboxResourceWithStreamingResponse(self)
+
+
+class AsyncInboxResource(AsyncAPIResource):
+ @cached_property
+ def messages(self) -> AsyncMessagesResource:
+ """Manage the messages in a user's in-app inbox."""
+ return AsyncMessagesResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncInboxResourceWithRawResponse:
+ """
+ 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 AsyncInboxResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncInboxResourceWithStreamingResponse:
+ """
+ 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 AsyncInboxResourceWithStreamingResponse(self)
+
+
+class InboxResourceWithRawResponse:
+ def __init__(self, inbox: InboxResource) -> None:
+ self._inbox = inbox
+
+ @cached_property
+ def messages(self) -> MessagesResourceWithRawResponse:
+ """Manage the messages in a user's in-app inbox."""
+ return MessagesResourceWithRawResponse(self._inbox.messages)
+
+
+class AsyncInboxResourceWithRawResponse:
+ def __init__(self, inbox: AsyncInboxResource) -> None:
+ self._inbox = inbox
+
+ @cached_property
+ def messages(self) -> AsyncMessagesResourceWithRawResponse:
+ """Manage the messages in a user's in-app inbox."""
+ return AsyncMessagesResourceWithRawResponse(self._inbox.messages)
+
+
+class InboxResourceWithStreamingResponse:
+ def __init__(self, inbox: InboxResource) -> None:
+ self._inbox = inbox
+
+ @cached_property
+ def messages(self) -> MessagesResourceWithStreamingResponse:
+ """Manage the messages in a user's in-app inbox."""
+ return MessagesResourceWithStreamingResponse(self._inbox.messages)
+
+
+class AsyncInboxResourceWithStreamingResponse:
+ def __init__(self, inbox: AsyncInboxResource) -> None:
+ self._inbox = inbox
+
+ @cached_property
+ def messages(self) -> AsyncMessagesResourceWithStreamingResponse:
+ """Manage the messages in a user's in-app inbox."""
+ return AsyncMessagesResourceWithStreamingResponse(self._inbox.messages)
diff --git a/src/courier/resources/inbox/messages.py b/src/courier/resources/inbox/messages.py
new file mode 100644
index 00000000..75bf3da5
--- /dev/null
+++ b/src/courier/resources/inbox/messages.py
@@ -0,0 +1,253 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..._types import Body, Query, Headers, NoneType, NotGiven, not_given
+from ..._utils import path_template
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+
+__all__ = ["MessagesResource", "AsyncMessagesResource"]
+
+
+class MessagesResource(SyncAPIResource):
+ """Manage the messages in a user's in-app inbox."""
+
+ @cached_property
+ def with_raw_response(self) -> MessagesResourceWithRawResponse:
+ """
+ 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 MessagesResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> MessagesResourceWithStreamingResponse:
+ """
+ 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 MessagesResourceWithStreamingResponse(self)
+
+ def delete(
+ self,
+ message_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """Delete a user's inbox message.
+
+ The message is removed from every inbox read (it
+ stops appearing in the recipient's Inbox); it can be restored.
+
+ 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 message_id:
+ raise ValueError(f"Expected a non-empty value for `message_id` but received {message_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._delete(
+ path_template("/inbox/messages/{message_id}", message_id=message_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ def restore(
+ self,
+ message_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Restore a previously deleted inbox message.
+
+ 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 message_id:
+ raise ValueError(f"Expected a non-empty value for `message_id` but received {message_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._put(
+ path_template("/inbox/messages/{message_id}/restore", message_id=message_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+
+class AsyncMessagesResource(AsyncAPIResource):
+ """Manage the messages in a user's in-app inbox."""
+
+ @cached_property
+ def with_raw_response(self) -> AsyncMessagesResourceWithRawResponse:
+ """
+ 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 AsyncMessagesResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncMessagesResourceWithStreamingResponse:
+ """
+ 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 AsyncMessagesResourceWithStreamingResponse(self)
+
+ async def delete(
+ self,
+ message_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """Delete a user's inbox message.
+
+ The message is removed from every inbox read (it
+ stops appearing in the recipient's Inbox); it can be restored.
+
+ 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 message_id:
+ raise ValueError(f"Expected a non-empty value for `message_id` but received {message_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._delete(
+ path_template("/inbox/messages/{message_id}", message_id=message_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+ async def restore(
+ self,
+ message_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Restore a previously deleted inbox message.
+
+ 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 message_id:
+ raise ValueError(f"Expected a non-empty value for `message_id` but received {message_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._put(
+ path_template("/inbox/messages/{message_id}/restore", message_id=message_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+
+class MessagesResourceWithRawResponse:
+ def __init__(self, messages: MessagesResource) -> None:
+ self._messages = messages
+
+ self.delete = to_raw_response_wrapper(
+ messages.delete,
+ )
+ self.restore = to_raw_response_wrapper(
+ messages.restore,
+ )
+
+
+class AsyncMessagesResourceWithRawResponse:
+ def __init__(self, messages: AsyncMessagesResource) -> None:
+ self._messages = messages
+
+ self.delete = async_to_raw_response_wrapper(
+ messages.delete,
+ )
+ self.restore = async_to_raw_response_wrapper(
+ messages.restore,
+ )
+
+
+class MessagesResourceWithStreamingResponse:
+ def __init__(self, messages: MessagesResource) -> None:
+ self._messages = messages
+
+ self.delete = to_streamed_response_wrapper(
+ messages.delete,
+ )
+ self.restore = to_streamed_response_wrapper(
+ messages.restore,
+ )
+
+
+class AsyncMessagesResourceWithStreamingResponse:
+ def __init__(self, messages: AsyncMessagesResource) -> None:
+ self._messages = messages
+
+ self.delete = async_to_streamed_response_wrapper(
+ messages.delete,
+ )
+ self.restore = async_to_streamed_response_wrapper(
+ messages.restore,
+ )
diff --git a/src/courier/resources/journeys/journeys.py b/src/courier/resources/journeys/journeys.py
index c71614e6..7bb1439a 100644
--- a/src/courier/resources/journeys/journeys.py
+++ b/src/courier/resources/journeys/journeys.py
@@ -18,7 +18,7 @@
journey_retrieve_params,
)
from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
-from ..._utils import path_template, required_args, maybe_transform, async_maybe_transform
+from ..._utils import path_template, required_args, maybe_transform, strip_not_given, async_maybe_transform
from ..._compat import cached_property
from .templates import (
TemplatesResource,
@@ -48,8 +48,15 @@
class JourneysResource(SyncAPIResource):
+ """
+ Build, version, publish, invoke, and cancel multi-step notification workflows, along with the templates scoped to them.
+ """
+
@cached_property
def templates(self) -> TemplatesResource:
+ """
+ Build, version, publish, invoke, and cancel multi-step notification workflows, along with the templates scoped to them.
+ """
return TemplatesResource(self._client)
@cached_property
@@ -78,6 +85,8 @@ def create(
nodes: Iterable[JourneyNodeParam],
enabled: bool | Omit = omit,
state: JourneyState | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -85,14 +94,9 @@ def create(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> JourneyResponse:
- """Create a journey.
-
- Defaults to `DRAFT` state; pass `state: "PUBLISHED"` to
- publish on create. Send nodes are not allowed on `POST`. The standard flow is:
- create the journey shell here, add notification templates with
- `POST /journeys/{templateId}/templates`, then wire them into the journey with
- `PUT /journeys/{templateId}`. Call `POST /journeys/{templateId}/publish` to
- publish a draft after the fact.
+ """
+ Creates a journey from a set of nodes, in draft state unless you pass a
+ published state. Send nodes cannot be included until their templates exist.
Args:
state: Lifecycle state of a journey.
@@ -105,6 +109,15 @@ def create(
timeout: Override the client-level default timeout for this request, in seconds
"""
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return self._post(
"/journeys",
body=maybe_transform(
@@ -176,12 +189,12 @@ def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> JourneysListResponse:
- """Get the list of journeys.
+ """
+ Lists the workspace's journeys, each carrying a name, state, and enabled flag.
+ Paged by cursor.
Args:
- cursor: A cursor token for pagination.
-
- Use the cursor from the previous response to
+ cursor: A cursor token for pagination. Use the cursor from the previous response to
fetch the next page of results.
version: The version of journeys to retrieve. Accepted values are published (for
@@ -224,10 +237,10 @@ def archive(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Archive a journey.
+ """Archives a journey so it can no longer be invoked.
- Archived journeys cannot be invoked. Existing journey runs
- continue to completion.
+ Runs already in flight
+ continue to completion, so archiving never strands a user mid-sequence.
Args:
extra_headers: Send extra headers
@@ -254,6 +267,8 @@ def cancel(
self,
*,
cancelation_token: str,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -261,14 +276,9 @@ def cancel(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> CancelJourneyResponse:
- """Cancel journey runs.
-
- The request body must include EXACTLY ONE of
- `cancelation_token` (cancels every run associated with the token) or `run_id`
- (cancels a single tenant-scoped run). Supplying both or neither returns a `400`.
- A `run_id` that does not match a run for the tenant returns `404`. Cancelation
- is idempotent: a run that has already finished (`PROCESSED`/`ERROR`) or was
- already `CANCELED` is left unchanged and its current status is returned.
+ """
+ Cancels in-flight journey runs, either every run sharing a cancelation token or
+ one run by id. Use it to stop a sequence when the event resolves.
Args:
extra_headers: Send extra headers
@@ -286,6 +296,8 @@ def cancel(
self,
*,
run_id: str,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -293,14 +305,9 @@ def cancel(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> CancelJourneyResponse:
- """Cancel journey runs.
-
- The request body must include EXACTLY ONE of
- `cancelation_token` (cancels every run associated with the token) or `run_id`
- (cancels a single tenant-scoped run). Supplying both or neither returns a `400`.
- A `run_id` that does not match a run for the tenant returns `404`. Cancelation
- is idempotent: a run that has already finished (`PROCESSED`/`ERROR`) or was
- already `CANCELED` is left unchanged and its current status is returned.
+ """
+ Cancels in-flight journey runs, either every run sharing a cancelation token or
+ one run by id. Use it to stop a sequence when the event resolves.
Args:
extra_headers: Send extra headers
@@ -318,6 +325,8 @@ def cancel(
self,
*,
cancelation_token: str | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: str | Omit = omit,
run_id: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
@@ -326,6 +335,15 @@ def cancel(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> CancelJourneyResponse:
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return cast(
CancelJourneyResponse,
self._post(
@@ -353,6 +371,8 @@ def invoke(
data: Dict[str, object] | Omit = omit,
profile: Dict[str, object] | Omit = omit,
user_id: str | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -360,10 +380,10 @@ def invoke(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> JourneysInvokeResponse:
- """Invoke a journey by id or alias to start a new run.
+ """Starts a journey run for one user and returns a runId.
- The response includes a
- `runId` identifying the run.
+ Runs execute
+ asynchronously, so the response arrives before any message is sent.
Args:
data: Data payload passed to the journey. The expected shape can be predefined using
@@ -391,6 +411,15 @@ def invoke(
"""
if not template_id:
raise ValueError(f"Expected a non-empty value for `template_id` but received {template_id!r}")
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return self._post(
path_template("/journeys/{template_id}/invoke", template_id=template_id),
body=maybe_transform(
@@ -419,7 +448,8 @@ def list_versions(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> JourneyVersionsListResponse:
"""
- List published versions of a journey, ordered most recent first.
+ Lists a journey's published versions, most recent first, so you have a version
+ id to roll back to. Paged by cursor.
Args:
extra_headers: Send extra headers
@@ -445,6 +475,8 @@ def publish(
template_id: str,
*,
version: str | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -452,11 +484,9 @@ def publish(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> JourneyResponse:
- """Publish the current draft as a new version.
-
- Body is optional; pass
- `{ "version": "vN" }` to roll back to a prior version instead. Returns 404 if
- the journey has no draft to publish.
+ """
+ Publishes a journey's current draft as a new version, making it live for new
+ runs. Pass a version instead to roll back to an earlier one.
Args:
extra_headers: Send extra headers
@@ -469,6 +499,15 @@ def publish(
"""
if not template_id:
raise ValueError(f"Expected a non-empty value for `template_id` but received {template_id!r}")
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return self._post(
path_template("/journeys/{template_id}/publish", template_id=template_id),
body=maybe_transform({"version": version}, journey_publish_params.JourneyPublishParams),
@@ -493,13 +532,9 @@ def replace(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> JourneyResponse:
- """Replace the journey draft.
-
- Updates the working draft only; call
- `POST /journeys/{templateId}/publish` to make it live, or pass
- `state: "PUBLISHED"` in this request to publish immediately. Send-node
- `template` ids must already exist and be scoped to this journey, and node ids
- must not be claimed by another journey.
+ """
+ Replaces a journey's working draft, leaving the published version live until you
+ publish. Reach for this when editing a journey already running.
Args:
state: Lifecycle state of a journey.
@@ -533,8 +568,15 @@ def replace(
class AsyncJourneysResource(AsyncAPIResource):
+ """
+ Build, version, publish, invoke, and cancel multi-step notification workflows, along with the templates scoped to them.
+ """
+
@cached_property
def templates(self) -> AsyncTemplatesResource:
+ """
+ Build, version, publish, invoke, and cancel multi-step notification workflows, along with the templates scoped to them.
+ """
return AsyncTemplatesResource(self._client)
@cached_property
@@ -563,6 +605,8 @@ async def create(
nodes: Iterable[JourneyNodeParam],
enabled: bool | Omit = omit,
state: JourneyState | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -570,14 +614,9 @@ async def create(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> JourneyResponse:
- """Create a journey.
-
- Defaults to `DRAFT` state; pass `state: "PUBLISHED"` to
- publish on create. Send nodes are not allowed on `POST`. The standard flow is:
- create the journey shell here, add notification templates with
- `POST /journeys/{templateId}/templates`, then wire them into the journey with
- `PUT /journeys/{templateId}`. Call `POST /journeys/{templateId}/publish` to
- publish a draft after the fact.
+ """
+ Creates a journey from a set of nodes, in draft state unless you pass a
+ published state. Send nodes cannot be included until their templates exist.
Args:
state: Lifecycle state of a journey.
@@ -590,6 +629,15 @@ async def create(
timeout: Override the client-level default timeout for this request, in seconds
"""
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return await self._post(
"/journeys",
body=await async_maybe_transform(
@@ -661,12 +709,12 @@ async def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> JourneysListResponse:
- """Get the list of journeys.
+ """
+ Lists the workspace's journeys, each carrying a name, state, and enabled flag.
+ Paged by cursor.
Args:
- cursor: A cursor token for pagination.
-
- Use the cursor from the previous response to
+ cursor: A cursor token for pagination. Use the cursor from the previous response to
fetch the next page of results.
version: The version of journeys to retrieve. Accepted values are published (for
@@ -709,10 +757,10 @@ async def archive(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Archive a journey.
+ """Archives a journey so it can no longer be invoked.
- Archived journeys cannot be invoked. Existing journey runs
- continue to completion.
+ Runs already in flight
+ continue to completion, so archiving never strands a user mid-sequence.
Args:
extra_headers: Send extra headers
@@ -739,6 +787,8 @@ async def cancel(
self,
*,
cancelation_token: str,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -746,14 +796,9 @@ async def cancel(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> CancelJourneyResponse:
- """Cancel journey runs.
-
- The request body must include EXACTLY ONE of
- `cancelation_token` (cancels every run associated with the token) or `run_id`
- (cancels a single tenant-scoped run). Supplying both or neither returns a `400`.
- A `run_id` that does not match a run for the tenant returns `404`. Cancelation
- is idempotent: a run that has already finished (`PROCESSED`/`ERROR`) or was
- already `CANCELED` is left unchanged and its current status is returned.
+ """
+ Cancels in-flight journey runs, either every run sharing a cancelation token or
+ one run by id. Use it to stop a sequence when the event resolves.
Args:
extra_headers: Send extra headers
@@ -771,6 +816,8 @@ async def cancel(
self,
*,
run_id: str,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -778,14 +825,9 @@ async def cancel(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> CancelJourneyResponse:
- """Cancel journey runs.
-
- The request body must include EXACTLY ONE of
- `cancelation_token` (cancels every run associated with the token) or `run_id`
- (cancels a single tenant-scoped run). Supplying both or neither returns a `400`.
- A `run_id` that does not match a run for the tenant returns `404`. Cancelation
- is idempotent: a run that has already finished (`PROCESSED`/`ERROR`) or was
- already `CANCELED` is left unchanged and its current status is returned.
+ """
+ Cancels in-flight journey runs, either every run sharing a cancelation token or
+ one run by id. Use it to stop a sequence when the event resolves.
Args:
extra_headers: Send extra headers
@@ -803,6 +845,8 @@ async def cancel(
self,
*,
cancelation_token: str | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: str | Omit = omit,
run_id: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
@@ -811,6 +855,15 @@ async def cancel(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> CancelJourneyResponse:
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return cast(
CancelJourneyResponse,
await self._post(
@@ -838,6 +891,8 @@ async def invoke(
data: Dict[str, object] | Omit = omit,
profile: Dict[str, object] | Omit = omit,
user_id: str | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -845,10 +900,10 @@ async def invoke(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> JourneysInvokeResponse:
- """Invoke a journey by id or alias to start a new run.
+ """Starts a journey run for one user and returns a runId.
- The response includes a
- `runId` identifying the run.
+ Runs execute
+ asynchronously, so the response arrives before any message is sent.
Args:
data: Data payload passed to the journey. The expected shape can be predefined using
@@ -876,6 +931,15 @@ async def invoke(
"""
if not template_id:
raise ValueError(f"Expected a non-empty value for `template_id` but received {template_id!r}")
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return await self._post(
path_template("/journeys/{template_id}/invoke", template_id=template_id),
body=await async_maybe_transform(
@@ -904,7 +968,8 @@ async def list_versions(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> JourneyVersionsListResponse:
"""
- List published versions of a journey, ordered most recent first.
+ Lists a journey's published versions, most recent first, so you have a version
+ id to roll back to. Paged by cursor.
Args:
extra_headers: Send extra headers
@@ -930,6 +995,8 @@ async def publish(
template_id: str,
*,
version: str | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -937,11 +1004,9 @@ async def publish(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> JourneyResponse:
- """Publish the current draft as a new version.
-
- Body is optional; pass
- `{ "version": "vN" }` to roll back to a prior version instead. Returns 404 if
- the journey has no draft to publish.
+ """
+ Publishes a journey's current draft as a new version, making it live for new
+ runs. Pass a version instead to roll back to an earlier one.
Args:
extra_headers: Send extra headers
@@ -954,6 +1019,15 @@ async def publish(
"""
if not template_id:
raise ValueError(f"Expected a non-empty value for `template_id` but received {template_id!r}")
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return await self._post(
path_template("/journeys/{template_id}/publish", template_id=template_id),
body=await async_maybe_transform({"version": version}, journey_publish_params.JourneyPublishParams),
@@ -978,13 +1052,9 @@ async def replace(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> JourneyResponse:
- """Replace the journey draft.
-
- Updates the working draft only; call
- `POST /journeys/{templateId}/publish` to make it live, or pass
- `state: "PUBLISHED"` in this request to publish immediately. Send-node
- `template` ids must already exist and be scoped to this journey, and node ids
- must not be claimed by another journey.
+ """
+ Replaces a journey's working draft, leaving the published version live until you
+ publish. Reach for this when editing a journey already running.
Args:
state: Lifecycle state of a journey.
@@ -1051,6 +1121,9 @@ def __init__(self, journeys: JourneysResource) -> None:
@cached_property
def templates(self) -> TemplatesResourceWithRawResponse:
+ """
+ Build, version, publish, invoke, and cancel multi-step notification workflows, along with the templates scoped to them.
+ """
return TemplatesResourceWithRawResponse(self._journeys.templates)
@@ -1088,6 +1161,9 @@ def __init__(self, journeys: AsyncJourneysResource) -> None:
@cached_property
def templates(self) -> AsyncTemplatesResourceWithRawResponse:
+ """
+ Build, version, publish, invoke, and cancel multi-step notification workflows, along with the templates scoped to them.
+ """
return AsyncTemplatesResourceWithRawResponse(self._journeys.templates)
@@ -1125,6 +1201,9 @@ def __init__(self, journeys: JourneysResource) -> None:
@cached_property
def templates(self) -> TemplatesResourceWithStreamingResponse:
+ """
+ Build, version, publish, invoke, and cancel multi-step notification workflows, along with the templates scoped to them.
+ """
return TemplatesResourceWithStreamingResponse(self._journeys.templates)
@@ -1162,4 +1241,7 @@ def __init__(self, journeys: AsyncJourneysResource) -> None:
@cached_property
def templates(self) -> AsyncTemplatesResourceWithStreamingResponse:
+ """
+ Build, version, publish, invoke, and cancel multi-step notification workflows, along with the templates scoped to them.
+ """
return AsyncTemplatesResourceWithStreamingResponse(self._journeys.templates)
diff --git a/src/courier/resources/journeys/templates.py b/src/courier/resources/journeys/templates.py
index cb716dca..b030ff6c 100644
--- a/src/courier/resources/journeys/templates.py
+++ b/src/courier/resources/journeys/templates.py
@@ -8,7 +8,7 @@
from ...types import NotificationTemplateState
from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
-from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import (
@@ -38,6 +38,10 @@
class TemplatesResource(SyncAPIResource):
+ """
+ Build, version, publish, invoke, and cancel multi-step notification workflows, along with the templates scoped to them.
+ """
+
@cached_property
def with_raw_response(self) -> TemplatesResourceWithRawResponse:
"""
@@ -65,6 +69,8 @@ def create(
notification: template_create_params.Notification,
provider_key: str | Omit = omit,
state: str | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -88,6 +94,15 @@ def create(
"""
if not template_id:
raise ValueError(f"Expected a non-empty value for `template_id` but received {template_id!r}")
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return self._post(
path_template("/journeys/{template_id}/templates", template_id=template_id),
body=maybe_transform(
@@ -117,11 +132,9 @@ def retrieve(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> JourneyTemplateGetResponse:
- """Fetch a journey-scoped notification template by id.
-
- Pass `?version=draft`
- (default `published`) to retrieve the working draft, or `?version=vN` for a
- historical version.
+ """
+ Returns a journey's own notification template with its name, brand, subscription
+ topic, and content. Defaults to the published version.
Args:
extra_headers: Send extra headers
@@ -211,10 +224,10 @@ def archive(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Archive the journey-scoped notification template.
+ """Archives one journey's notification template, preventing further sends.
- Archived templates cannot be
- sent.
+ Detach
+ any send node referencing it beforehand.
Args:
extra_headers: Send extra headers
@@ -255,8 +268,8 @@ def list_versions(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> NotificationTemplateVersionListResponse:
"""
- List published versions of the journey-scoped notification template, ordered
- most recent first.
+ Lists the published versions of a template that belongs to a journey, most
+ recent first. Paged by cursor.
Args:
extra_headers: Send extra headers
@@ -289,6 +302,8 @@ def publish(
*,
template_id: str,
version: str | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -296,10 +311,10 @@ def publish(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """
- Publish the current draft of the journey-scoped notification template as a new
- version. Optionally roll back to a prior version by passing
- `{ "version": "vN" }`.
+ """Publishes a journey-scoped template's draft as a new version.
+
+ Pass a version
+ instead to roll back the template to an earlier publish.
Args:
extra_headers: Send extra headers
@@ -315,6 +330,15 @@ def publish(
if not notification_id:
raise ValueError(f"Expected a non-empty value for `notification_id` but received {notification_id!r}")
extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return self._post(
path_template(
"/journeys/{template_id}/templates/{notification_id}/publish",
@@ -455,8 +479,10 @@ def replace(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> JourneyTemplateGetResponse:
- """
- Replace the journey-scoped notification template draft.
+ """Replaces the draft content of one journey's notification template.
+
+ Publish it
+ before send nodes referencing it render the change.
Args:
extra_headers: Send extra headers
@@ -503,13 +529,9 @@ def retrieve_content(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> NotificationContentGetResponse:
- """Retrieve the elemental content of a journey-scoped notification template.
-
- The
- response contains the versioned elements along with their content checksums,
- which can be used to detect changes between versions. Pass `?version=draft`
- (default `published`) to retrieve the working draft, or `?version=vN` for a
- historical version.
+ """
+ Returns the Elemental elements and version of a journey-scoped template's
+ content. Compare versions to see what changed between publishes.
Args:
version: Accepts `draft`, `published`, or a version string (e.g., `v001`). Defaults to
@@ -547,6 +569,10 @@ def retrieve_content(
class AsyncTemplatesResource(AsyncAPIResource):
+ """
+ Build, version, publish, invoke, and cancel multi-step notification workflows, along with the templates scoped to them.
+ """
+
@cached_property
def with_raw_response(self) -> AsyncTemplatesResourceWithRawResponse:
"""
@@ -574,6 +600,8 @@ async def create(
notification: template_create_params.Notification,
provider_key: str | Omit = omit,
state: str | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -597,6 +625,15 @@ async def create(
"""
if not template_id:
raise ValueError(f"Expected a non-empty value for `template_id` but received {template_id!r}")
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return await self._post(
path_template("/journeys/{template_id}/templates", template_id=template_id),
body=await async_maybe_transform(
@@ -626,11 +663,9 @@ async def retrieve(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> JourneyTemplateGetResponse:
- """Fetch a journey-scoped notification template by id.
-
- Pass `?version=draft`
- (default `published`) to retrieve the working draft, or `?version=vN` for a
- historical version.
+ """
+ Returns a journey's own notification template with its name, brand, subscription
+ topic, and content. Defaults to the published version.
Args:
extra_headers: Send extra headers
@@ -720,10 +755,10 @@ async def archive(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Archive the journey-scoped notification template.
+ """Archives one journey's notification template, preventing further sends.
- Archived templates cannot be
- sent.
+ Detach
+ any send node referencing it beforehand.
Args:
extra_headers: Send extra headers
@@ -764,8 +799,8 @@ async def list_versions(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> NotificationTemplateVersionListResponse:
"""
- List published versions of the journey-scoped notification template, ordered
- most recent first.
+ Lists the published versions of a template that belongs to a journey, most
+ recent first. Paged by cursor.
Args:
extra_headers: Send extra headers
@@ -798,6 +833,8 @@ async def publish(
*,
template_id: str,
version: str | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -805,10 +842,10 @@ async def publish(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """
- Publish the current draft of the journey-scoped notification template as a new
- version. Optionally roll back to a prior version by passing
- `{ "version": "vN" }`.
+ """Publishes a journey-scoped template's draft as a new version.
+
+ Pass a version
+ instead to roll back the template to an earlier publish.
Args:
extra_headers: Send extra headers
@@ -824,6 +861,15 @@ async def publish(
if not notification_id:
raise ValueError(f"Expected a non-empty value for `notification_id` but received {notification_id!r}")
extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return await self._post(
path_template(
"/journeys/{template_id}/templates/{notification_id}/publish",
@@ -964,8 +1010,10 @@ async def replace(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> JourneyTemplateGetResponse:
- """
- Replace the journey-scoped notification template draft.
+ """Replaces the draft content of one journey's notification template.
+
+ Publish it
+ before send nodes referencing it render the change.
Args:
extra_headers: Send extra headers
@@ -1012,13 +1060,9 @@ async def retrieve_content(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> NotificationContentGetResponse:
- """Retrieve the elemental content of a journey-scoped notification template.
-
- The
- response contains the versioned elements along with their content checksums,
- which can be used to detect changes between versions. Pass `?version=draft`
- (default `published`) to retrieve the working draft, or `?version=vN` for a
- historical version.
+ """
+ Returns the Elemental elements and version of a journey-scoped template's
+ content. Compare versions to see what changed between publishes.
Args:
version: Accepts `draft`, `published`, or a version string (e.g., `v001`). Defaults to
diff --git a/src/courier/resources/lists/lists.py b/src/courier/resources/lists/lists.py
index ae71b29b..95b43ebd 100644
--- a/src/courier/resources/lists/lists.py
+++ b/src/courier/resources/lists/lists.py
@@ -34,8 +34,15 @@
class ListsResource(SyncAPIResource):
+ """
+ Manage static groups of users that you subscribe explicitly, and send to them by list id or list pattern.
+ """
+
@cached_property
def subscriptions(self) -> SubscriptionsResource:
+ """
+ Manage static groups of users that you subscribe explicitly, and send to them by list id or list pattern.
+ """
return SubscriptionsResource(self._client)
@cached_property
@@ -68,8 +75,10 @@ def retrieve(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> SubscriptionList:
- """
- Returns a list based on the list ID provided.
+ """Returns one list by id with its name and created and updated timestamps.
+
+ Fetch
+ its subscribers separately with the subscriptions endpoint.
Args:
extra_headers: Send extra headers
@@ -103,8 +112,10 @@ def update(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """
- Create or replace an existing list with the supplied values.
+ """Creates or replaces a list from a name and preferences.
+
+ Subscribers are managed
+ through the separate subscriptions endpoints.
Args:
extra_headers: Send extra headers
@@ -146,7 +157,8 @@ def list(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ListListResponse:
"""
- Returns all of the lists, with the ability to filter based on a pattern.
+ Returns the workspace's lists, filterable by a pattern to fetch a subset such as
+ every regional list. Paged by cursor.
Args:
cursor: A unique identifier that allows for fetching the next page of lists.
@@ -194,8 +206,10 @@ def delete(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """
- Delete a list by list ID.
+ """Deletes a list, halting sends that target it.
+
+ A previously deleted list can be
+ brought back with the companion restore endpoint.
Args:
extra_headers: Send extra headers
@@ -229,7 +243,8 @@ def restore(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
"""
- Restore a previously deleted list.
+ Restores a previously deleted list along with its subscribers, so a list removed
+ by mistake can be brought back rather than rebuilt.
Args:
extra_headers: Send extra headers
@@ -253,8 +268,15 @@ def restore(
class AsyncListsResource(AsyncAPIResource):
+ """
+ Manage static groups of users that you subscribe explicitly, and send to them by list id or list pattern.
+ """
+
@cached_property
def subscriptions(self) -> AsyncSubscriptionsResource:
+ """
+ Manage static groups of users that you subscribe explicitly, and send to them by list id or list pattern.
+ """
return AsyncSubscriptionsResource(self._client)
@cached_property
@@ -287,8 +309,10 @@ async def retrieve(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> SubscriptionList:
- """
- Returns a list based on the list ID provided.
+ """Returns one list by id with its name and created and updated timestamps.
+
+ Fetch
+ its subscribers separately with the subscriptions endpoint.
Args:
extra_headers: Send extra headers
@@ -322,8 +346,10 @@ async def update(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """
- Create or replace an existing list with the supplied values.
+ """Creates or replaces a list from a name and preferences.
+
+ Subscribers are managed
+ through the separate subscriptions endpoints.
Args:
extra_headers: Send extra headers
@@ -365,7 +391,8 @@ async def list(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ListListResponse:
"""
- Returns all of the lists, with the ability to filter based on a pattern.
+ Returns the workspace's lists, filterable by a pattern to fetch a subset such as
+ every regional list. Paged by cursor.
Args:
cursor: A unique identifier that allows for fetching the next page of lists.
@@ -413,8 +440,10 @@ async def delete(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """
- Delete a list by list ID.
+ """Deletes a list, halting sends that target it.
+
+ A previously deleted list can be
+ brought back with the companion restore endpoint.
Args:
extra_headers: Send extra headers
@@ -448,7 +477,8 @@ async def restore(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
"""
- Restore a previously deleted list.
+ Restores a previously deleted list along with its subscribers, so a list removed
+ by mistake can be brought back rather than rebuilt.
Args:
extra_headers: Send extra headers
@@ -493,6 +523,9 @@ def __init__(self, lists: ListsResource) -> None:
@cached_property
def subscriptions(self) -> SubscriptionsResourceWithRawResponse:
+ """
+ Manage static groups of users that you subscribe explicitly, and send to them by list id or list pattern.
+ """
return SubscriptionsResourceWithRawResponse(self._lists.subscriptions)
@@ -518,6 +551,9 @@ def __init__(self, lists: AsyncListsResource) -> None:
@cached_property
def subscriptions(self) -> AsyncSubscriptionsResourceWithRawResponse:
+ """
+ Manage static groups of users that you subscribe explicitly, and send to them by list id or list pattern.
+ """
return AsyncSubscriptionsResourceWithRawResponse(self._lists.subscriptions)
@@ -543,6 +579,9 @@ def __init__(self, lists: ListsResource) -> None:
@cached_property
def subscriptions(self) -> SubscriptionsResourceWithStreamingResponse:
+ """
+ Manage static groups of users that you subscribe explicitly, and send to them by list id or list pattern.
+ """
return SubscriptionsResourceWithStreamingResponse(self._lists.subscriptions)
@@ -568,4 +607,7 @@ def __init__(self, lists: AsyncListsResource) -> None:
@cached_property
def subscriptions(self) -> AsyncSubscriptionsResourceWithStreamingResponse:
+ """
+ Manage static groups of users that you subscribe explicitly, and send to them by list id or list pattern.
+ """
return AsyncSubscriptionsResourceWithStreamingResponse(self._lists.subscriptions)
diff --git a/src/courier/resources/lists/subscriptions.py b/src/courier/resources/lists/subscriptions.py
index 74034f4b..a9b5ef55 100644
--- a/src/courier/resources/lists/subscriptions.py
+++ b/src/courier/resources/lists/subscriptions.py
@@ -7,7 +7,7 @@
import httpx
from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
-from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import (
@@ -31,6 +31,10 @@
class SubscriptionsResource(SyncAPIResource):
+ """
+ Manage static groups of users that you subscribe explicitly, and send to them by list id or list pattern.
+ """
+
@cached_property
def with_raw_response(self) -> SubscriptionsResourceWithRawResponse:
"""
@@ -63,7 +67,8 @@ def list(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> SubscriptionListResponse:
"""
- Get the list's subscriptions.
+ Returns the users subscribed to a list with paging, each with the preferences
+ recorded for that subscription.
Args:
cursor: A unique identifier that allows for fetching the next set of list subscriptions
@@ -95,6 +100,8 @@ def add(
list_id: str,
*,
recipients: Iterable[PutSubscriptionsRecipientParam],
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -118,6 +125,15 @@ def add(
if not list_id:
raise ValueError(f"Expected a non-empty value for `list_id` but received {list_id!r}")
extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return self._post(
path_template("/lists/{list_id}/subscriptions", list_id=list_id),
body=maybe_transform({"recipients": recipients}, subscription_add_params.SubscriptionAddParams),
@@ -179,8 +195,8 @@ def subscribe_user(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
"""
- Subscribe a user to an existing list (note: if the List does not exist, it will
- be automatically created).
+ Subscribes one user to a list, creating the list if it does not yet exist.
+ Optional preferences apply to this subscription only.
Args:
extra_headers: Send extra headers
@@ -219,8 +235,10 @@ def unsubscribe_user(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """
- Delete a subscription to a list by list ID and user ID.
+ """Removes one user's subscription to a list, addressed by list id and user id.
+
+ The
+ user's profile and other subscriptions are separate resources.
Args:
extra_headers: Send extra headers
@@ -246,6 +264,10 @@ def unsubscribe_user(
class AsyncSubscriptionsResource(AsyncAPIResource):
+ """
+ Manage static groups of users that you subscribe explicitly, and send to them by list id or list pattern.
+ """
+
@cached_property
def with_raw_response(self) -> AsyncSubscriptionsResourceWithRawResponse:
"""
@@ -278,7 +300,8 @@ async def list(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> SubscriptionListResponse:
"""
- Get the list's subscriptions.
+ Returns the users subscribed to a list with paging, each with the preferences
+ recorded for that subscription.
Args:
cursor: A unique identifier that allows for fetching the next set of list subscriptions
@@ -310,6 +333,8 @@ async def add(
list_id: str,
*,
recipients: Iterable[PutSubscriptionsRecipientParam],
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -333,6 +358,15 @@ async def add(
if not list_id:
raise ValueError(f"Expected a non-empty value for `list_id` but received {list_id!r}")
extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return await self._post(
path_template("/lists/{list_id}/subscriptions", list_id=list_id),
body=await async_maybe_transform({"recipients": recipients}, subscription_add_params.SubscriptionAddParams),
@@ -396,8 +430,8 @@ async def subscribe_user(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
"""
- Subscribe a user to an existing list (note: if the List does not exist, it will
- be automatically created).
+ Subscribes one user to a list, creating the list if it does not yet exist.
+ Optional preferences apply to this subscription only.
Args:
extra_headers: Send extra headers
@@ -436,8 +470,10 @@ async def unsubscribe_user(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """
- Delete a subscription to a list by list ID and user ID.
+ """Removes one user's subscription to a list, addressed by list id and user id.
+
+ The
+ user's profile and other subscriptions are separate resources.
Args:
extra_headers: Send extra headers
diff --git a/src/courier/resources/messages.py b/src/courier/resources/messages.py
index c8c87ce9..9784b2b2 100644
--- a/src/courier/resources/messages.py
+++ b/src/courier/resources/messages.py
@@ -29,6 +29,10 @@
class MessagesResource(SyncAPIResource):
+ """
+ Look up the messages Courier has accepted, inspect their delivery history and rendered output, and cancel, resend, or archive them.
+ """
+
@cached_property
def with_raw_response(self) -> MessagesResourceWithRawResponse:
"""
@@ -60,7 +64,8 @@ def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> MessageRetrieveResponse:
"""
- Fetch the status of a message you've previously sent.
+ Returns a sent message's status, recipient, event, and per-provider delivery
+ detail, with timestamps for enqueued, sent, delivered, opened, and clicked.
Args:
extra_headers: Send extra headers
@@ -106,7 +111,8 @@ def list(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> MessageListResponse:
"""
- Fetch the statuses of messages you've previously sent.
+ Returns previously sent messages, most recent first, each carrying its status,
+ recipient, channel, and provider. Paged by cursor.
Args:
archived: A boolean value that indicates whether archived messages should be included in
@@ -195,13 +201,9 @@ def cancel(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> MessageDetails:
- """Cancel a message that is currently in the process of being delivered.
-
- A
- well-formatted API call to the cancel message API will return either `200`
- status code for a successful cancellation or `409` status code for an
- unsuccessful cancellation. Both cases will include the actual message record in
- the response body (see details below).
+ """
+ Cancels a message that is still in the delivery pipeline and returns the message
+ record with its resulting canceled or failed status.
Args:
extra_headers: Send extra headers
@@ -234,7 +236,8 @@ def content(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> MessageContentResponse:
"""
- Get message content
+ Returns the rendered content Courier delivered for a message, broken out per
+ channel, to confirm what the recipient received.
Args:
extra_headers: Send extra headers
@@ -268,7 +271,8 @@ def history(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> MessageHistoryResponse:
"""
- Fetch the array of events of a message you've previously sent.
+ Returns the ordered event history for a sent message, one entry per status
+ transition with its timestamp.
Args:
type: A supported Message History type that will filter the events returned.
@@ -306,13 +310,9 @@ def resend(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> MessageResendResponse:
- """Resend a previously sent message.
-
- The original send request is loaded from
- storage and a brand-new send is enqueued for the same recipient and content,
- producing a **new** `messageId` — the original message is not modified.
- Throttled by a per-message rate limit; a repeat inside the limit window returns
- `429 Too Many Requests`.
+ """
+ Resends a previously sent message to the same recipient and content, returning a
+ new messageId. The original send request is not modified.
Args:
extra_headers: Send extra headers
@@ -335,6 +335,10 @@ def resend(
class AsyncMessagesResource(AsyncAPIResource):
+ """
+ Look up the messages Courier has accepted, inspect their delivery history and rendered output, and cancel, resend, or archive them.
+ """
+
@cached_property
def with_raw_response(self) -> AsyncMessagesResourceWithRawResponse:
"""
@@ -366,7 +370,8 @@ async def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> MessageRetrieveResponse:
"""
- Fetch the status of a message you've previously sent.
+ Returns a sent message's status, recipient, event, and per-provider delivery
+ detail, with timestamps for enqueued, sent, delivered, opened, and clicked.
Args:
extra_headers: Send extra headers
@@ -412,7 +417,8 @@ async def list(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> MessageListResponse:
"""
- Fetch the statuses of messages you've previously sent.
+ Returns previously sent messages, most recent first, each carrying its status,
+ recipient, channel, and provider. Paged by cursor.
Args:
archived: A boolean value that indicates whether archived messages should be included in
@@ -501,13 +507,9 @@ async def cancel(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> MessageDetails:
- """Cancel a message that is currently in the process of being delivered.
-
- A
- well-formatted API call to the cancel message API will return either `200`
- status code for a successful cancellation or `409` status code for an
- unsuccessful cancellation. Both cases will include the actual message record in
- the response body (see details below).
+ """
+ Cancels a message that is still in the delivery pipeline and returns the message
+ record with its resulting canceled or failed status.
Args:
extra_headers: Send extra headers
@@ -540,7 +542,8 @@ async def content(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> MessageContentResponse:
"""
- Get message content
+ Returns the rendered content Courier delivered for a message, broken out per
+ channel, to confirm what the recipient received.
Args:
extra_headers: Send extra headers
@@ -574,7 +577,8 @@ async def history(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> MessageHistoryResponse:
"""
- Fetch the array of events of a message you've previously sent.
+ Returns the ordered event history for a sent message, one entry per status
+ transition with its timestamp.
Args:
type: A supported Message History type that will filter the events returned.
@@ -612,13 +616,9 @@ async def resend(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> MessageResendResponse:
- """Resend a previously sent message.
-
- The original send request is loaded from
- storage and a brand-new send is enqueued for the same recipient and content,
- producing a **new** `messageId` — the original message is not modified.
- Throttled by a per-message rate limit; a repeat inside the limit window returns
- `429 Too Many Requests`.
+ """
+ Resends a previously sent message to the same recipient and content, returning a
+ new messageId. The original send request is not modified.
Args:
extra_headers: Send extra headers
diff --git a/src/courier/resources/notifications/checks.py b/src/courier/resources/notifications/checks.py
index b619de48..d26396c5 100644
--- a/src/courier/resources/notifications/checks.py
+++ b/src/courier/resources/notifications/checks.py
@@ -26,6 +26,10 @@
class ChecksResource(SyncAPIResource):
+ """
+ Create, update, version, publish, and localize notification templates and their content.
+ """
+
@cached_property
def with_raw_response(self) -> ChecksResourceWithRawResponse:
"""
@@ -59,7 +63,8 @@ def update(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> CheckUpdateResponse:
"""
- Replace the submission checks for a notification template.
+ Replaces the approval checks on a template submission with the complete set
+ supplied in the request body.
Args:
extra_headers: Send extra headers
@@ -96,7 +101,8 @@ def list(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> CheckListResponse:
"""
- Retrieve the submission checks for a notification template.
+ Returns the approval checks recorded for a template submission, each with its
+ pass or fail result.
Args:
extra_headers: Send extra headers
@@ -131,8 +137,10 @@ def delete(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """
- Cancel a submission for a notification template.
+ """Cancels a pending template submission, withdrawing it from the approval
+ workflow.
+
+ The template stays in draft and can be resubmitted later.
Args:
extra_headers: Send extra headers
@@ -158,6 +166,10 @@ def delete(
class AsyncChecksResource(AsyncAPIResource):
+ """
+ Create, update, version, publish, and localize notification templates and their content.
+ """
+
@cached_property
def with_raw_response(self) -> AsyncChecksResourceWithRawResponse:
"""
@@ -191,7 +203,8 @@ async def update(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> CheckUpdateResponse:
"""
- Replace the submission checks for a notification template.
+ Replaces the approval checks on a template submission with the complete set
+ supplied in the request body.
Args:
extra_headers: Send extra headers
@@ -228,7 +241,8 @@ async def list(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> CheckListResponse:
"""
- Retrieve the submission checks for a notification template.
+ Returns the approval checks recorded for a template submission, each with its
+ pass or fail result.
Args:
extra_headers: Send extra headers
@@ -263,8 +277,10 @@ async def delete(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """
- Cancel a submission for a notification template.
+ """Cancels a pending template submission, withdrawing it from the approval
+ workflow.
+
+ The template stays in draft and can be resubmitted later.
Args:
extra_headers: Send extra headers
diff --git a/src/courier/resources/notifications/notifications.py b/src/courier/resources/notifications/notifications.py
index a479a76d..3c772f94 100644
--- a/src/courier/resources/notifications/notifications.py
+++ b/src/courier/resources/notifications/notifications.py
@@ -29,7 +29,7 @@
notification_retrieve_content_params,
)
from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, omit, not_given
-from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import (
@@ -51,8 +51,15 @@
class NotificationsResource(SyncAPIResource):
+ """
+ Create, update, version, publish, and localize notification templates and their content.
+ """
+
@cached_property
def checks(self) -> ChecksResource:
+ """
+ Create, update, version, publish, and localize notification templates and their content.
+ """
return ChecksResource(self._client)
@cached_property
@@ -79,6 +86,8 @@ def create(
*,
notification: NotificationTemplatePayloadParam,
state: Literal["DRAFT", "PUBLISHED"] | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -106,6 +115,15 @@ def create(
timeout: Override the client-level default timeout for this request, in seconds
"""
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return self._post(
"/notifications",
body=maybe_transform(
@@ -177,8 +195,10 @@ def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> NotificationListResponse:
- """
- List notification templates in your workspace.
+ """Lists the workspace's notification templates.
+
+ Each carries a name, tags, brand,
+ routing, and its draft or published state.
Args:
cursor: Opaque pagination cursor from a previous response. Omit for the first page.
@@ -225,8 +245,10 @@ def archive(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """
- Archive a notification template.
+ """Archives a notification template, preventing new sends from referencing it.
+
+ The
+ template stays retrievable for its version history.
Args:
extra_headers: Send extra headers
@@ -259,14 +281,10 @@ def duplicate(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> NotificationTemplateResponse:
- """Duplicate a notification template.
-
- Creates a standalone copy within the same
- workspace and environment, with " COPY" appended to the title. The copy clones
- the source draft's tags, brand, subscription topic, routing strategy, channels,
- and content, and is always created as a standalone template (it is not linked to
- any journey or broadcast, even if the source was). Templates that are scoped to
- a journey or a broadcast cannot be duplicated through this endpoint.
+ """
+ Copies a notification template within the same workspace and environment,
+ appending " COPY" to the title. The copy is standalone and independently
+ editable.
Args:
extra_headers: Send extra headers
@@ -301,7 +319,8 @@ def list_versions(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> NotificationTemplateVersionListResponse:
"""
- List versions of a notification template.
+ Returns a notification template's published versions, most recent first, for
+ comparison or rollback. Paged.
Args:
cursor: Opaque pagination cursor from a previous response. Omit for the first page.
@@ -341,6 +360,8 @@ def publish(
id: str,
*,
version: str | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -367,6 +388,15 @@ def publish(
if not id:
raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return self._post(
path_template("/notifications/{id}/publish", id=id),
body=maybe_transform({"version": version}, notification_publish_params.NotificationPublishParams),
@@ -389,11 +419,10 @@ def put_content(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> NotificationContentMutationResponse:
- """Replace the elemental content of a notification template.
+ """Replaces all Elemental content in a template, overwriting every existing
+ element.
- Overwrites all
- elements in the template with the provided content. Only supported for V2
- (elemental) templates.
+ Supported for V2 templates only, not V1 blocks and channels.
Args:
content: Elemental content payload. The server defaults `version` when omitted.
@@ -444,10 +473,9 @@ def put_element(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> NotificationContentMutationResponse:
- """Update a single element within a notification template.
-
- Only supported for V2
- (elemental) templates.
+ """
+ Replaces one Elemental element in a template, addressed by its element id.
+ Supported for V2 templates only, not V1 blocks and channels.
Args:
type: Element type (text, meta, action, image, etc.).
@@ -500,11 +528,10 @@ def put_locale(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> NotificationContentMutationResponse:
- """Set locale-specific content overrides for a notification template.
+ """Sets locale-specific content overrides for a template.
- Each element
- override must reference an existing element by ID. Only supported for V2
- (elemental) templates.
+ Each override must
+ reference an element that already exists in the default content.
Args:
elements: Elements with locale-specific content overrides.
@@ -551,9 +578,9 @@ def replace(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> NotificationTemplateResponse:
- """Replace a notification template.
-
- All fields are required.
+ """
+ Replaces a notification template in full, so send every field rather than only
+ the ones you want changed. Publish separately to make it live.
Args:
notification: Core template fields used in POST and PUT request bodies (nested under a
@@ -599,12 +626,10 @@ def retrieve_content(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> NotificationRetrieveContentResponse:
- """Retrieve the content of a notification template.
+ """Returns a template's content and checksum.
- The response shape depends on
- whether the template uses V1 (blocks/channels) or V2 (elemental) content. Use
- the `version` query parameter to select draft, published, or a specific
- historical version.
+ V2 templates return Elemental
+ elements, while V1 templates return blocks and channels instead.
Args:
version: Accepts `draft`, `published`, or a version string (e.g., `v001`). Defaults to
@@ -641,8 +666,15 @@ def retrieve_content(
class AsyncNotificationsResource(AsyncAPIResource):
+ """
+ Create, update, version, publish, and localize notification templates and their content.
+ """
+
@cached_property
def checks(self) -> AsyncChecksResource:
+ """
+ Create, update, version, publish, and localize notification templates and their content.
+ """
return AsyncChecksResource(self._client)
@cached_property
@@ -669,6 +701,8 @@ async def create(
*,
notification: NotificationTemplatePayloadParam,
state: Literal["DRAFT", "PUBLISHED"] | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -696,6 +730,15 @@ async def create(
timeout: Override the client-level default timeout for this request, in seconds
"""
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return await self._post(
"/notifications",
body=await async_maybe_transform(
@@ -769,8 +812,10 @@ async def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> NotificationListResponse:
- """
- List notification templates in your workspace.
+ """Lists the workspace's notification templates.
+
+ Each carries a name, tags, brand,
+ routing, and its draft or published state.
Args:
cursor: Opaque pagination cursor from a previous response. Omit for the first page.
@@ -817,8 +862,10 @@ async def archive(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """
- Archive a notification template.
+ """Archives a notification template, preventing new sends from referencing it.
+
+ The
+ template stays retrievable for its version history.
Args:
extra_headers: Send extra headers
@@ -851,14 +898,10 @@ async def duplicate(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> NotificationTemplateResponse:
- """Duplicate a notification template.
-
- Creates a standalone copy within the same
- workspace and environment, with " COPY" appended to the title. The copy clones
- the source draft's tags, brand, subscription topic, routing strategy, channels,
- and content, and is always created as a standalone template (it is not linked to
- any journey or broadcast, even if the source was). Templates that are scoped to
- a journey or a broadcast cannot be duplicated through this endpoint.
+ """
+ Copies a notification template within the same workspace and environment,
+ appending " COPY" to the title. The copy is standalone and independently
+ editable.
Args:
extra_headers: Send extra headers
@@ -893,7 +936,8 @@ async def list_versions(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> NotificationTemplateVersionListResponse:
"""
- List versions of a notification template.
+ Returns a notification template's published versions, most recent first, for
+ comparison or rollback. Paged.
Args:
cursor: Opaque pagination cursor from a previous response. Omit for the first page.
@@ -933,6 +977,8 @@ async def publish(
id: str,
*,
version: str | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -959,6 +1005,15 @@ async def publish(
if not id:
raise ValueError(f"Expected a non-empty value for `id` but received {id!r}")
extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return await self._post(
path_template("/notifications/{id}/publish", id=id),
body=await async_maybe_transform(
@@ -983,11 +1038,10 @@ async def put_content(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> NotificationContentMutationResponse:
- """Replace the elemental content of a notification template.
+ """Replaces all Elemental content in a template, overwriting every existing
+ element.
- Overwrites all
- elements in the template with the provided content. Only supported for V2
- (elemental) templates.
+ Supported for V2 templates only, not V1 blocks and channels.
Args:
content: Elemental content payload. The server defaults `version` when omitted.
@@ -1038,10 +1092,9 @@ async def put_element(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> NotificationContentMutationResponse:
- """Update a single element within a notification template.
-
- Only supported for V2
- (elemental) templates.
+ """
+ Replaces one Elemental element in a template, addressed by its element id.
+ Supported for V2 templates only, not V1 blocks and channels.
Args:
type: Element type (text, meta, action, image, etc.).
@@ -1094,11 +1147,10 @@ async def put_locale(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> NotificationContentMutationResponse:
- """Set locale-specific content overrides for a notification template.
+ """Sets locale-specific content overrides for a template.
- Each element
- override must reference an existing element by ID. Only supported for V2
- (elemental) templates.
+ Each override must
+ reference an element that already exists in the default content.
Args:
elements: Elements with locale-specific content overrides.
@@ -1145,9 +1197,9 @@ async def replace(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> NotificationTemplateResponse:
- """Replace a notification template.
-
- All fields are required.
+ """
+ Replaces a notification template in full, so send every field rather than only
+ the ones you want changed. Publish separately to make it live.
Args:
notification: Core template fields used in POST and PUT request bodies (nested under a
@@ -1193,12 +1245,10 @@ async def retrieve_content(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> NotificationRetrieveContentResponse:
- """Retrieve the content of a notification template.
+ """Returns a template's content and checksum.
- The response shape depends on
- whether the template uses V1 (blocks/channels) or V2 (elemental) content. Use
- the `version` query parameter to select draft, published, or a specific
- historical version.
+ V2 templates return Elemental
+ elements, while V1 templates return blocks and channels instead.
Args:
version: Accepts `draft`, `published`, or a version string (e.g., `v001`). Defaults to
@@ -1277,6 +1327,9 @@ def __init__(self, notifications: NotificationsResource) -> None:
@cached_property
def checks(self) -> ChecksResourceWithRawResponse:
+ """
+ Create, update, version, publish, and localize notification templates and their content.
+ """
return ChecksResourceWithRawResponse(self._notifications.checks)
@@ -1323,6 +1376,9 @@ def __init__(self, notifications: AsyncNotificationsResource) -> None:
@cached_property
def checks(self) -> AsyncChecksResourceWithRawResponse:
+ """
+ Create, update, version, publish, and localize notification templates and their content.
+ """
return AsyncChecksResourceWithRawResponse(self._notifications.checks)
@@ -1369,6 +1425,9 @@ def __init__(self, notifications: NotificationsResource) -> None:
@cached_property
def checks(self) -> ChecksResourceWithStreamingResponse:
+ """
+ Create, update, version, publish, and localize notification templates and their content.
+ """
return ChecksResourceWithStreamingResponse(self._notifications.checks)
@@ -1415,4 +1474,7 @@ def __init__(self, notifications: AsyncNotificationsResource) -> None:
@cached_property
def checks(self) -> AsyncChecksResourceWithStreamingResponse:
+ """
+ Create, update, version, publish, and localize notification templates and their content.
+ """
return AsyncChecksResourceWithStreamingResponse(self._notifications.checks)
diff --git a/src/courier/resources/profiles/lists.py b/src/courier/resources/profiles/lists.py
index 4f41eb9d..7e2c040f 100644
--- a/src/courier/resources/profiles/lists.py
+++ b/src/courier/resources/profiles/lists.py
@@ -7,7 +7,7 @@
import httpx
from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
-from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import (
@@ -27,6 +27,10 @@
class ListsResource(SyncAPIResource):
+ """
+ Store the contact information Courier delivers to for each user — email, phone number, push tokens, and any custom data you send to.
+ """
+
@cached_property
def with_raw_response(self) -> ListsResourceWithRawResponse:
"""
@@ -58,8 +62,10 @@ def retrieve(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ListRetrieveResponse:
- """
- Returns the subscribed lists for a specified user.
+ """Returns the lists a user is subscribed to, with paging.
+
+ Use it to check what a
+ recipient will receive before sending to a list.
Args:
cursor: A unique identifier that allows for fetching the next set of message statuses.
@@ -97,8 +103,10 @@ def delete(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ListDeleteResponse:
- """
- Removes all list subscriptions for given user.
+ """Removes every list subscription for a user at once.
+
+ Their profile and
+ preferences are untouched, so this only affects list-targeted sends.
Args:
extra_headers: Send extra headers
@@ -124,6 +132,8 @@ def subscribe(
user_id: str,
*,
lists: Iterable[SubscribeToListsRequestItemParam],
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -131,10 +141,9 @@ def subscribe(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ListSubscribeResponse:
- """Subscribes the given user to one or more lists.
-
- If the list does not exist, it
- will be created.
+ """
+ Subscribes a user to one or more lists, creating any list that does not yet
+ exist. Optional preferences apply to each subscription.
Args:
extra_headers: Send extra headers
@@ -147,6 +156,15 @@ def subscribe(
"""
if not user_id:
raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return self._post(
path_template("/profiles/{user_id}/lists", user_id=user_id),
body=maybe_transform({"lists": lists}, list_subscribe_params.ListSubscribeParams),
@@ -158,6 +176,10 @@ def subscribe(
class AsyncListsResource(AsyncAPIResource):
+ """
+ Store the contact information Courier delivers to for each user — email, phone number, push tokens, and any custom data you send to.
+ """
+
@cached_property
def with_raw_response(self) -> AsyncListsResourceWithRawResponse:
"""
@@ -189,8 +211,10 @@ async def retrieve(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ListRetrieveResponse:
- """
- Returns the subscribed lists for a specified user.
+ """Returns the lists a user is subscribed to, with paging.
+
+ Use it to check what a
+ recipient will receive before sending to a list.
Args:
cursor: A unique identifier that allows for fetching the next set of message statuses.
@@ -228,8 +252,10 @@ async def delete(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ListDeleteResponse:
- """
- Removes all list subscriptions for given user.
+ """Removes every list subscription for a user at once.
+
+ Their profile and
+ preferences are untouched, so this only affects list-targeted sends.
Args:
extra_headers: Send extra headers
@@ -255,6 +281,8 @@ async def subscribe(
user_id: str,
*,
lists: Iterable[SubscribeToListsRequestItemParam],
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -262,10 +290,9 @@ async def subscribe(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ListSubscribeResponse:
- """Subscribes the given user to one or more lists.
-
- If the list does not exist, it
- will be created.
+ """
+ Subscribes a user to one or more lists, creating any list that does not yet
+ exist. Optional preferences apply to each subscription.
Args:
extra_headers: Send extra headers
@@ -278,6 +305,15 @@ async def subscribe(
"""
if not user_id:
raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return await self._post(
path_template("/profiles/{user_id}/lists", user_id=user_id),
body=await async_maybe_transform({"lists": lists}, list_subscribe_params.ListSubscribeParams),
diff --git a/src/courier/resources/profiles/profiles.py b/src/courier/resources/profiles/profiles.py
index d359fbc1..afd7d636 100644
--- a/src/courier/resources/profiles/profiles.py
+++ b/src/courier/resources/profiles/profiles.py
@@ -15,8 +15,8 @@
AsyncListsResourceWithStreamingResponse,
)
from ...types import profile_create_params, profile_update_params, profile_replace_params
-from ..._types import Body, Query, Headers, NoneType, NotGiven, not_given
-from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
+from ..._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import (
@@ -34,8 +34,15 @@
class ProfilesResource(SyncAPIResource):
+ """
+ Store the contact information Courier delivers to for each user — email, phone number, push tokens, and any custom data you send to.
+ """
+
@cached_property
def lists(self) -> ListsResource:
+ """
+ Store the contact information Courier delivers to for each user — email, phone number, push tokens, and any custom data you send to.
+ """
return ListsResource(self._client)
@cached_property
@@ -62,6 +69,8 @@ def create(
user_id: str,
*,
profile: Dict[str, object],
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -70,8 +79,8 @@ def create(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ProfileCreateResponse:
"""
- Merge the supplied values with an existing profile or create a new profile if
- one doesn't already exist.
+ Merges the supplied values into a user's profile, creating it if absent and
+ leaving any key you omit untouched. Prefer this for everyday writes.
Args:
extra_headers: Send extra headers
@@ -84,6 +93,15 @@ def create(
"""
if not user_id:
raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return self._post(
path_template("/profiles/{user_id}", user_id=user_id),
body=maybe_transform({"profile": profile}, profile_create_params.ProfileCreateParams),
@@ -105,7 +123,8 @@ def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ProfileRetrieveResponse:
"""
- Returns the specified user profile.
+ Returns a user's stored profile and preferences, including the email address,
+ phone number, and push tokens Courier can reach them on.
Args:
extra_headers: Send extra headers
@@ -139,7 +158,8 @@ def update(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
"""
- Update a profile
+ Applies a JSON Patch to a user profile, adding, removing, or replacing
+ individual fields without sending the whole object.
Args:
patch: List of patch operations to apply to the profile.
@@ -175,8 +195,10 @@ def delete(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """
- Deletes the specified user profile.
+ """Deletes a user's profile and stored contact details.
+
+ List subscriptions and
+ preferences are separate resources, so remove those too if required.
Args:
extra_headers: Send extra headers
@@ -210,12 +232,10 @@ def replace(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ProfileReplaceResponse:
- """
- When using `PUT`, be sure to include all the key-value pairs required by the
- recipient's profile. Any key-value pairs that exist in the profile but fail to
- be included in the `PUT` request will be removed from the profile. Remember, a
- `PUT` update is a full replacement of the data. For partial updates, use the
- [Patch](https://www.courier.com/docs/reference/profiles/patch/) request.
+ """Overwrites a user profile in full, removing any key absent from the request
+ body.
+
+ Use the patch endpoint when changing a single field.
Args:
extra_headers: Send extra headers
@@ -239,8 +259,15 @@ def replace(
class AsyncProfilesResource(AsyncAPIResource):
+ """
+ Store the contact information Courier delivers to for each user — email, phone number, push tokens, and any custom data you send to.
+ """
+
@cached_property
def lists(self) -> AsyncListsResource:
+ """
+ Store the contact information Courier delivers to for each user — email, phone number, push tokens, and any custom data you send to.
+ """
return AsyncListsResource(self._client)
@cached_property
@@ -267,6 +294,8 @@ async def create(
user_id: str,
*,
profile: Dict[str, object],
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -275,8 +304,8 @@ async def create(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ProfileCreateResponse:
"""
- Merge the supplied values with an existing profile or create a new profile if
- one doesn't already exist.
+ Merges the supplied values into a user's profile, creating it if absent and
+ leaving any key you omit untouched. Prefer this for everyday writes.
Args:
extra_headers: Send extra headers
@@ -289,6 +318,15 @@ async def create(
"""
if not user_id:
raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return await self._post(
path_template("/profiles/{user_id}", user_id=user_id),
body=await async_maybe_transform({"profile": profile}, profile_create_params.ProfileCreateParams),
@@ -310,7 +348,8 @@ async def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ProfileRetrieveResponse:
"""
- Returns the specified user profile.
+ Returns a user's stored profile and preferences, including the email address,
+ phone number, and push tokens Courier can reach them on.
Args:
extra_headers: Send extra headers
@@ -344,7 +383,8 @@ async def update(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
"""
- Update a profile
+ Applies a JSON Patch to a user profile, adding, removing, or replacing
+ individual fields without sending the whole object.
Args:
patch: List of patch operations to apply to the profile.
@@ -380,8 +420,10 @@ async def delete(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """
- Deletes the specified user profile.
+ """Deletes a user's profile and stored contact details.
+
+ List subscriptions and
+ preferences are separate resources, so remove those too if required.
Args:
extra_headers: Send extra headers
@@ -415,12 +457,10 @@ async def replace(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ProfileReplaceResponse:
- """
- When using `PUT`, be sure to include all the key-value pairs required by the
- recipient's profile. Any key-value pairs that exist in the profile but fail to
- be included in the `PUT` request will be removed from the profile. Remember, a
- `PUT` update is a full replacement of the data. For partial updates, use the
- [Patch](https://www.courier.com/docs/reference/profiles/patch/) request.
+ """Overwrites a user profile in full, removing any key absent from the request
+ body.
+
+ Use the patch endpoint when changing a single field.
Args:
extra_headers: Send extra headers
@@ -465,6 +505,9 @@ def __init__(self, profiles: ProfilesResource) -> None:
@cached_property
def lists(self) -> ListsResourceWithRawResponse:
+ """
+ Store the contact information Courier delivers to for each user — email, phone number, push tokens, and any custom data you send to.
+ """
return ListsResourceWithRawResponse(self._profiles.lists)
@@ -490,6 +533,9 @@ def __init__(self, profiles: AsyncProfilesResource) -> None:
@cached_property
def lists(self) -> AsyncListsResourceWithRawResponse:
+ """
+ Store the contact information Courier delivers to for each user — email, phone number, push tokens, and any custom data you send to.
+ """
return AsyncListsResourceWithRawResponse(self._profiles.lists)
@@ -515,6 +561,9 @@ def __init__(self, profiles: ProfilesResource) -> None:
@cached_property
def lists(self) -> ListsResourceWithStreamingResponse:
+ """
+ Store the contact information Courier delivers to for each user — email, phone number, push tokens, and any custom data you send to.
+ """
return ListsResourceWithStreamingResponse(self._profiles.lists)
@@ -540,4 +589,7 @@ def __init__(self, profiles: AsyncProfilesResource) -> None:
@cached_property
def lists(self) -> AsyncListsResourceWithStreamingResponse:
+ """
+ Store the contact information Courier delivers to for each user — email, phone number, push tokens, and any custom data you send to.
+ """
return AsyncListsResourceWithStreamingResponse(self._profiles.lists)
diff --git a/src/courier/resources/providers/catalog.py b/src/courier/resources/providers/catalog.py
index d89a86c0..57daa70d 100644
--- a/src/courier/resources/providers/catalog.py
+++ b/src/courier/resources/providers/catalog.py
@@ -22,6 +22,10 @@
class CatalogResource(SyncAPIResource):
+ """
+ Configure the channel providers Courier delivers through, and browse the provider types it supports.
+ """
+
@cached_property
def with_raw_response(self) -> CatalogResourceWithRawResponse:
"""
@@ -55,10 +59,8 @@ def list(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> CatalogListResponse:
"""
- Returns the catalog of available provider types with their display names,
- descriptions, and configuration schema fields (snake_case, with `type` and
- `required`). Providers with no configurable schema return only `provider`,
- `name`, and `description`.
+ Returns the provider types Courier supports, each with a display name,
+ description, and the configuration fields it requires.
Args:
channel: Exact match (case-insensitive) against the provider channel taxonomy (e.g.
@@ -97,6 +99,10 @@ def list(
class AsyncCatalogResource(AsyncAPIResource):
+ """
+ Configure the channel providers Courier delivers through, and browse the provider types it supports.
+ """
+
@cached_property
def with_raw_response(self) -> AsyncCatalogResourceWithRawResponse:
"""
@@ -130,10 +136,8 @@ async def list(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> CatalogListResponse:
"""
- Returns the catalog of available provider types with their display names,
- descriptions, and configuration schema fields (snake_case, with `type` and
- `required`). Providers with no configurable schema return only `provider`,
- `name`, and `description`.
+ Returns the provider types Courier supports, each with a display name,
+ description, and the configuration fields it requires.
Args:
channel: Exact match (case-insensitive) against the provider channel taxonomy (e.g.
diff --git a/src/courier/resources/providers/providers.py b/src/courier/resources/providers/providers.py
index 57bc9da5..dadeb0df 100644
--- a/src/courier/resources/providers/providers.py
+++ b/src/courier/resources/providers/providers.py
@@ -16,7 +16,7 @@
AsyncCatalogResourceWithStreamingResponse,
)
from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
-from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import (
@@ -33,8 +33,15 @@
class ProvidersResource(SyncAPIResource):
+ """
+ Configure the channel providers Courier delivers through, and browse the provider types it supports.
+ """
+
@cached_property
def catalog(self) -> CatalogResource:
+ """
+ Configure the channel providers Courier delivers through, and browse the provider types it supports.
+ """
return CatalogResource(self._client)
@cached_property
@@ -63,6 +70,8 @@ def create(
alias: str | Omit = omit,
settings: Dict[str, object] | Omit = omit,
title: str | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -70,10 +79,9 @@ def create(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Provider:
- """Create a new provider configuration.
-
- The `provider` field must be a known
- Courier provider key (see catalog).
+ """
+ Configures a provider integration from a Courier provider key and its settings.
+ Check the catalog endpoint for the schema each provider expects.
Args:
provider: The provider key identifying the type (e.g. "sendgrid", "twilio"). Must be a
@@ -95,6 +103,15 @@ def create(
timeout: Override the client-level default timeout for this request, in seconds
"""
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return self._post(
"/providers",
body=maybe_transform(
@@ -124,7 +141,8 @@ def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Provider:
"""
- Fetch a single provider configuration by ID.
+ Returns one configured provider by id, including its channel, provider key,
+ alias, title, and current settings.
Args:
extra_headers: Send extra headers
@@ -160,13 +178,9 @@ def update(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Provider:
- """Replace an existing provider configuration.
-
- The `provider` key is required and
- determines which provider-specific settings schema is applied. All other fields
- are optional — omitted fields are cleared from the stored configuration (this is
- a full replacement, not a partial merge). Changing the provider type for an
- existing configuration is not supported.
+ """
+ Replaces a provider's configuration in full, clearing any field you omit rather
+ than merging it. Send the complete settings object.
Args:
provider: The provider key identifying the type. Required on every request because it
@@ -218,10 +232,9 @@ def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ProviderListResponse:
- """List configured provider integrations for the current workspace.
-
- Supports
- cursor-based pagination.
+ """
+ Lists the provider integrations configured in the workspace, one entry per
+ channel and provider key with its alias and settings.
Args:
cursor: Opaque cursor for fetching the next page.
@@ -257,10 +270,9 @@ def delete(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Delete a provider configuration.
-
- Returns 409 if the provider is still referenced
- by routing or notifications.
+ """
+ Deletes a provider configuration, which fails while routing strategies or
+ templates still reference it. Update those references first.
Args:
extra_headers: Send extra headers
@@ -284,8 +296,15 @@ def delete(
class AsyncProvidersResource(AsyncAPIResource):
+ """
+ Configure the channel providers Courier delivers through, and browse the provider types it supports.
+ """
+
@cached_property
def catalog(self) -> AsyncCatalogResource:
+ """
+ Configure the channel providers Courier delivers through, and browse the provider types it supports.
+ """
return AsyncCatalogResource(self._client)
@cached_property
@@ -314,6 +333,8 @@ async def create(
alias: str | Omit = omit,
settings: Dict[str, object] | Omit = omit,
title: str | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -321,10 +342,9 @@ async def create(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Provider:
- """Create a new provider configuration.
-
- The `provider` field must be a known
- Courier provider key (see catalog).
+ """
+ Configures a provider integration from a Courier provider key and its settings.
+ Check the catalog endpoint for the schema each provider expects.
Args:
provider: The provider key identifying the type (e.g. "sendgrid", "twilio"). Must be a
@@ -346,6 +366,15 @@ async def create(
timeout: Override the client-level default timeout for this request, in seconds
"""
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return await self._post(
"/providers",
body=await async_maybe_transform(
@@ -375,7 +404,8 @@ async def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Provider:
"""
- Fetch a single provider configuration by ID.
+ Returns one configured provider by id, including its channel, provider key,
+ alias, title, and current settings.
Args:
extra_headers: Send extra headers
@@ -411,13 +441,9 @@ async def update(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Provider:
- """Replace an existing provider configuration.
-
- The `provider` key is required and
- determines which provider-specific settings schema is applied. All other fields
- are optional — omitted fields are cleared from the stored configuration (this is
- a full replacement, not a partial merge). Changing the provider type for an
- existing configuration is not supported.
+ """
+ Replaces a provider's configuration in full, clearing any field you omit rather
+ than merging it. Send the complete settings object.
Args:
provider: The provider key identifying the type. Required on every request because it
@@ -469,10 +495,9 @@ async def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ProviderListResponse:
- """List configured provider integrations for the current workspace.
-
- Supports
- cursor-based pagination.
+ """
+ Lists the provider integrations configured in the workspace, one entry per
+ channel and provider key with its alias and settings.
Args:
cursor: Opaque cursor for fetching the next page.
@@ -508,10 +533,9 @@ async def delete(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Delete a provider configuration.
-
- Returns 409 if the provider is still referenced
- by routing or notifications.
+ """
+ Deletes a provider configuration, which fails while routing strategies or
+ templates still reference it. Update those references first.
Args:
extra_headers: Send extra headers
@@ -556,6 +580,9 @@ def __init__(self, providers: ProvidersResource) -> None:
@cached_property
def catalog(self) -> CatalogResourceWithRawResponse:
+ """
+ Configure the channel providers Courier delivers through, and browse the provider types it supports.
+ """
return CatalogResourceWithRawResponse(self._providers.catalog)
@@ -581,6 +608,9 @@ def __init__(self, providers: AsyncProvidersResource) -> None:
@cached_property
def catalog(self) -> AsyncCatalogResourceWithRawResponse:
+ """
+ Configure the channel providers Courier delivers through, and browse the provider types it supports.
+ """
return AsyncCatalogResourceWithRawResponse(self._providers.catalog)
@@ -606,6 +636,9 @@ def __init__(self, providers: ProvidersResource) -> None:
@cached_property
def catalog(self) -> CatalogResourceWithStreamingResponse:
+ """
+ Configure the channel providers Courier delivers through, and browse the provider types it supports.
+ """
return CatalogResourceWithStreamingResponse(self._providers.catalog)
@@ -631,4 +664,7 @@ def __init__(self, providers: AsyncProvidersResource) -> None:
@cached_property
def catalog(self) -> AsyncCatalogResourceWithStreamingResponse:
+ """
+ Configure the channel providers Courier delivers through, and browse the provider types it supports.
+ """
return AsyncCatalogResourceWithStreamingResponse(self._providers.catalog)
diff --git a/src/courier/resources/requests.py b/src/courier/resources/requests.py
index c2ddf98b..3dfd380a 100644
--- a/src/courier/resources/requests.py
+++ b/src/courier/resources/requests.py
@@ -20,6 +20,10 @@
class RequestsResource(SyncAPIResource):
+ """
+ Look up the messages Courier has accepted, inspect their delivery history and rendered output, and cancel, resend, or archive them.
+ """
+
@cached_property
def with_raw_response(self) -> RequestsResourceWithRawResponse:
"""
@@ -50,8 +54,10 @@ def archive(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """
- Archive message
+ """Archives a send request by its request id.
+
+ Use it to remove test sends or
+ superseded requests from the message list without deleting them.
Args:
extra_headers: Send extra headers
@@ -75,6 +81,10 @@ def archive(
class AsyncRequestsResource(AsyncAPIResource):
+ """
+ Look up the messages Courier has accepted, inspect their delivery history and rendered output, and cancel, resend, or archive them.
+ """
+
@cached_property
def with_raw_response(self) -> AsyncRequestsResourceWithRawResponse:
"""
@@ -105,8 +115,10 @@ async def archive(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """
- Archive message
+ """Archives a send request by its request id.
+
+ Use it to remove test sends or
+ superseded requests from the message list without deleting them.
Args:
extra_headers: Send extra headers
diff --git a/src/courier/resources/routing_strategies.py b/src/courier/resources/routing_strategies.py
index 99910979..48aaf066 100644
--- a/src/courier/resources/routing_strategies.py
+++ b/src/courier/resources/routing_strategies.py
@@ -13,7 +13,7 @@
routing_strategy_list_notifications_params,
)
from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, omit, not_given
-from .._utils import path_template, maybe_transform, async_maybe_transform
+from .._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform
from .._compat import cached_property
from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
@@ -34,6 +34,10 @@
class RoutingStrategiesResource(SyncAPIResource):
+ """
+ Define reusable channel routing and failover strategies, and see which templates use them.
+ """
+
@cached_property
def with_raw_response(self) -> RoutingStrategiesResourceWithRawResponse:
"""
@@ -62,6 +66,8 @@ def create(
description: Optional[str] | Omit = omit,
providers: Optional[MessageProviders] | Omit = omit,
tags: Optional[SequenceNotStr[str]] | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -95,6 +101,15 @@ def create(
timeout: Override the client-level default timeout for this request, in seconds
"""
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return self._post(
"/routing-strategies",
body=maybe_transform(
@@ -125,10 +140,9 @@ def retrieve(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> RoutingStrategyGetResponse:
- """Retrieve a routing strategy by ID.
-
- Returns the full entity including routing
- content and metadata.
+ """
+ Returns one routing strategy by id with its name, tags, channels, and the
+ routing rules that decide provider order and fallback.
Args:
extra_headers: Send extra headers
@@ -247,10 +261,10 @@ def list_notifications(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AssociatedNotificationListResponse:
- """List notification templates associated with a routing strategy.
+ """Returns the notification templates using a routing strategy, with paging.
- Includes
- template metadata only, not full content.
+ Check
+ this before changing a strategy that templates depend on.
Args:
cursor: Opaque pagination cursor from a previous response. Omit for the first page.
@@ -351,6 +365,10 @@ def replace(
class AsyncRoutingStrategiesResource(AsyncAPIResource):
+ """
+ Define reusable channel routing and failover strategies, and see which templates use them.
+ """
+
@cached_property
def with_raw_response(self) -> AsyncRoutingStrategiesResourceWithRawResponse:
"""
@@ -379,6 +397,8 @@ async def create(
description: Optional[str] | Omit = omit,
providers: Optional[MessageProviders] | Omit = omit,
tags: Optional[SequenceNotStr[str]] | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -412,6 +432,15 @@ async def create(
timeout: Override the client-level default timeout for this request, in seconds
"""
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return await self._post(
"/routing-strategies",
body=await async_maybe_transform(
@@ -442,10 +471,9 @@ async def retrieve(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> RoutingStrategyGetResponse:
- """Retrieve a routing strategy by ID.
-
- Returns the full entity including routing
- content and metadata.
+ """
+ Returns one routing strategy by id with its name, tags, channels, and the
+ routing rules that decide provider order and fallback.
Args:
extra_headers: Send extra headers
@@ -564,10 +592,10 @@ async def list_notifications(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> AssociatedNotificationListResponse:
- """List notification templates associated with a routing strategy.
+ """Returns the notification templates using a routing strategy, with paging.
- Includes
- template metadata only, not full content.
+ Check
+ this before changing a strategy that templates depend on.
Args:
cursor: Opaque pagination cursor from a previous response. Omit for the first page.
diff --git a/src/courier/resources/send.py b/src/courier/resources/send.py
index 21c8cbe8..1c25a491 100644
--- a/src/courier/resources/send.py
+++ b/src/courier/resources/send.py
@@ -5,8 +5,8 @@
import httpx
from ..types import send_message_params
-from .._types import Body, Query, Headers, NotGiven, not_given
-from .._utils import maybe_transform, async_maybe_transform
+from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from .._utils import maybe_transform, strip_not_given, async_maybe_transform
from .._compat import cached_property
from .._resource import SyncAPIResource, AsyncAPIResource
from .._response import (
@@ -22,6 +22,10 @@
class SendResource(SyncAPIResource):
+ """
+ Send a message to one or more recipients — users, lists, audiences, or tenants — across every channel you have configured.
+ """
+
@cached_property
def with_raw_response(self) -> SendResourceWithRawResponse:
"""
@@ -45,6 +49,8 @@ def message(
self,
*,
message: send_message_params.Message,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -52,8 +58,10 @@ def message(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> SendMessageResponse:
- """
- Send a message to one or more recipients.
+ """Sends a message to one or more recipients and returns a requestId.
+
+ Courier
+ routes it to email, SMS, push, chat, or in-app based on your rules.
Args:
message: The message property has the following primary top-level properties. They define
@@ -67,6 +75,15 @@ def message(
timeout: Override the client-level default timeout for this request, in seconds
"""
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return self._post(
"/send",
body=maybe_transform({"message": message}, send_message_params.SendMessageParams),
@@ -78,6 +95,10 @@ def message(
class AsyncSendResource(AsyncAPIResource):
+ """
+ Send a message to one or more recipients — users, lists, audiences, or tenants — across every channel you have configured.
+ """
+
@cached_property
def with_raw_response(self) -> AsyncSendResourceWithRawResponse:
"""
@@ -101,6 +122,8 @@ async def message(
self,
*,
message: send_message_params.Message,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -108,8 +131,10 @@ async def message(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> SendMessageResponse:
- """
- Send a message to one or more recipients.
+ """Sends a message to one or more recipients and returns a requestId.
+
+ Courier
+ routes it to email, SMS, push, chat, or in-app based on your rules.
Args:
message: The message property has the following primary top-level properties. They define
@@ -123,6 +148,15 @@ async def message(
timeout: Override the client-level default timeout for this request, in seconds
"""
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return await self._post(
"/send",
body=await async_maybe_transform({"message": message}, send_message_params.SendMessageParams),
diff --git a/src/courier/resources/tenants/preferences/items.py b/src/courier/resources/tenants/preferences/items.py
index 91d35dc4..103d9af3 100644
--- a/src/courier/resources/tenants/preferences/items.py
+++ b/src/courier/resources/tenants/preferences/items.py
@@ -25,6 +25,10 @@
class ItemsResource(SyncAPIResource):
+ """
+ Manage tenants — the organizations, teams, or accounts your users belong to — along with their users and default preferences.
+ """
+
@cached_property
def with_raw_response(self) -> ItemsResourceWithRawResponse:
"""
@@ -60,7 +64,8 @@ def update(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
"""
- Create or Replace Default Preferences For Topic
+ Sets a tenant's default opt-in status for one subscription topic, which applies
+ to every member unless a user sets their own override.
Args:
custom_routing: The default channels to send to this tenant when has_custom_routing is enabled
@@ -113,7 +118,8 @@ def delete(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
"""
- Remove Default Preferences For Topic
+ Removes a tenant's default preference for one subscription topic, addressed by
+ tenant id and topic id.
Args:
extra_headers: Send extra headers
@@ -141,6 +147,10 @@ def delete(
class AsyncItemsResource(AsyncAPIResource):
+ """
+ Manage tenants — the organizations, teams, or accounts your users belong to — along with their users and default preferences.
+ """
+
@cached_property
def with_raw_response(self) -> AsyncItemsResourceWithRawResponse:
"""
@@ -176,7 +186,8 @@ async def update(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
"""
- Create or Replace Default Preferences For Topic
+ Sets a tenant's default opt-in status for one subscription topic, which applies
+ to every member unless a user sets their own override.
Args:
custom_routing: The default channels to send to this tenant when has_custom_routing is enabled
@@ -229,7 +240,8 @@ async def delete(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
"""
- Remove Default Preferences For Topic
+ Removes a tenant's default preference for one subscription topic, addressed by
+ tenant id and topic id.
Args:
extra_headers: Send extra headers
diff --git a/src/courier/resources/tenants/preferences/preferences.py b/src/courier/resources/tenants/preferences/preferences.py
index 0d8c47d4..6b0408dc 100644
--- a/src/courier/resources/tenants/preferences/preferences.py
+++ b/src/courier/resources/tenants/preferences/preferences.py
@@ -19,6 +19,9 @@
class PreferencesResource(SyncAPIResource):
@cached_property
def items(self) -> ItemsResource:
+ """
+ Manage tenants — the organizations, teams, or accounts your users belong to — along with their users and default preferences.
+ """
return ItemsResource(self._client)
@cached_property
@@ -44,6 +47,9 @@ def with_streaming_response(self) -> PreferencesResourceWithStreamingResponse:
class AsyncPreferencesResource(AsyncAPIResource):
@cached_property
def items(self) -> AsyncItemsResource:
+ """
+ Manage tenants — the organizations, teams, or accounts your users belong to — along with their users and default preferences.
+ """
return AsyncItemsResource(self._client)
@cached_property
@@ -72,6 +78,9 @@ def __init__(self, preferences: PreferencesResource) -> None:
@cached_property
def items(self) -> ItemsResourceWithRawResponse:
+ """
+ Manage tenants — the organizations, teams, or accounts your users belong to — along with their users and default preferences.
+ """
return ItemsResourceWithRawResponse(self._preferences.items)
@@ -81,6 +90,9 @@ def __init__(self, preferences: AsyncPreferencesResource) -> None:
@cached_property
def items(self) -> AsyncItemsResourceWithRawResponse:
+ """
+ Manage tenants — the organizations, teams, or accounts your users belong to — along with their users and default preferences.
+ """
return AsyncItemsResourceWithRawResponse(self._preferences.items)
@@ -90,6 +102,9 @@ def __init__(self, preferences: PreferencesResource) -> None:
@cached_property
def items(self) -> ItemsResourceWithStreamingResponse:
+ """
+ Manage tenants — the organizations, teams, or accounts your users belong to — along with their users and default preferences.
+ """
return ItemsResourceWithStreamingResponse(self._preferences.items)
@@ -99,4 +114,7 @@ def __init__(self, preferences: AsyncPreferencesResource) -> None:
@cached_property
def items(self) -> AsyncItemsResourceWithStreamingResponse:
+ """
+ Manage tenants — the organizations, teams, or accounts your users belong to — along with their users and default preferences.
+ """
return AsyncItemsResourceWithStreamingResponse(self._preferences.items)
diff --git a/src/courier/resources/tenants/templates/templates.py b/src/courier/resources/tenants/templates/templates.py
index accb1275..20015941 100644
--- a/src/courier/resources/tenants/templates/templates.py
+++ b/src/courier/resources/tenants/templates/templates.py
@@ -36,8 +36,15 @@
class TemplatesResource(SyncAPIResource):
+ """
+ Manage the templates and template versions scoped to a single tenant, including the ones authored in the embedded designer.
+ """
+
@cached_property
def versions(self) -> VersionsResource:
+ """
+ Manage the templates and template versions scoped to a single tenant, including the ones authored in the embedded designer.
+ """
return VersionsResource(self._client)
@cached_property
@@ -72,7 +79,8 @@ def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> BaseTemplateTenantAssociation:
"""
- Get a Template in Tenant
+ Returns a tenant's notification template with its content, version, and created,
+ updated, and published timestamps.
Args:
extra_headers: Send extra headers
@@ -109,7 +117,8 @@ def list(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> TemplateListResponse:
"""
- List Templates in Tenant
+ Lists a tenant's notification templates, each carrying its version and published
+ timestamp. Paged.
Args:
cursor: Continue the pagination with the next cursor
@@ -156,13 +165,10 @@ def delete(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """
- Deletes the tenant's notification template with the given `template_id`.
-
- Returns **204 No Content** with an empty body on success.
+ """Deletes a tenant's notification template by id.
- Returns **404** if there is no template with this ID for the tenant, including a
- second `DELETE` after a successful removal.
+ Sends for that tenant then use
+ the workspace template registered under the same id.
Args:
extra_headers: Send extra headers
@@ -200,10 +206,8 @@ def publish(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PostTenantTemplatePublishResponse:
"""
- Publishes a specific version of a notification template for a tenant.
-
- The template must already exist in the tenant's notification map. If no version
- is specified, defaults to publishing the "latest" version.
+ Publishes a version of a tenant's notification template, making it the content
+ that tenant's sends render from until you publish another.
Args:
version: The version of the template to publish (e.g., "v1", "v2", "latest"). If not
@@ -247,13 +251,8 @@ def replace(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PutTenantTemplateResponse:
"""
- Creates or updates a notification template for a tenant.
-
- If the template already exists for the tenant, it will be updated (200).
- Otherwise, a new template is created (201).
-
- Optionally publishes the template immediately if the `published` flag is set to
- true.
+ Creates or updates a notification template scoped to one tenant, letting a
+ tenant override the content the workspace template would send.
Args:
template: Template configuration for creating or updating a tenant notification template
@@ -291,8 +290,15 @@ def replace(
class AsyncTemplatesResource(AsyncAPIResource):
+ """
+ Manage the templates and template versions scoped to a single tenant, including the ones authored in the embedded designer.
+ """
+
@cached_property
def versions(self) -> AsyncVersionsResource:
+ """
+ Manage the templates and template versions scoped to a single tenant, including the ones authored in the embedded designer.
+ """
return AsyncVersionsResource(self._client)
@cached_property
@@ -327,7 +333,8 @@ async def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> BaseTemplateTenantAssociation:
"""
- Get a Template in Tenant
+ Returns a tenant's notification template with its content, version, and created,
+ updated, and published timestamps.
Args:
extra_headers: Send extra headers
@@ -364,7 +371,8 @@ async def list(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> TemplateListResponse:
"""
- List Templates in Tenant
+ Lists a tenant's notification templates, each carrying its version and published
+ timestamp. Paged.
Args:
cursor: Continue the pagination with the next cursor
@@ -411,13 +419,10 @@ async def delete(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """
- Deletes the tenant's notification template with the given `template_id`.
+ """Deletes a tenant's notification template by id.
- Returns **204 No Content** with an empty body on success.
-
- Returns **404** if there is no template with this ID for the tenant, including a
- second `DELETE` after a successful removal.
+ Sends for that tenant then use
+ the workspace template registered under the same id.
Args:
extra_headers: Send extra headers
@@ -455,10 +460,8 @@ async def publish(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PostTenantTemplatePublishResponse:
"""
- Publishes a specific version of a notification template for a tenant.
-
- The template must already exist in the tenant's notification map. If no version
- is specified, defaults to publishing the "latest" version.
+ Publishes a version of a tenant's notification template, making it the content
+ that tenant's sends render from until you publish another.
Args:
version: The version of the template to publish (e.g., "v1", "v2", "latest"). If not
@@ -502,13 +505,8 @@ async def replace(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PutTenantTemplateResponse:
"""
- Creates or updates a notification template for a tenant.
-
- If the template already exists for the tenant, it will be updated (200).
- Otherwise, a new template is created (201).
-
- Optionally publishes the template immediately if the `published` flag is set to
- true.
+ Creates or updates a notification template scoped to one tenant, letting a
+ tenant override the content the workspace template would send.
Args:
template: Template configuration for creating or updating a tenant notification template
@@ -567,6 +565,9 @@ def __init__(self, templates: TemplatesResource) -> None:
@cached_property
def versions(self) -> VersionsResourceWithRawResponse:
+ """
+ Manage the templates and template versions scoped to a single tenant, including the ones authored in the embedded designer.
+ """
return VersionsResourceWithRawResponse(self._templates.versions)
@@ -592,6 +593,9 @@ def __init__(self, templates: AsyncTemplatesResource) -> None:
@cached_property
def versions(self) -> AsyncVersionsResourceWithRawResponse:
+ """
+ Manage the templates and template versions scoped to a single tenant, including the ones authored in the embedded designer.
+ """
return AsyncVersionsResourceWithRawResponse(self._templates.versions)
@@ -617,6 +621,9 @@ def __init__(self, templates: TemplatesResource) -> None:
@cached_property
def versions(self) -> VersionsResourceWithStreamingResponse:
+ """
+ Manage the templates and template versions scoped to a single tenant, including the ones authored in the embedded designer.
+ """
return VersionsResourceWithStreamingResponse(self._templates.versions)
@@ -642,4 +649,7 @@ def __init__(self, templates: AsyncTemplatesResource) -> None:
@cached_property
def versions(self) -> AsyncVersionsResourceWithStreamingResponse:
+ """
+ Manage the templates and template versions scoped to a single tenant, including the ones authored in the embedded designer.
+ """
return AsyncVersionsResourceWithStreamingResponse(self._templates.versions)
diff --git a/src/courier/resources/tenants/templates/versions.py b/src/courier/resources/tenants/templates/versions.py
index 8b941609..bd91faa9 100644
--- a/src/courier/resources/tenants/templates/versions.py
+++ b/src/courier/resources/tenants/templates/versions.py
@@ -21,6 +21,10 @@
class VersionsResource(SyncAPIResource):
+ """
+ Manage the templates and template versions scoped to a single tenant, including the ones authored in the embedded designer.
+ """
+
@cached_property
def with_raw_response(self) -> VersionsResourceWithRawResponse:
"""
@@ -54,13 +58,8 @@ def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> BaseTemplateTenantAssociation:
"""
- Fetches a specific version of a tenant template.
-
- Supports the following version formats:
-
- - `latest` - The most recent version of the template
- - `published` - The currently published version
- - `v{version}` - A specific version (e.g., "v1", "v2", "v1.0.0")
+ Returns one version of a tenant template, addressed by version number or by
+ latest, with its content and publish timestamp.
Args:
extra_headers: Send extra headers
@@ -92,6 +91,10 @@ def retrieve(
class AsyncVersionsResource(AsyncAPIResource):
+ """
+ Manage the templates and template versions scoped to a single tenant, including the ones authored in the embedded designer.
+ """
+
@cached_property
def with_raw_response(self) -> AsyncVersionsResourceWithRawResponse:
"""
@@ -125,13 +128,8 @@ async def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> BaseTemplateTenantAssociation:
"""
- Fetches a specific version of a tenant template.
-
- Supports the following version formats:
-
- - `latest` - The most recent version of the template
- - `published` - The currently published version
- - `v{version}` - A specific version (e.g., "v1", "v2", "v1.0.0")
+ Returns one version of a tenant template, addressed by version number or by
+ latest, with its content and publish timestamp.
Args:
extra_headers: Send extra headers
diff --git a/src/courier/resources/tenants/tenants.py b/src/courier/resources/tenants/tenants.py
index 1c0d55b9..a05a830c 100644
--- a/src/courier/resources/tenants/tenants.py
+++ b/src/courier/resources/tenants/tenants.py
@@ -43,12 +43,19 @@
class TenantsResource(SyncAPIResource):
+ """
+ Manage tenants — the organizations, teams, or accounts your users belong to — along with their users and default preferences.
+ """
+
@cached_property
def preferences(self) -> PreferencesResource:
return PreferencesResource(self._client)
@cached_property
def templates(self) -> TemplatesResource:
+ """
+ Manage the templates and template versions scoped to a single tenant, including the ones authored in the embedded designer.
+ """
return TemplatesResource(self._client)
@cached_property
@@ -82,7 +89,8 @@ def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Tenant:
"""
- Get a Tenant
+ Returns one tenant with its name, parent tenant id, default preferences,
+ properties, and the user profile applied to its members.
Args:
extra_headers: Send extra headers
@@ -121,7 +129,8 @@ def update(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Tenant:
"""
- Create or Replace a Tenant
+ Creates or replaces a tenant from a name, parent, brand, properties, and default
+ preferences supplied in the request body.
Args:
name: Name of the tenant.
@@ -180,7 +189,8 @@ def list(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> TenantListResponse:
"""
- Get a List of Tenants
+ Lists the workspace's tenants, each carrying a name, parent tenant, properties,
+ and default preferences. Paged.
Args:
cursor: Continue the pagination with the next cursor
@@ -227,8 +237,10 @@ def delete(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """
- Delete a Tenant
+ """Deletes a tenant.
+
+ Its members' workspace-level profiles and preferences live
+ outside the tenant and are managed separately.
Args:
extra_headers: Send extra headers
@@ -263,8 +275,10 @@ def list_users(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> TenantListUsersResponse:
- """
- Get Users in Tenant
+ """Returns the users belonging to a tenant with cursor paging.
+
+ Use it to see who a
+ tenant-scoped send will reach.
Args:
cursor: Continue the pagination with the next cursor
@@ -301,12 +315,19 @@ def list_users(
class AsyncTenantsResource(AsyncAPIResource):
+ """
+ Manage tenants — the organizations, teams, or accounts your users belong to — along with their users and default preferences.
+ """
+
@cached_property
def preferences(self) -> AsyncPreferencesResource:
return AsyncPreferencesResource(self._client)
@cached_property
def templates(self) -> AsyncTemplatesResource:
+ """
+ Manage the templates and template versions scoped to a single tenant, including the ones authored in the embedded designer.
+ """
return AsyncTemplatesResource(self._client)
@cached_property
@@ -340,7 +361,8 @@ async def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Tenant:
"""
- Get a Tenant
+ Returns one tenant with its name, parent tenant id, default preferences,
+ properties, and the user profile applied to its members.
Args:
extra_headers: Send extra headers
@@ -379,7 +401,8 @@ async def update(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> Tenant:
"""
- Create or Replace a Tenant
+ Creates or replaces a tenant from a name, parent, brand, properties, and default
+ preferences supplied in the request body.
Args:
name: Name of the tenant.
@@ -438,7 +461,8 @@ async def list(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> TenantListResponse:
"""
- Get a List of Tenants
+ Lists the workspace's tenants, each carrying a name, parent tenant, properties,
+ and default preferences. Paged.
Args:
cursor: Continue the pagination with the next cursor
@@ -485,8 +509,10 @@ async def delete(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """
- Delete a Tenant
+ """Deletes a tenant.
+
+ Its members' workspace-level profiles and preferences live
+ outside the tenant and are managed separately.
Args:
extra_headers: Send extra headers
@@ -521,8 +547,10 @@ async def list_users(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> TenantListUsersResponse:
- """
- Get Users in Tenant
+ """Returns the users belonging to a tenant with cursor paging.
+
+ Use it to see who a
+ tenant-scoped send will reach.
Args:
cursor: Continue the pagination with the next cursor
@@ -584,6 +612,9 @@ def preferences(self) -> PreferencesResourceWithRawResponse:
@cached_property
def templates(self) -> TemplatesResourceWithRawResponse:
+ """
+ Manage the templates and template versions scoped to a single tenant, including the ones authored in the embedded designer.
+ """
return TemplatesResourceWithRawResponse(self._tenants.templates)
@@ -613,6 +644,9 @@ def preferences(self) -> AsyncPreferencesResourceWithRawResponse:
@cached_property
def templates(self) -> AsyncTemplatesResourceWithRawResponse:
+ """
+ Manage the templates and template versions scoped to a single tenant, including the ones authored in the embedded designer.
+ """
return AsyncTemplatesResourceWithRawResponse(self._tenants.templates)
@@ -642,6 +676,9 @@ def preferences(self) -> PreferencesResourceWithStreamingResponse:
@cached_property
def templates(self) -> TemplatesResourceWithStreamingResponse:
+ """
+ Manage the templates and template versions scoped to a single tenant, including the ones authored in the embedded designer.
+ """
return TemplatesResourceWithStreamingResponse(self._tenants.templates)
@@ -671,4 +708,7 @@ def preferences(self) -> AsyncPreferencesResourceWithStreamingResponse:
@cached_property
def templates(self) -> AsyncTemplatesResourceWithStreamingResponse:
+ """
+ Manage the templates and template versions scoped to a single tenant, including the ones authored in the embedded designer.
+ """
return AsyncTemplatesResourceWithStreamingResponse(self._tenants.templates)
diff --git a/src/courier/resources/translations.py b/src/courier/resources/translations.py
index f4b1cfd9..dc20557c 100644
--- a/src/courier/resources/translations.py
+++ b/src/courier/resources/translations.py
@@ -21,6 +21,10 @@
class TranslationsResource(SyncAPIResource):
+ """
+ Store and retrieve the translation strings Courier uses to render localized template content.
+ """
+
@cached_property
def with_raw_response(self) -> TranslationsResourceWithRawResponse:
"""
@@ -53,7 +57,8 @@ def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> str:
"""
- Get translations by locale
+ Returns the translation strings stored for one domain and locale, for use in
+ localized notification content.
Args:
extra_headers: Send extra headers
@@ -89,8 +94,10 @@ def update(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """
- Update a translation
+ """Uploads the translation strings for one domain and locale.
+
+ Courier uses them to
+ render localized content for recipients in that locale.
Args:
extra_headers: Send extra headers
@@ -117,6 +124,10 @@ def update(
class AsyncTranslationsResource(AsyncAPIResource):
+ """
+ Store and retrieve the translation strings Courier uses to render localized template content.
+ """
+
@cached_property
def with_raw_response(self) -> AsyncTranslationsResourceWithRawResponse:
"""
@@ -149,7 +160,8 @@ async def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> str:
"""
- Get translations by locale
+ Returns the translation strings stored for one domain and locale, for use in
+ localized notification content.
Args:
extra_headers: Send extra headers
@@ -185,8 +197,10 @@ async def update(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """
- Update a translation
+ """Uploads the translation strings for one domain and locale.
+
+ Courier uses them to
+ render localized content for recipients in that locale.
Args:
extra_headers: Send extra headers
diff --git a/src/courier/resources/users/preferences.py b/src/courier/resources/users/preferences.py
index 27e74a1d..61d18743 100644
--- a/src/courier/resources/users/preferences.py
+++ b/src/courier/resources/users/preferences.py
@@ -7,7 +7,7 @@
import httpx
from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
-from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import (
@@ -35,6 +35,10 @@
class PreferencesResource(SyncAPIResource):
+ """
+ Read and write a single user's notification preferences, per topic and per channel.
+ """
+
@cached_property
def with_raw_response(self) -> PreferencesResourceWithRawResponse:
"""
@@ -67,7 +71,8 @@ def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PreferenceRetrieveResponse:
"""
- Fetch all user preferences.
+ Returns a user's preference overrides with paging, one entry per subscription
+ topic they have set a choice for.
Args:
tenant_id: Query the preferences of a user for this specific tenant context.
@@ -107,25 +112,10 @@ def bulk_replace(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PreferenceBulkReplaceResponse:
- """Replace a user's complete set of preference overrides in a single request.
-
- The
- topics in the request body become the recipient's entire set of overrides:
- listed topics are created or updated, and every existing override that is not
- included in the body is reset to its topic default. Submitting an empty `topics`
- array is a valid clear-all that resets every existing override.
-
- This operation is validation-atomic (all-or-nothing): structural validation
- fails fast with a single `400`, and if any topic is semantically invalid (an
- unknown topic, a `REQUIRED` topic that cannot be opted out, or a custom routing
- request that is not available on the workspace's plan) the request returns a
- single `400` aggregating every failure in `errors` and writes nothing. On
- success it returns `200` with `items` (the complete resulting override set) and
- `deleted` (the ids of the overrides that were reset to default).
-
- Every `topic_id` in the response — in `items`, `deleted`, and any `errors` — is
- returned in Courier's canonical topic id form, regardless of the form supplied
- in the request.
+ """Replaces a user's entire set of preference overrides.
+
+ Any topic you leave out is
+ reset to its default, so send the full set rather than a subset.
Args:
topics: The complete set of topic overrides for the user. Up to 50 topics may be
@@ -165,6 +155,8 @@ def bulk_update(
*,
topics: Iterable[preference_bulk_update_params.Topic],
tenant_id: Optional[str] | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -173,23 +165,8 @@ def bulk_update(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PreferenceBulkUpdateResponse:
"""
- Additively create or update a user's preferences for one or more subscription
- topics in a single request. Only the topics included in the request body are
- created or updated; any existing overrides for topics not listed are left
- untouched.
-
- Structural validation of the request body fails fast with a single `400`. Beyond
- that, each topic is processed independently (partial-success, not
- all-or-nothing): valid topics are written and returned in `items`, while topics
- that cannot be applied are collected in `errors` with a per-topic `reason` (for
- example an unknown topic, a `REQUIRED` topic that cannot be opted out, a custom
- routing request that is not available on the workspace's plan, or a write
- failure). The request therefore returns `200` with both lists whenever the body
- is structurally valid.
-
- Every `topic_id` in the response — in both `items` and `errors` — is returned in
- Courier's canonical topic id form, regardless of the form supplied in the
- request.
+ Adds or updates a user's preferences for several subscription topics at once.
+ Topics you leave out keep whatever they were set to before.
Args:
topics: The topics to create or update. Between 1 and 50 topics may be provided in a
@@ -207,6 +184,15 @@ def bulk_update(
"""
if not user_id:
raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return self._post(
path_template("/users/{user_id}/preferences", user_id=user_id),
body=maybe_transform({"topics": topics}, preference_bulk_update_params.PreferenceBulkUpdateParams),
@@ -236,9 +222,8 @@ def delete_topic(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
"""
- Remove a user's preferences for a specific subscription topic, resetting the
- topic to its effective default. This operation is idempotent: deleting a
- preference that does not exist succeeds with no error.
+ Removes a user's override for one subscription topic, resetting it to the
+ effective default from the tenant or workspace.
Args:
tenant_id: Delete the preferences of a user for this specific tenant context.
@@ -284,7 +269,8 @@ def retrieve_topic(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PreferenceRetrieveTopicResponse:
"""
- Fetch user preferences for a specific subscription topic.
+ Returns a user's opt-in status and channel choices for one subscription topic,
+ or the effective default if they have set no override.
Args:
tenant_id: Query the preferences of a user for this specific tenant context.
@@ -330,7 +316,8 @@ def update_or_create_topic(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PreferenceUpdateOrCreateTopicResponse:
"""
- Update or Create user preferences for a specific subscription topic.
+ Sets a user's opt-in status and channel choices for one subscription topic,
+ overriding the tenant default for that topic only.
Args:
tenant_id: Update the preferences of a user for this specific tenant context.
@@ -367,6 +354,10 @@ def update_or_create_topic(
class AsyncPreferencesResource(AsyncAPIResource):
+ """
+ Read and write a single user's notification preferences, per topic and per channel.
+ """
+
@cached_property
def with_raw_response(self) -> AsyncPreferencesResourceWithRawResponse:
"""
@@ -399,7 +390,8 @@ async def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PreferenceRetrieveResponse:
"""
- Fetch all user preferences.
+ Returns a user's preference overrides with paging, one entry per subscription
+ topic they have set a choice for.
Args:
tenant_id: Query the preferences of a user for this specific tenant context.
@@ -441,25 +433,10 @@ async def bulk_replace(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PreferenceBulkReplaceResponse:
- """Replace a user's complete set of preference overrides in a single request.
-
- The
- topics in the request body become the recipient's entire set of overrides:
- listed topics are created or updated, and every existing override that is not
- included in the body is reset to its topic default. Submitting an empty `topics`
- array is a valid clear-all that resets every existing override.
-
- This operation is validation-atomic (all-or-nothing): structural validation
- fails fast with a single `400`, and if any topic is semantically invalid (an
- unknown topic, a `REQUIRED` topic that cannot be opted out, or a custom routing
- request that is not available on the workspace's plan) the request returns a
- single `400` aggregating every failure in `errors` and writes nothing. On
- success it returns `200` with `items` (the complete resulting override set) and
- `deleted` (the ids of the overrides that were reset to default).
-
- Every `topic_id` in the response — in `items`, `deleted`, and any `errors` — is
- returned in Courier's canonical topic id form, regardless of the form supplied
- in the request.
+ """Replaces a user's entire set of preference overrides.
+
+ Any topic you leave out is
+ reset to its default, so send the full set rather than a subset.
Args:
topics: The complete set of topic overrides for the user. Up to 50 topics may be
@@ -501,6 +478,8 @@ async def bulk_update(
*,
topics: Iterable[preference_bulk_update_params.Topic],
tenant_id: Optional[str] | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -509,23 +488,8 @@ async def bulk_update(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PreferenceBulkUpdateResponse:
"""
- Additively create or update a user's preferences for one or more subscription
- topics in a single request. Only the topics included in the request body are
- created or updated; any existing overrides for topics not listed are left
- untouched.
-
- Structural validation of the request body fails fast with a single `400`. Beyond
- that, each topic is processed independently (partial-success, not
- all-or-nothing): valid topics are written and returned in `items`, while topics
- that cannot be applied are collected in `errors` with a per-topic `reason` (for
- example an unknown topic, a `REQUIRED` topic that cannot be opted out, a custom
- routing request that is not available on the workspace's plan, or a write
- failure). The request therefore returns `200` with both lists whenever the body
- is structurally valid.
-
- Every `topic_id` in the response — in both `items` and `errors` — is returned in
- Courier's canonical topic id form, regardless of the form supplied in the
- request.
+ Adds or updates a user's preferences for several subscription topics at once.
+ Topics you leave out keep whatever they were set to before.
Args:
topics: The topics to create or update. Between 1 and 50 topics may be provided in a
@@ -543,6 +507,15 @@ async def bulk_update(
"""
if not user_id:
raise ValueError(f"Expected a non-empty value for `user_id` but received {user_id!r}")
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return await self._post(
path_template("/users/{user_id}/preferences", user_id=user_id),
body=await async_maybe_transform(
@@ -574,9 +547,8 @@ async def delete_topic(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
"""
- Remove a user's preferences for a specific subscription topic, resetting the
- topic to its effective default. This operation is idempotent: deleting a
- preference that does not exist succeeds with no error.
+ Removes a user's override for one subscription topic, resetting it to the
+ effective default from the tenant or workspace.
Args:
tenant_id: Delete the preferences of a user for this specific tenant context.
@@ -622,7 +594,8 @@ async def retrieve_topic(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PreferenceRetrieveTopicResponse:
"""
- Fetch user preferences for a specific subscription topic.
+ Returns a user's opt-in status and channel choices for one subscription topic,
+ or the effective default if they have set no override.
Args:
tenant_id: Query the preferences of a user for this specific tenant context.
@@ -668,7 +641,8 @@ async def update_or_create_topic(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PreferenceUpdateOrCreateTopicResponse:
"""
- Update or Create user preferences for a specific subscription topic.
+ Sets a user's opt-in status and channel choices for one subscription topic,
+ overriding the tenant default for that topic only.
Args:
tenant_id: Update the preferences of a user for this specific tenant context.
diff --git a/src/courier/resources/users/tenants.py b/src/courier/resources/users/tenants.py
index 64b9fb56..b7b13102 100644
--- a/src/courier/resources/users/tenants.py
+++ b/src/courier/resources/users/tenants.py
@@ -25,6 +25,10 @@
class TenantsResource(SyncAPIResource):
+ """
+ Associate a user with one or more tenants, and read or remove those associations.
+ """
+
@cached_property
def with_raw_response(self) -> TenantsResourceWithRawResponse:
"""
@@ -57,8 +61,10 @@ def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> TenantListResponse:
- """
- Returns a paginated list of user tenant associations.
+ """Returns the tenants a user belongs to, with cursor paging.
+
+ A user can belong to
+ many tenants, each with its own profile and preferences.
Args:
cursor: Continue the pagination with the next cursor
@@ -105,11 +111,9 @@ def add_multiple(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """This endpoint is used to add a user to multiple tenants in one call.
-
- A custom
- profile can also be supplied for each tenant. This profile will be merged with
- the user's main profile when sending to the user with that tenant.
+ """
+ Adds a user to several tenants in one call, each optionally with a per-tenant
+ profile that overrides their workspace profile.
Args:
extra_headers: Send extra headers
@@ -146,10 +150,8 @@ def add_single(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
"""
- This endpoint is used to add a single tenant.
-
- A custom profile can also be supplied with the tenant. This profile will be
- merged with the user's main profile when sending to the user with that tenant.
+ Adds a user to one tenant, optionally with a tenant-specific profile that
+ overrides their workspace profile for sends in that tenant.
Args:
extra_headers: Send extra headers
@@ -185,8 +187,10 @@ def remove_all(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """
- Removes a user from any tenants they may have been associated with.
+ """Removes a user from every tenant they belong to in one call.
+
+ Their
+ workspace-level profile is a separate resource.
Args:
extra_headers: Send extra headers
@@ -220,8 +224,10 @@ def remove_single(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """
- Removes a user from the supplied tenant.
+ """Removes a user from one tenant.
+
+ Their other tenant memberships and workspace
+ profile are managed through separate endpoints.
Args:
extra_headers: Send extra headers
@@ -247,6 +253,10 @@ def remove_single(
class AsyncTenantsResource(AsyncAPIResource):
+ """
+ Associate a user with one or more tenants, and read or remove those associations.
+ """
+
@cached_property
def with_raw_response(self) -> AsyncTenantsResourceWithRawResponse:
"""
@@ -279,8 +289,10 @@ async def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> TenantListResponse:
- """
- Returns a paginated list of user tenant associations.
+ """Returns the tenants a user belongs to, with cursor paging.
+
+ A user can belong to
+ many tenants, each with its own profile and preferences.
Args:
cursor: Continue the pagination with the next cursor
@@ -327,11 +339,9 @@ async def add_multiple(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """This endpoint is used to add a user to multiple tenants in one call.
-
- A custom
- profile can also be supplied for each tenant. This profile will be merged with
- the user's main profile when sending to the user with that tenant.
+ """
+ Adds a user to several tenants in one call, each optionally with a per-tenant
+ profile that overrides their workspace profile.
Args:
extra_headers: Send extra headers
@@ -368,10 +378,8 @@ async def add_single(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
"""
- This endpoint is used to add a single tenant.
-
- A custom profile can also be supplied with the tenant. This profile will be
- merged with the user's main profile when sending to the user with that tenant.
+ Adds a user to one tenant, optionally with a tenant-specific profile that
+ overrides their workspace profile for sends in that tenant.
Args:
extra_headers: Send extra headers
@@ -407,8 +415,10 @@ async def remove_all(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """
- Removes a user from any tenants they may have been associated with.
+ """Removes a user from every tenant they belong to in one call.
+
+ Their
+ workspace-level profile is a separate resource.
Args:
extra_headers: Send extra headers
@@ -442,8 +452,10 @@ async def remove_single(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """
- Removes a user from the supplied tenant.
+ """Removes a user from one tenant.
+
+ Their other tenant memberships and workspace
+ profile are managed through separate endpoints.
Args:
extra_headers: Send extra headers
diff --git a/src/courier/resources/users/tokens.py b/src/courier/resources/users/tokens.py
index 964065af..c43acb01 100644
--- a/src/courier/resources/users/tokens.py
+++ b/src/courier/resources/users/tokens.py
@@ -26,6 +26,10 @@
class TokensResource(SyncAPIResource):
+ """
+ Register and manage the APNS and FCM device tokens Courier delivers push notifications to.
+ """
+
@cached_property
def with_raw_response(self) -> TokensResourceWithRawResponse:
"""
@@ -58,7 +62,8 @@ def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> TokenRetrieveResponse:
"""
- Get single token available for a `:token`
+ Returns one device token with its provider key, status and status reason, expiry
+ date, and any properties stored alongside it.
Args:
extra_headers: Send extra headers
@@ -95,7 +100,8 @@ def update(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
"""
- Apply a JSON Patch (RFC 6902) to the specified token.
+ Applies a JSON Patch to a device token, changing its status, expiry, or
+ properties without re-registering it.
Args:
extra_headers: Send extra headers
@@ -132,7 +138,8 @@ def list(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> TokenListResponse:
"""
- Gets all tokens available for a :user_id
+ Returns every device token registered for a user, each with its provider key,
+ status, and expiry date.
Args:
extra_headers: Send extra headers
@@ -166,7 +173,8 @@ def delete(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
"""
- Delete User Token
+ Deletes one device token for a user, addressed by the token value, so push sends
+ no longer target that device.
Args:
extra_headers: Send extra headers
@@ -202,7 +210,8 @@ def add_multiple(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
"""
- Adds multiple tokens to a user and overwrites matching existing tokens.
+ Registers several device tokens for a user in one call, overwriting any stored
+ token with a matching value.
Args:
extra_headers: Send extra headers
@@ -242,7 +251,8 @@ def add_single(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
"""
- Adds a single token to a user and overwrites a matching existing token.
+ Registers one device token for a user against a provider key, overwriting the
+ token if it already exists. Push sends resolve tokens per user.
Args:
device: Information about the device the token came from.
@@ -287,6 +297,10 @@ def add_single(
class AsyncTokensResource(AsyncAPIResource):
+ """
+ Register and manage the APNS and FCM device tokens Courier delivers push notifications to.
+ """
+
@cached_property
def with_raw_response(self) -> AsyncTokensResourceWithRawResponse:
"""
@@ -319,7 +333,8 @@ async def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> TokenRetrieveResponse:
"""
- Get single token available for a `:token`
+ Returns one device token with its provider key, status and status reason, expiry
+ date, and any properties stored alongside it.
Args:
extra_headers: Send extra headers
@@ -356,7 +371,8 @@ async def update(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
"""
- Apply a JSON Patch (RFC 6902) to the specified token.
+ Applies a JSON Patch to a device token, changing its status, expiry, or
+ properties without re-registering it.
Args:
extra_headers: Send extra headers
@@ -393,7 +409,8 @@ async def list(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> TokenListResponse:
"""
- Gets all tokens available for a :user_id
+ Returns every device token registered for a user, each with its provider key,
+ status, and expiry date.
Args:
extra_headers: Send extra headers
@@ -427,7 +444,8 @@ async def delete(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
"""
- Delete User Token
+ Deletes one device token for a user, addressed by the token value, so push sends
+ no longer target that device.
Args:
extra_headers: Send extra headers
@@ -463,7 +481,8 @@ async def add_multiple(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
"""
- Adds multiple tokens to a user and overwrites matching existing tokens.
+ Registers several device tokens for a user in one call, overwriting any stored
+ token with a matching value.
Args:
extra_headers: Send extra headers
@@ -503,7 +522,8 @@ async def add_single(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
"""
- Adds a single token to a user and overwrites a matching existing token.
+ Registers one device token for a user against a provider key, overwriting the
+ token if it already exists. Push sends resolve tokens per user.
Args:
device: Information about the device the token came from.
diff --git a/src/courier/resources/users/users.py b/src/courier/resources/users/users.py
index 185580b7..9d080525 100644
--- a/src/courier/resources/users/users.py
+++ b/src/courier/resources/users/users.py
@@ -35,14 +35,23 @@
class UsersResource(SyncAPIResource):
@cached_property
def preferences(self) -> PreferencesResource:
+ """
+ Read and write a single user's notification preferences, per topic and per channel.
+ """
return PreferencesResource(self._client)
@cached_property
def tenants(self) -> TenantsResource:
+ """
+ Associate a user with one or more tenants, and read or remove those associations.
+ """
return TenantsResource(self._client)
@cached_property
def tokens(self) -> TokensResource:
+ """
+ Register and manage the APNS and FCM device tokens Courier delivers push notifications to.
+ """
return TokensResource(self._client)
@cached_property
@@ -68,14 +77,23 @@ def with_streaming_response(self) -> UsersResourceWithStreamingResponse:
class AsyncUsersResource(AsyncAPIResource):
@cached_property
def preferences(self) -> AsyncPreferencesResource:
+ """
+ Read and write a single user's notification preferences, per topic and per channel.
+ """
return AsyncPreferencesResource(self._client)
@cached_property
def tenants(self) -> AsyncTenantsResource:
+ """
+ Associate a user with one or more tenants, and read or remove those associations.
+ """
return AsyncTenantsResource(self._client)
@cached_property
def tokens(self) -> AsyncTokensResource:
+ """
+ Register and manage the APNS and FCM device tokens Courier delivers push notifications to.
+ """
return AsyncTokensResource(self._client)
@cached_property
@@ -104,14 +122,23 @@ def __init__(self, users: UsersResource) -> None:
@cached_property
def preferences(self) -> PreferencesResourceWithRawResponse:
+ """
+ Read and write a single user's notification preferences, per topic and per channel.
+ """
return PreferencesResourceWithRawResponse(self._users.preferences)
@cached_property
def tenants(self) -> TenantsResourceWithRawResponse:
+ """
+ Associate a user with one or more tenants, and read or remove those associations.
+ """
return TenantsResourceWithRawResponse(self._users.tenants)
@cached_property
def tokens(self) -> TokensResourceWithRawResponse:
+ """
+ Register and manage the APNS and FCM device tokens Courier delivers push notifications to.
+ """
return TokensResourceWithRawResponse(self._users.tokens)
@@ -121,14 +148,23 @@ def __init__(self, users: AsyncUsersResource) -> None:
@cached_property
def preferences(self) -> AsyncPreferencesResourceWithRawResponse:
+ """
+ Read and write a single user's notification preferences, per topic and per channel.
+ """
return AsyncPreferencesResourceWithRawResponse(self._users.preferences)
@cached_property
def tenants(self) -> AsyncTenantsResourceWithRawResponse:
+ """
+ Associate a user with one or more tenants, and read or remove those associations.
+ """
return AsyncTenantsResourceWithRawResponse(self._users.tenants)
@cached_property
def tokens(self) -> AsyncTokensResourceWithRawResponse:
+ """
+ Register and manage the APNS and FCM device tokens Courier delivers push notifications to.
+ """
return AsyncTokensResourceWithRawResponse(self._users.tokens)
@@ -138,14 +174,23 @@ def __init__(self, users: UsersResource) -> None:
@cached_property
def preferences(self) -> PreferencesResourceWithStreamingResponse:
+ """
+ Read and write a single user's notification preferences, per topic and per channel.
+ """
return PreferencesResourceWithStreamingResponse(self._users.preferences)
@cached_property
def tenants(self) -> TenantsResourceWithStreamingResponse:
+ """
+ Associate a user with one or more tenants, and read or remove those associations.
+ """
return TenantsResourceWithStreamingResponse(self._users.tenants)
@cached_property
def tokens(self) -> TokensResourceWithStreamingResponse:
+ """
+ Register and manage the APNS and FCM device tokens Courier delivers push notifications to.
+ """
return TokensResourceWithStreamingResponse(self._users.tokens)
@@ -155,12 +200,21 @@ def __init__(self, users: AsyncUsersResource) -> None:
@cached_property
def preferences(self) -> AsyncPreferencesResourceWithStreamingResponse:
+ """
+ Read and write a single user's notification preferences, per topic and per channel.
+ """
return AsyncPreferencesResourceWithStreamingResponse(self._users.preferences)
@cached_property
def tenants(self) -> AsyncTenantsResourceWithStreamingResponse:
+ """
+ Associate a user with one or more tenants, and read or remove those associations.
+ """
return AsyncTenantsResourceWithStreamingResponse(self._users.tenants)
@cached_property
def tokens(self) -> AsyncTokensResourceWithStreamingResponse:
+ """
+ Register and manage the APNS and FCM device tokens Courier delivers push notifications to.
+ """
return AsyncTokensResourceWithStreamingResponse(self._users.tokens)
diff --git a/src/courier/resources/workspace_preferences/topics.py b/src/courier/resources/workspace_preferences/topics.py
index 7c2ca936..2e9d5f3b 100644
--- a/src/courier/resources/workspace_preferences/topics.py
+++ b/src/courier/resources/workspace_preferences/topics.py
@@ -8,7 +8,7 @@
import httpx
from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
-from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import (
@@ -27,6 +27,10 @@
class TopicsResource(SyncAPIResource):
+ """
+ Manage the workspace catalog of subscription topics, the sections that group them, and publishing the preference page.
+ """
+
@cached_property
def with_raw_response(self) -> TopicsResourceWithRawResponse:
"""
@@ -57,6 +61,8 @@ def create(
include_unsubscribe_header: Optional[bool] | Omit = omit,
routing_options: Optional[List[ChannelClassification]] | Omit = omit,
topic_data: Optional[Dict[str, object]] | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -64,11 +70,10 @@ def create(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> WorkspacePreferenceTopicGetResponse:
- """Create a subscription preference topic inside a workspace preference.
+ """Creates a subscription topic inside a workspace preference.
- Fails with
- 404 if the workspace preference does not exist. The topic id is generated and
- returned.
+ The default status
+ sets whether users start opted in, opted out, or required.
Args:
default_status: The default subscription status applied when a recipient has not set their own.
@@ -96,6 +101,15 @@ def create(
"""
if not section_id:
raise ValueError(f"Expected a non-empty value for `section_id` but received {section_id!r}")
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return self._post(
path_template("/preferences/sections/{section_id}/topics", section_id=section_id),
body=maybe_transform(
@@ -128,11 +142,9 @@ def retrieve(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> WorkspacePreferenceTopicGetResponse:
- """Retrieve a topic within a workspace preference.
-
- Returns 404 if the workspace
- preference does not exist, the topic does not exist, or the topic belongs to a
- different workspace preference.
+ """
+ Returns one subscription topic with its default status, routing options, allowed
+ preferences, and unsubscribe header setting.
Args:
extra_headers: Send extra headers
@@ -169,7 +181,8 @@ def list(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> WorkspacePreferenceTopicListResponse:
"""
- List the topics in a workspace preference.
+ Returns the subscription topics inside a workspace preference, each with its
+ default status and routing options.
Args:
extra_headers: Send extra headers
@@ -202,10 +215,9 @@ def archive(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Archive a topic and remove it from its workspace preference.
-
- Same 404 rules as
- GET.
+ """
+ Archives a subscription topic and removes it from its workspace preference,
+ addressed by section id and topic id.
Args:
extra_headers: Send extra headers
@@ -307,6 +319,10 @@ def replace(
class AsyncTopicsResource(AsyncAPIResource):
+ """
+ Manage the workspace catalog of subscription topics, the sections that group them, and publishing the preference page.
+ """
+
@cached_property
def with_raw_response(self) -> AsyncTopicsResourceWithRawResponse:
"""
@@ -337,6 +353,8 @@ async def create(
include_unsubscribe_header: Optional[bool] | Omit = omit,
routing_options: Optional[List[ChannelClassification]] | Omit = omit,
topic_data: Optional[Dict[str, object]] | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -344,11 +362,10 @@ async def create(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> WorkspacePreferenceTopicGetResponse:
- """Create a subscription preference topic inside a workspace preference.
+ """Creates a subscription topic inside a workspace preference.
- Fails with
- 404 if the workspace preference does not exist. The topic id is generated and
- returned.
+ The default status
+ sets whether users start opted in, opted out, or required.
Args:
default_status: The default subscription status applied when a recipient has not set their own.
@@ -376,6 +393,15 @@ async def create(
"""
if not section_id:
raise ValueError(f"Expected a non-empty value for `section_id` but received {section_id!r}")
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return await self._post(
path_template("/preferences/sections/{section_id}/topics", section_id=section_id),
body=await async_maybe_transform(
@@ -408,11 +434,9 @@ async def retrieve(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> WorkspacePreferenceTopicGetResponse:
- """Retrieve a topic within a workspace preference.
-
- Returns 404 if the workspace
- preference does not exist, the topic does not exist, or the topic belongs to a
- different workspace preference.
+ """
+ Returns one subscription topic with its default status, routing options, allowed
+ preferences, and unsubscribe header setting.
Args:
extra_headers: Send extra headers
@@ -449,7 +473,8 @@ async def list(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> WorkspacePreferenceTopicListResponse:
"""
- List the topics in a workspace preference.
+ Returns the subscription topics inside a workspace preference, each with its
+ default status and routing options.
Args:
extra_headers: Send extra headers
@@ -482,10 +507,9 @@ async def archive(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> None:
- """Archive a topic and remove it from its workspace preference.
-
- Same 404 rules as
- GET.
+ """
+ Archives a subscription topic and removes it from its workspace preference,
+ addressed by section id and topic id.
Args:
extra_headers: Send extra headers
diff --git a/src/courier/resources/workspace_preferences/workspace_preferences.py b/src/courier/resources/workspace_preferences/workspace_preferences.py
index 361f9551..cdb79e2d 100644
--- a/src/courier/resources/workspace_preferences/workspace_preferences.py
+++ b/src/courier/resources/workspace_preferences/workspace_preferences.py
@@ -20,7 +20,7 @@
workspace_preference_replace_params,
)
from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
-from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform
from ..._compat import cached_property
from ..._resource import SyncAPIResource, AsyncAPIResource
from ..._response import (
@@ -39,8 +39,15 @@
class WorkspacePreferencesResource(SyncAPIResource):
+ """
+ Manage the workspace catalog of subscription topics, the sections that group them, and publishing the preference page.
+ """
+
@cached_property
def topics(self) -> TopicsResource:
+ """
+ Manage the workspace catalog of subscription topics, the sections that group them, and publishing the preference page.
+ """
return TopicsResource(self._client)
@cached_property
@@ -69,6 +76,8 @@ def create(
description: Optional[str] | Omit = omit,
has_custom_routing: Optional[bool] | Omit = omit,
routing_options: Optional[List[ChannelClassification]] | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -76,11 +85,10 @@ def create(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> WorkspacePreferenceGetResponse:
- """Create a workspace preference.
+ """Creates a workspace preference and returns its generated id.
- The workspace preference id is generated and
- returned. Topics are created inside a workspace preference via POST
- /preferences/sections/{section_id}/topics.
+ Add subscription
+ topics to it afterwards with the topics endpoint.
Args:
name: Human-readable name for the workspace preference.
@@ -99,6 +107,15 @@ def create(
timeout: Override the client-level default timeout for this request, in seconds
"""
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return self._post(
"/preferences/sections",
body=maybe_transform(
@@ -128,7 +145,8 @@ def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> WorkspacePreferenceGetResponse:
"""
- Retrieve a workspace preference by id, including its topics.
+ Returns one workspace preference by id, including its subscription topics,
+ routing options, and custom routing flag.
Args:
extra_headers: Send extra headers
@@ -159,10 +177,9 @@ def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> WorkspacePreferenceListResponse:
- """List the workspace's preferences.
-
- Each workspace preference embeds its topics.
- Scoped to the workspace of the API key.
+ """
+ Returns the workspace's preferences, each embedding its subscription topics,
+ routing options, and whether custom routing is allowed.
"""
return self._get(
"/preferences/sections",
@@ -214,6 +231,8 @@ def publish(
brand_id: Optional[str] | Omit = omit,
description: Optional[str] | Omit = omit,
heading: Optional[str] | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -221,11 +240,9 @@ def publish(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PublishPreferencesResponse:
- """Publish the workspace's preferences page.
-
- Takes a snapshot of every workspace
- preference with its topics under a new published version, making the current
- state visible on the hosted preferences page (non-draft).
+ """
+ Publishes the workspace preference page, snapshotting every preference and
+ topic, and returns the page id and a preview URL.
Args:
brand_id: Brand for the hosted page - "default" (workspace default brand), "none" (no
@@ -243,6 +260,15 @@ def publish(
timeout: Override the client-level default timeout for this request, in seconds
"""
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return self._post(
"/preferences/publish",
body=maybe_transform(
@@ -318,8 +344,15 @@ def replace(
class AsyncWorkspacePreferencesResource(AsyncAPIResource):
+ """
+ Manage the workspace catalog of subscription topics, the sections that group them, and publishing the preference page.
+ """
+
@cached_property
def topics(self) -> AsyncTopicsResource:
+ """
+ Manage the workspace catalog of subscription topics, the sections that group them, and publishing the preference page.
+ """
return AsyncTopicsResource(self._client)
@cached_property
@@ -348,6 +381,8 @@ async def create(
description: Optional[str] | Omit = omit,
has_custom_routing: Optional[bool] | Omit = omit,
routing_options: Optional[List[ChannelClassification]] | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -355,11 +390,10 @@ async def create(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> WorkspacePreferenceGetResponse:
- """Create a workspace preference.
+ """Creates a workspace preference and returns its generated id.
- The workspace preference id is generated and
- returned. Topics are created inside a workspace preference via POST
- /preferences/sections/{section_id}/topics.
+ Add subscription
+ topics to it afterwards with the topics endpoint.
Args:
name: Human-readable name for the workspace preference.
@@ -378,6 +412,15 @@ async def create(
timeout: Override the client-level default timeout for this request, in seconds
"""
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return await self._post(
"/preferences/sections",
body=await async_maybe_transform(
@@ -407,7 +450,8 @@ async def retrieve(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> WorkspacePreferenceGetResponse:
"""
- Retrieve a workspace preference by id, including its topics.
+ Returns one workspace preference by id, including its subscription topics,
+ routing options, and custom routing flag.
Args:
extra_headers: Send extra headers
@@ -438,10 +482,9 @@ async def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> WorkspacePreferenceListResponse:
- """List the workspace's preferences.
-
- Each workspace preference embeds its topics.
- Scoped to the workspace of the API key.
+ """
+ Returns the workspace's preferences, each embedding its subscription topics,
+ routing options, and whether custom routing is allowed.
"""
return await self._get(
"/preferences/sections",
@@ -493,6 +536,8 @@ async def publish(
brand_id: Optional[str] | Omit = omit,
description: Optional[str] | Omit = omit,
heading: Optional[str] | Omit = omit,
+ idempotency_key: str | Omit = omit,
+ x_idempotency_expiration: 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,
@@ -500,11 +545,9 @@ async def publish(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> PublishPreferencesResponse:
- """Publish the workspace's preferences page.
-
- Takes a snapshot of every workspace
- preference with its topics under a new published version, making the current
- state visible on the hosted preferences page (non-draft).
+ """
+ Publishes the workspace preference page, snapshotting every preference and
+ topic, and returns the page id and a preview URL.
Args:
brand_id: Brand for the hosted page - "default" (workspace default brand), "none" (no
@@ -522,6 +565,15 @@ async def publish(
timeout: Override the client-level default timeout for this request, in seconds
"""
+ extra_headers = {
+ **strip_not_given(
+ {
+ "Idempotency-Key": idempotency_key,
+ "x-idempotency-expiration": x_idempotency_expiration,
+ }
+ ),
+ **(extra_headers or {}),
+ }
return await self._post(
"/preferences/publish",
body=await async_maybe_transform(
@@ -621,6 +673,9 @@ def __init__(self, workspace_preferences: WorkspacePreferencesResource) -> None:
@cached_property
def topics(self) -> TopicsResourceWithRawResponse:
+ """
+ Manage the workspace catalog of subscription topics, the sections that group them, and publishing the preference page.
+ """
return TopicsResourceWithRawResponse(self._workspace_preferences.topics)
@@ -649,6 +704,9 @@ def __init__(self, workspace_preferences: AsyncWorkspacePreferencesResource) ->
@cached_property
def topics(self) -> AsyncTopicsResourceWithRawResponse:
+ """
+ Manage the workspace catalog of subscription topics, the sections that group them, and publishing the preference page.
+ """
return AsyncTopicsResourceWithRawResponse(self._workspace_preferences.topics)
@@ -677,6 +735,9 @@ def __init__(self, workspace_preferences: WorkspacePreferencesResource) -> None:
@cached_property
def topics(self) -> TopicsResourceWithStreamingResponse:
+ """
+ Manage the workspace catalog of subscription topics, the sections that group them, and publishing the preference page.
+ """
return TopicsResourceWithStreamingResponse(self._workspace_preferences.topics)
@@ -705,4 +766,7 @@ def __init__(self, workspace_preferences: AsyncWorkspacePreferencesResource) ->
@cached_property
def topics(self) -> AsyncTopicsResourceWithStreamingResponse:
+ """
+ Manage the workspace catalog of subscription topics, the sections that group them, and publishing the preference page.
+ """
return AsyncTopicsResourceWithStreamingResponse(self._workspace_preferences.topics)
diff --git a/src/courier/types/automations/invoke_invoke_ad_hoc_params.py b/src/courier/types/automations/invoke_invoke_ad_hoc_params.py
index 8a4b76c6..d2d903c8 100644
--- a/src/courier/types/automations/invoke_invoke_ad_hoc_params.py
+++ b/src/courier/types/automations/invoke_invoke_ad_hoc_params.py
@@ -3,7 +3,9 @@
from __future__ import annotations
from typing import Dict, Union, Iterable, Optional
-from typing_extensions import Literal, Required, TypeAlias, TypedDict
+from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict
+
+from ..._utils import PropertyInfo
__all__ = [
"InvokeInvokeAdHocParams",
@@ -33,6 +35,10 @@ class InvokeInvokeAdHocParams(TypedDict, total=False):
template: Optional[str]
+ idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
+
+ x_idempotency_expiration: Annotated[str, PropertyInfo(alias="x-idempotency-expiration")]
+
class AutomationStepAutomationDelayStep(TypedDict, total=False):
action: Required[Literal["delay"]]
diff --git a/src/courier/types/automations/invoke_invoke_by_template_params.py b/src/courier/types/automations/invoke_invoke_by_template_params.py
index ffe4af83..5a98e774 100644
--- a/src/courier/types/automations/invoke_invoke_by_template_params.py
+++ b/src/courier/types/automations/invoke_invoke_by_template_params.py
@@ -3,7 +3,9 @@
from __future__ import annotations
from typing import Dict, Optional
-from typing_extensions import Required, TypedDict
+from typing_extensions import Required, Annotated, TypedDict
+
+from ..._utils import PropertyInfo
__all__ = ["InvokeInvokeByTemplateParams"]
@@ -18,3 +20,7 @@ class InvokeInvokeByTemplateParams(TypedDict, total=False):
profile: Optional[Dict[str, object]]
template: Optional[str]
+
+ idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
+
+ x_idempotency_expiration: Annotated[str, PropertyInfo(alias="x-idempotency-expiration")]
diff --git a/src/courier/types/brand_create_params.py b/src/courier/types/brand_create_params.py
index e7c8ed7a..5afcc3fe 100644
--- a/src/courier/types/brand_create_params.py
+++ b/src/courier/types/brand_create_params.py
@@ -3,8 +3,9 @@
from __future__ import annotations
from typing import Optional
-from typing_extensions import Required, TypedDict
+from typing_extensions import Required, Annotated, TypedDict
+from .._utils import PropertyInfo
from .brand_settings_param import BrandSettingsParam
from .brand_snippets_param import BrandSnippetsParam
@@ -19,3 +20,7 @@ class BrandCreateParams(TypedDict, total=False):
id: Optional[str]
snippets: Optional[BrandSnippetsParam]
+
+ idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
+
+ x_idempotency_expiration: Annotated[str, PropertyInfo(alias="x-idempotency-expiration")]
diff --git a/src/courier/types/inbox/__init__.py b/src/courier/types/inbox/__init__.py
new file mode 100644
index 00000000..f8ee8b14
--- /dev/null
+++ b/src/courier/types/inbox/__init__.py
@@ -0,0 +1,3 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
diff --git a/src/courier/types/journey_cancel_params.py b/src/courier/types/journey_cancel_params.py
index 3287312a..e7eb6af5 100644
--- a/src/courier/types/journey_cancel_params.py
+++ b/src/courier/types/journey_cancel_params.py
@@ -3,7 +3,9 @@
from __future__ import annotations
from typing import Union
-from typing_extensions import Required, TypeAlias, TypedDict
+from typing_extensions import Required, Annotated, TypeAlias, TypedDict
+
+from .._utils import PropertyInfo
__all__ = ["JourneyCancelParams", "ByCancelationToken", "ByRunID"]
@@ -11,9 +13,17 @@
class ByCancelationToken(TypedDict, total=False):
cancelation_token: Required[str]
+ idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
+
+ x_idempotency_expiration: Annotated[str, PropertyInfo(alias="x-idempotency-expiration")]
+
class ByRunID(TypedDict, total=False):
run_id: Required[str]
+ idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
+
+ x_idempotency_expiration: Annotated[str, PropertyInfo(alias="x-idempotency-expiration")]
+
JourneyCancelParams: TypeAlias = Union[ByCancelationToken, ByRunID]
diff --git a/src/courier/types/journey_create_params.py b/src/courier/types/journey_create_params.py
index c5326eef..bec2d0f0 100644
--- a/src/courier/types/journey_create_params.py
+++ b/src/courier/types/journey_create_params.py
@@ -3,8 +3,9 @@
from __future__ import annotations
from typing import Iterable
-from typing_extensions import Required, TypedDict
+from typing_extensions import Required, Annotated, TypedDict
+from .._utils import PropertyInfo
from .journey_state import JourneyState
__all__ = ["JourneyCreateParams"]
@@ -20,5 +21,9 @@ class JourneyCreateParams(TypedDict, total=False):
state: JourneyState
"""Lifecycle state of a journey."""
+ idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
+
+ x_idempotency_expiration: Annotated[str, PropertyInfo(alias="x-idempotency-expiration")]
+
from .journey_node_param import JourneyNodeParam
diff --git a/src/courier/types/journey_invoke_params.py b/src/courier/types/journey_invoke_params.py
index 07149e0a..bfc187e1 100644
--- a/src/courier/types/journey_invoke_params.py
+++ b/src/courier/types/journey_invoke_params.py
@@ -3,7 +3,9 @@
from __future__ import annotations
from typing import Dict
-from typing_extensions import TypedDict
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
__all__ = ["JourneyInvokeParams"]
@@ -33,3 +35,7 @@ class JourneyInvokeParams(TypedDict, total=False):
If not provided, the system will attempt to resolve the user identifier from
profile or data objects.
"""
+
+ idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
+
+ x_idempotency_expiration: Annotated[str, PropertyInfo(alias="x-idempotency-expiration")]
diff --git a/src/courier/types/journey_publish_params.py b/src/courier/types/journey_publish_params.py
index 2c6fcb7f..c0f6b89c 100644
--- a/src/courier/types/journey_publish_params.py
+++ b/src/courier/types/journey_publish_params.py
@@ -2,10 +2,16 @@
from __future__ import annotations
-from typing_extensions import TypedDict
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
__all__ = ["JourneyPublishParams"]
class JourneyPublishParams(TypedDict, total=False):
version: str
+
+ idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
+
+ x_idempotency_expiration: Annotated[str, PropertyInfo(alias="x-idempotency-expiration")]
diff --git a/src/courier/types/journeys/template_create_params.py b/src/courier/types/journeys/template_create_params.py
index e3a4bb80..514816d7 100644
--- a/src/courier/types/journeys/template_create_params.py
+++ b/src/courier/types/journeys/template_create_params.py
@@ -27,6 +27,10 @@ class TemplateCreateParams(TypedDict, total=False):
state: str
+ idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
+
+ x_idempotency_expiration: Annotated[str, PropertyInfo(alias="x-idempotency-expiration")]
+
class NotificationBrand(TypedDict, total=False):
id: Required[str]
diff --git a/src/courier/types/journeys/template_publish_params.py b/src/courier/types/journeys/template_publish_params.py
index efbfdd7a..2c9693e6 100644
--- a/src/courier/types/journeys/template_publish_params.py
+++ b/src/courier/types/journeys/template_publish_params.py
@@ -13,3 +13,7 @@ class TemplatePublishParams(TypedDict, total=False):
template_id: Required[Annotated[str, PropertyInfo(alias="templateId")]]
version: str
+
+ idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
+
+ x_idempotency_expiration: Annotated[str, PropertyInfo(alias="x-idempotency-expiration")]
diff --git a/src/courier/types/lists/subscription_add_params.py b/src/courier/types/lists/subscription_add_params.py
index 3c695de0..46d79bbd 100644
--- a/src/courier/types/lists/subscription_add_params.py
+++ b/src/courier/types/lists/subscription_add_params.py
@@ -3,8 +3,9 @@
from __future__ import annotations
from typing import Iterable
-from typing_extensions import Required, TypedDict
+from typing_extensions import Required, Annotated, TypedDict
+from ..._utils import PropertyInfo
from ..put_subscriptions_recipient_param import PutSubscriptionsRecipientParam
__all__ = ["SubscriptionAddParams"]
@@ -12,3 +13,7 @@
class SubscriptionAddParams(TypedDict, total=False):
recipients: Required[Iterable[PutSubscriptionsRecipientParam]]
+
+ idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
+
+ x_idempotency_expiration: Annotated[str, PropertyInfo(alias="x-idempotency-expiration")]
diff --git a/src/courier/types/notification_create_params.py b/src/courier/types/notification_create_params.py
index 242017c7..1fb8cff1 100644
--- a/src/courier/types/notification_create_params.py
+++ b/src/courier/types/notification_create_params.py
@@ -2,8 +2,9 @@
from __future__ import annotations
-from typing_extensions import Literal, Required, TypedDict
+from typing_extensions import Literal, Required, Annotated, TypedDict
+from .._utils import PropertyInfo
from .notification_template_payload_param import NotificationTemplatePayloadParam
__all__ = ["NotificationCreateParams"]
@@ -22,3 +23,7 @@ class NotificationCreateParams(TypedDict, total=False):
Case-insensitive input, normalized to uppercase in the response. Defaults to
"DRAFT".
"""
+
+ idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
+
+ x_idempotency_expiration: Annotated[str, PropertyInfo(alias="x-idempotency-expiration")]
diff --git a/src/courier/types/notification_publish_params.py b/src/courier/types/notification_publish_params.py
index df41fc72..b5945921 100644
--- a/src/courier/types/notification_publish_params.py
+++ b/src/courier/types/notification_publish_params.py
@@ -2,7 +2,9 @@
from __future__ import annotations
-from typing_extensions import TypedDict
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
__all__ = ["NotificationPublishParams"]
@@ -10,3 +12,7 @@
class NotificationPublishParams(TypedDict, total=False):
version: str
"""Historical version to publish (e.g. "v001"). Omit to publish the current draft."""
+
+ idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
+
+ x_idempotency_expiration: Annotated[str, PropertyInfo(alias="x-idempotency-expiration")]
diff --git a/src/courier/types/profile_create_params.py b/src/courier/types/profile_create_params.py
index e37d1735..939ebb1a 100644
--- a/src/courier/types/profile_create_params.py
+++ b/src/courier/types/profile_create_params.py
@@ -3,10 +3,16 @@
from __future__ import annotations
from typing import Dict
-from typing_extensions import Required, TypedDict
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
__all__ = ["ProfileCreateParams"]
class ProfileCreateParams(TypedDict, total=False):
profile: Required[Dict[str, object]]
+
+ idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
+
+ x_idempotency_expiration: Annotated[str, PropertyInfo(alias="x-idempotency-expiration")]
diff --git a/src/courier/types/profiles/list_subscribe_params.py b/src/courier/types/profiles/list_subscribe_params.py
index 699069f3..2251c365 100644
--- a/src/courier/types/profiles/list_subscribe_params.py
+++ b/src/courier/types/profiles/list_subscribe_params.py
@@ -3,8 +3,9 @@
from __future__ import annotations
from typing import Iterable
-from typing_extensions import Required, TypedDict
+from typing_extensions import Required, Annotated, TypedDict
+from ..._utils import PropertyInfo
from ..subscribe_to_lists_request_item_param import SubscribeToListsRequestItemParam
__all__ = ["ListSubscribeParams"]
@@ -12,3 +13,7 @@
class ListSubscribeParams(TypedDict, total=False):
lists: Required[Iterable[SubscribeToListsRequestItemParam]]
+
+ idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
+
+ x_idempotency_expiration: Annotated[str, PropertyInfo(alias="x-idempotency-expiration")]
diff --git a/src/courier/types/provider_create_params.py b/src/courier/types/provider_create_params.py
index 0f7642f0..687be93f 100644
--- a/src/courier/types/provider_create_params.py
+++ b/src/courier/types/provider_create_params.py
@@ -3,7 +3,9 @@
from __future__ import annotations
from typing import Dict
-from typing_extensions import Required, TypedDict
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._utils import PropertyInfo
__all__ = ["ProviderCreateParams"]
@@ -29,3 +31,7 @@ class ProviderCreateParams(TypedDict, total=False):
title: str
"""Optional display title. Omit to use "Default Configuration"."""
+
+ idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
+
+ x_idempotency_expiration: Annotated[str, PropertyInfo(alias="x-idempotency-expiration")]
diff --git a/src/courier/types/routing_strategy_create_params.py b/src/courier/types/routing_strategy_create_params.py
index f62dfd5e..72a5e0df 100644
--- a/src/courier/types/routing_strategy_create_params.py
+++ b/src/courier/types/routing_strategy_create_params.py
@@ -3,9 +3,10 @@
from __future__ import annotations
from typing import Optional
-from typing_extensions import Required, TypedDict
+from typing_extensions import Required, Annotated, TypedDict
from .._types import SequenceNotStr
+from .._utils import PropertyInfo
from .shared_params.message_channels import MessageChannels
from .shared_params.message_providers import MessageProviders
@@ -31,5 +32,9 @@ class RoutingStrategyCreateParams(TypedDict, total=False):
tags: Optional[SequenceNotStr[str]]
"""Optional tags for categorization."""
+ idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
+
+ x_idempotency_expiration: Annotated[str, PropertyInfo(alias="x-idempotency-expiration")]
+
from .shared_params.message_routing import MessageRouting
diff --git a/src/courier/types/send_message_params.py b/src/courier/types/send_message_params.py
index ad50e405..f365ac64 100644
--- a/src/courier/types/send_message_params.py
+++ b/src/courier/types/send_message_params.py
@@ -3,9 +3,10 @@
from __future__ import annotations
from typing import Dict, Union, Iterable, Optional
-from typing_extensions import Literal, Required, TypeAlias, TypedDict
+from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict
from .._types import SequenceNotStr
+from .._utils import PropertyInfo
from .shared_params.utm import Utm
from .shared_params.list_recipient import ListRecipient
from .shared_params.user_recipient import UserRecipient
@@ -43,6 +44,10 @@ class SendMessageParams(TypedDict, total=False):
They define the destination and content of the message.
"""
+ idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
+
+ x_idempotency_expiration: Annotated[str, PropertyInfo(alias="x-idempotency-expiration")]
+
MessageContent: TypeAlias = Union[ElementalContentSugar, ElementalContent]
diff --git a/src/courier/types/users/preference_bulk_update_params.py b/src/courier/types/users/preference_bulk_update_params.py
index 70f79640..0de00849 100644
--- a/src/courier/types/users/preference_bulk_update_params.py
+++ b/src/courier/types/users/preference_bulk_update_params.py
@@ -3,8 +3,9 @@
from __future__ import annotations
from typing import List, Iterable, Optional
-from typing_extensions import Literal, Required, TypedDict
+from typing_extensions import Literal, Required, Annotated, TypedDict
+from ..._utils import PropertyInfo
from ..shared.channel_classification import ChannelClassification
__all__ = ["PreferenceBulkUpdateParams", "Topic"]
@@ -20,6 +21,10 @@ class PreferenceBulkUpdateParams(TypedDict, total=False):
tenant_id: Optional[str]
"""Update the preferences of a user for this specific tenant context."""
+ idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
+
+ x_idempotency_expiration: Annotated[str, PropertyInfo(alias="x-idempotency-expiration")]
+
class Topic(TypedDict, total=False):
status: Required[Literal["OPTED_IN", "OPTED_OUT"]]
diff --git a/src/courier/types/users/preference_update_or_create_topic_params.py b/src/courier/types/users/preference_update_or_create_topic_params.py
index 434de14b..6eb74fdd 100644
--- a/src/courier/types/users/preference_update_or_create_topic_params.py
+++ b/src/courier/types/users/preference_update_or_create_topic_params.py
@@ -22,8 +22,20 @@ class PreferenceUpdateOrCreateTopicParams(TypedDict, total=False):
class Topic(TypedDict, total=False):
status: Required[PreferenceStatus]
+ """The subscription status to set: OPTED_IN or OPTED_OUT.
+
+ REQUIRED is a topic-level default, not a user choice; the API rejects opting a
+ user out of a REQUIRED topic.
+ """
custom_routing: Optional[List[ChannelClassification]]
- """The Channels a user has chosen to receive notifications through for this topic"""
+ """The channels to deliver this topic on when has_custom_routing is true.
+
+ One or more of: direct_message, email, push, sms, webhook, inbox.
+ """
has_custom_routing: Optional[bool]
+ """
+ Set to true to route this topic to the channels in custom_routing instead of the
+ topic's default routing.
+ """
diff --git a/src/courier/types/users/topic_preference.py b/src/courier/types/users/topic_preference.py
index 55e74c03..b719e0e8 100644
--- a/src/courier/types/users/topic_preference.py
+++ b/src/courier/types/users/topic_preference.py
@@ -11,14 +11,34 @@
class TopicPreference(BaseModel):
default_status: PreferenceStatus
+ """The topic's default status, returned on reads.
+
+ It applies whenever the user has no override of their own (status equals this
+ value).
+ """
status: PreferenceStatus
+ """The user's subscription status for this topic.
+
+ OPTED_IN or OPTED_OUT reflect the user's own choice; REQUIRED is a topic-level
+ default set in the preferences editor, not a user choice.
+ """
topic_id: str
+ """The unique identifier of the subscription topic this preference applies to."""
topic_name: str
+ """The display name of the subscription topic, returned on reads."""
custom_routing: Optional[List[ChannelClassification]] = None
- """The Channels a user has chosen to receive notifications through for this topic"""
+ """
+ The channels the user has chosen to receive this topic on, present only when
+ has_custom_routing is true. One or more of: direct_message, email, push, sms,
+ webhook, inbox.
+ """
has_custom_routing: Optional[bool] = None
+ """
+ Whether the user has chosen specific delivery channels for this topic (listed in
+ custom_routing) rather than the topic's default routing.
+ """
diff --git a/src/courier/types/workspace_preference_create_params.py b/src/courier/types/workspace_preference_create_params.py
index 8f3367ca..c5dbbf10 100644
--- a/src/courier/types/workspace_preference_create_params.py
+++ b/src/courier/types/workspace_preference_create_params.py
@@ -3,8 +3,9 @@
from __future__ import annotations
from typing import List, Optional
-from typing_extensions import Required, TypedDict
+from typing_extensions import Required, Annotated, TypedDict
+from .._utils import PropertyInfo
from .shared.channel_classification import ChannelClassification
__all__ = ["WorkspacePreferenceCreateParams"]
@@ -22,3 +23,7 @@ class WorkspacePreferenceCreateParams(TypedDict, total=False):
routing_options: Optional[List[ChannelClassification]]
"""Default channels for the workspace preference. Defaults to empty if omitted."""
+
+ idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
+
+ x_idempotency_expiration: Annotated[str, PropertyInfo(alias="x-idempotency-expiration")]
diff --git a/src/courier/types/workspace_preference_publish_params.py b/src/courier/types/workspace_preference_publish_params.py
index 180318c9..50e3b0fb 100644
--- a/src/courier/types/workspace_preference_publish_params.py
+++ b/src/courier/types/workspace_preference_publish_params.py
@@ -3,7 +3,9 @@
from __future__ import annotations
from typing import Optional
-from typing_extensions import TypedDict
+from typing_extensions import Annotated, TypedDict
+
+from .._utils import PropertyInfo
__all__ = ["WorkspacePreferencePublishParams"]
@@ -20,3 +22,7 @@ class WorkspacePreferencePublishParams(TypedDict, total=False):
heading: Optional[str]
"""Heading shown at the top of the hosted preferences page."""
+
+ idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
+
+ x_idempotency_expiration: Annotated[str, PropertyInfo(alias="x-idempotency-expiration")]
diff --git a/src/courier/types/workspace_preferences/topic_create_params.py b/src/courier/types/workspace_preferences/topic_create_params.py
index 698c9426..d6a926e3 100644
--- a/src/courier/types/workspace_preferences/topic_create_params.py
+++ b/src/courier/types/workspace_preferences/topic_create_params.py
@@ -3,8 +3,9 @@
from __future__ import annotations
from typing import Dict, List, Optional
-from typing_extensions import Literal, Required, TypedDict
+from typing_extensions import Literal, Required, Annotated, TypedDict
+from ..._utils import PropertyInfo
from ..shared.channel_classification import ChannelClassification
__all__ = ["TopicCreateParams"]
@@ -34,3 +35,7 @@ class TopicCreateParams(TypedDict, total=False):
topic_data: Optional[Dict[str, object]]
"""Arbitrary metadata associated with the topic."""
+
+ idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
+
+ x_idempotency_expiration: Annotated[str, PropertyInfo(alias="x-idempotency-expiration")]
diff --git a/tests/api_resources/automations/test_invoke.py b/tests/api_resources/automations/test_invoke.py
index 081b6009..a2fd132a 100644
--- a/tests/api_resources/automations/test_invoke.py
+++ b/tests/api_resources/automations/test_invoke.py
@@ -52,6 +52,8 @@ def test_method_invoke_ad_hoc_with_all_params(self, client: Courier) -> None:
profile={"tenant_id": "bar"},
recipient="user-yes",
template="template",
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(AutomationInvokeResponse, invoke, path=["response"])
@@ -100,6 +102,8 @@ def test_method_invoke_by_template_with_all_params(self, client: Courier) -> Non
data={"foo": "bar"},
profile={"foo": "bar"},
template="template",
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(AutomationInvokeResponse, invoke, path=["response"])
@@ -181,6 +185,8 @@ async def test_method_invoke_ad_hoc_with_all_params(self, async_client: AsyncCou
profile={"tenant_id": "bar"},
recipient="user-yes",
template="template",
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(AutomationInvokeResponse, invoke, path=["response"])
@@ -229,6 +235,8 @@ async def test_method_invoke_by_template_with_all_params(self, async_client: Asy
data={"foo": "bar"},
profile={"foo": "bar"},
template="template",
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(AutomationInvokeResponse, invoke, path=["response"])
diff --git a/tests/api_resources/inbox/__init__.py b/tests/api_resources/inbox/__init__.py
new file mode 100644
index 00000000..fd8019a9
--- /dev/null
+++ b/tests/api_resources/inbox/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/inbox/test_messages.py b/tests/api_resources/inbox/test_messages.py
new file mode 100644
index 00000000..129fbfe4
--- /dev/null
+++ b/tests/api_resources/inbox/test_messages.py
@@ -0,0 +1,190 @@
+# 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
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestMessages:
+ 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_delete(self, client: Courier) -> None:
+ message = client.inbox.messages.delete(
+ "message_id",
+ )
+ assert message is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_delete(self, client: Courier) -> None:
+ response = client.inbox.messages.with_raw_response.delete(
+ "message_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ message = response.parse()
+ assert message is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_delete(self, client: Courier) -> None:
+ with client.inbox.messages.with_streaming_response.delete(
+ "message_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ message = response.parse()
+ assert message is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_delete(self, client: Courier) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"):
+ client.inbox.messages.with_raw_response.delete(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_restore(self, client: Courier) -> None:
+ message = client.inbox.messages.restore(
+ "message_id",
+ )
+ assert message is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_restore(self, client: Courier) -> None:
+ response = client.inbox.messages.with_raw_response.restore(
+ "message_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ message = response.parse()
+ assert message is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_restore(self, client: Courier) -> None:
+ with client.inbox.messages.with_streaming_response.restore(
+ "message_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ message = response.parse()
+ assert message is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_restore(self, client: Courier) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"):
+ client.inbox.messages.with_raw_response.restore(
+ "",
+ )
+
+
+class TestAsyncMessages:
+ 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_delete(self, async_client: AsyncCourier) -> None:
+ message = await async_client.inbox.messages.delete(
+ "message_id",
+ )
+ assert message is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_delete(self, async_client: AsyncCourier) -> None:
+ response = await async_client.inbox.messages.with_raw_response.delete(
+ "message_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ message = await response.parse()
+ assert message is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_delete(self, async_client: AsyncCourier) -> None:
+ async with async_client.inbox.messages.with_streaming_response.delete(
+ "message_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ message = await response.parse()
+ assert message is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_delete(self, async_client: AsyncCourier) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"):
+ await async_client.inbox.messages.with_raw_response.delete(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_restore(self, async_client: AsyncCourier) -> None:
+ message = await async_client.inbox.messages.restore(
+ "message_id",
+ )
+ assert message is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_restore(self, async_client: AsyncCourier) -> None:
+ response = await async_client.inbox.messages.with_raw_response.restore(
+ "message_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ message = await response.parse()
+ assert message is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_restore(self, async_client: AsyncCourier) -> None:
+ async with async_client.inbox.messages.with_streaming_response.restore(
+ "message_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ message = await response.parse()
+ assert message is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_restore(self, async_client: AsyncCourier) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"):
+ await async_client.inbox.messages.with_raw_response.restore(
+ "",
+ )
diff --git a/tests/api_resources/journeys/test_templates.py b/tests/api_resources/journeys/test_templates.py
index 80149a4b..bde3602b 100644
--- a/tests/api_resources/journeys/test_templates.py
+++ b/tests/api_resources/journeys/test_templates.py
@@ -69,6 +69,8 @@ def test_method_create_with_all_params(self, client: Courier) -> None:
},
provider_key="x",
state="state",
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(JourneyTemplateGetResponse, template, path=["response"])
@@ -363,6 +365,8 @@ def test_method_publish_with_all_params(self, client: Courier) -> None:
notification_id="x",
template_id="x",
version="v321669910225",
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert template is None
@@ -807,6 +811,8 @@ async def test_method_create_with_all_params(self, async_client: AsyncCourier) -
},
provider_key="x",
state="state",
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(JourneyTemplateGetResponse, template, path=["response"])
@@ -1101,6 +1107,8 @@ async def test_method_publish_with_all_params(self, async_client: AsyncCourier)
notification_id="x",
template_id="x",
version="v321669910225",
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert template is None
diff --git a/tests/api_resources/lists/test_subscriptions.py b/tests/api_resources/lists/test_subscriptions.py
index ee6936d2..b51d1e4b 100644
--- a/tests/api_resources/lists/test_subscriptions.py
+++ b/tests/api_resources/lists/test_subscriptions.py
@@ -79,6 +79,47 @@ def test_method_add(self, client: Courier) -> None:
)
assert subscription is None
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_add_with_all_params(self, client: Courier) -> None:
+ subscription = client.lists.subscriptions.add(
+ list_id="list_id",
+ recipients=[
+ {
+ "recipient_id": "recipientId",
+ "preferences": {
+ "categories": {
+ "foo": {
+ "status": "OPTED_IN",
+ "channel_preferences": [{"channel": "direct_message"}],
+ "rules": [
+ {
+ "until": "until",
+ "start": "start",
+ }
+ ],
+ }
+ },
+ "notifications": {
+ "foo": {
+ "status": "OPTED_IN",
+ "channel_preferences": [{"channel": "direct_message"}],
+ "rules": [
+ {
+ "until": "until",
+ "start": "start",
+ }
+ ],
+ }
+ },
+ },
+ }
+ ],
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
+ )
+ assert subscription is None
+
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_raw_response_add(self, client: Courier) -> None:
@@ -367,6 +408,47 @@ async def test_method_add(self, async_client: AsyncCourier) -> None:
)
assert subscription is None
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_add_with_all_params(self, async_client: AsyncCourier) -> None:
+ subscription = await async_client.lists.subscriptions.add(
+ list_id="list_id",
+ recipients=[
+ {
+ "recipient_id": "recipientId",
+ "preferences": {
+ "categories": {
+ "foo": {
+ "status": "OPTED_IN",
+ "channel_preferences": [{"channel": "direct_message"}],
+ "rules": [
+ {
+ "until": "until",
+ "start": "start",
+ }
+ ],
+ }
+ },
+ "notifications": {
+ "foo": {
+ "status": "OPTED_IN",
+ "channel_preferences": [{"channel": "direct_message"}],
+ "rules": [
+ {
+ "until": "until",
+ "start": "start",
+ }
+ ],
+ }
+ },
+ },
+ }
+ ],
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
+ )
+ assert subscription is None
+
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_raw_response_add(self, async_client: AsyncCourier) -> None:
diff --git a/tests/api_resources/profiles/test_lists.py b/tests/api_resources/profiles/test_lists.py
index 83aeb961..d7458b67 100644
--- a/tests/api_resources/profiles/test_lists.py
+++ b/tests/api_resources/profiles/test_lists.py
@@ -123,6 +123,47 @@ def test_method_subscribe(self, client: Courier) -> None:
)
assert_matches_type(ListSubscribeResponse, list_, path=["response"])
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_subscribe_with_all_params(self, client: Courier) -> None:
+ list_ = client.profiles.lists.subscribe(
+ user_id="user_id",
+ lists=[
+ {
+ "list_id": "listId",
+ "preferences": {
+ "categories": {
+ "foo": {
+ "status": "OPTED_IN",
+ "channel_preferences": [{"channel": "direct_message"}],
+ "rules": [
+ {
+ "until": "until",
+ "start": "start",
+ }
+ ],
+ }
+ },
+ "notifications": {
+ "foo": {
+ "status": "OPTED_IN",
+ "channel_preferences": [{"channel": "direct_message"}],
+ "rules": [
+ {
+ "until": "until",
+ "start": "start",
+ }
+ ],
+ }
+ },
+ },
+ }
+ ],
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
+ )
+ assert_matches_type(ListSubscribeResponse, list_, path=["response"])
+
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_raw_response_subscribe(self, client: Courier) -> None:
@@ -268,6 +309,47 @@ async def test_method_subscribe(self, async_client: AsyncCourier) -> None:
)
assert_matches_type(ListSubscribeResponse, list_, path=["response"])
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_subscribe_with_all_params(self, async_client: AsyncCourier) -> None:
+ list_ = await async_client.profiles.lists.subscribe(
+ user_id="user_id",
+ lists=[
+ {
+ "list_id": "listId",
+ "preferences": {
+ "categories": {
+ "foo": {
+ "status": "OPTED_IN",
+ "channel_preferences": [{"channel": "direct_message"}],
+ "rules": [
+ {
+ "until": "until",
+ "start": "start",
+ }
+ ],
+ }
+ },
+ "notifications": {
+ "foo": {
+ "status": "OPTED_IN",
+ "channel_preferences": [{"channel": "direct_message"}],
+ "rules": [
+ {
+ "until": "until",
+ "start": "start",
+ }
+ ],
+ }
+ },
+ },
+ }
+ ],
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
+ )
+ assert_matches_type(ListSubscribeResponse, list_, path=["response"])
+
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_raw_response_subscribe(self, async_client: AsyncCourier) -> None:
diff --git a/tests/api_resources/test_brands.py b/tests/api_resources/test_brands.py
index 118bfaf0..dd548d5c 100644
--- a/tests/api_resources/test_brands.py
+++ b/tests/api_resources/test_brands.py
@@ -105,6 +105,8 @@ def test_method_create_with_all_params(self, client: Courier) -> None:
}
]
},
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(Brand, brand, path=["response"])
@@ -472,6 +474,8 @@ async def test_method_create_with_all_params(self, async_client: AsyncCourier) -
}
]
},
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(Brand, brand, path=["response"])
diff --git a/tests/api_resources/test_journeys.py b/tests/api_resources/test_journeys.py
index 22962b21..3b1c49d4 100644
--- a/tests/api_resources/test_journeys.py
+++ b/tests/api_resources/test_journeys.py
@@ -64,6 +64,8 @@ def test_method_create_with_all_params(self, client: Courier) -> None:
],
enabled=True,
state="DRAFT",
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(JourneyResponse, journey, path=["response"])
@@ -251,6 +253,16 @@ def test_method_cancel_overload_1(self, client: Courier) -> None:
)
assert_matches_type(CancelJourneyResponse, journey, path=["response"])
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_cancel_with_all_params_overload_1(self, client: Courier) -> None:
+ journey = client.journeys.cancel(
+ cancelation_token="x",
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
+ )
+ assert_matches_type(CancelJourneyResponse, journey, path=["response"])
+
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_raw_response_cancel_overload_1(self, client: Courier) -> None:
@@ -285,6 +297,16 @@ def test_method_cancel_overload_2(self, client: Courier) -> None:
)
assert_matches_type(CancelJourneyResponse, journey, path=["response"])
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_cancel_with_all_params_overload_2(self, client: Courier) -> None:
+ journey = client.journeys.cancel(
+ run_id="x",
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
+ )
+ assert_matches_type(CancelJourneyResponse, journey, path=["response"])
+
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_raw_response_cancel_overload_2(self, client: Courier) -> None:
@@ -330,6 +352,8 @@ def test_method_invoke_with_all_params(self, client: Courier) -> None:
},
profile={"foo": "bar"},
user_id="user-123",
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(JourneysInvokeResponse, journey, path=["response"])
@@ -423,6 +447,8 @@ def test_method_publish_with_all_params(self, client: Courier) -> None:
journey = client.journeys.publish(
template_id="x",
version="v321669910225",
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(JourneyResponse, journey, path=["response"])
@@ -597,6 +623,8 @@ async def test_method_create_with_all_params(self, async_client: AsyncCourier) -
],
enabled=True,
state="DRAFT",
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(JourneyResponse, journey, path=["response"])
@@ -784,6 +812,16 @@ async def test_method_cancel_overload_1(self, async_client: AsyncCourier) -> Non
)
assert_matches_type(CancelJourneyResponse, journey, path=["response"])
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_cancel_with_all_params_overload_1(self, async_client: AsyncCourier) -> None:
+ journey = await async_client.journeys.cancel(
+ cancelation_token="x",
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
+ )
+ assert_matches_type(CancelJourneyResponse, journey, path=["response"])
+
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_raw_response_cancel_overload_1(self, async_client: AsyncCourier) -> None:
@@ -818,6 +856,16 @@ async def test_method_cancel_overload_2(self, async_client: AsyncCourier) -> Non
)
assert_matches_type(CancelJourneyResponse, journey, path=["response"])
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_cancel_with_all_params_overload_2(self, async_client: AsyncCourier) -> None:
+ journey = await async_client.journeys.cancel(
+ run_id="x",
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
+ )
+ assert_matches_type(CancelJourneyResponse, journey, path=["response"])
+
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_raw_response_cancel_overload_2(self, async_client: AsyncCourier) -> None:
@@ -863,6 +911,8 @@ async def test_method_invoke_with_all_params(self, async_client: AsyncCourier) -
},
profile={"foo": "bar"},
user_id="user-123",
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(JourneysInvokeResponse, journey, path=["response"])
@@ -956,6 +1006,8 @@ async def test_method_publish_with_all_params(self, async_client: AsyncCourier)
journey = await async_client.journeys.publish(
template_id="x",
version="v321669910225",
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(JourneyResponse, journey, path=["response"])
diff --git a/tests/api_resources/test_notifications.py b/tests/api_resources/test_notifications.py
index e4c9a1f9..db8f81e6 100644
--- a/tests/api_resources/test_notifications.py
+++ b/tests/api_resources/test_notifications.py
@@ -57,6 +57,8 @@ def test_method_create_with_all_params(self, client: Courier) -> None:
"tags": ["onboarding", "welcome"],
},
state="DRAFT",
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(NotificationTemplateResponse, notification, path=["response"])
@@ -345,6 +347,8 @@ def test_method_publish_with_all_params(self, client: Courier) -> None:
notification = client.notifications.publish(
id="id",
version="v321669910225",
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert notification is None
@@ -779,6 +783,8 @@ async def test_method_create_with_all_params(self, async_client: AsyncCourier) -
"tags": ["onboarding", "welcome"],
},
state="DRAFT",
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(NotificationTemplateResponse, notification, path=["response"])
@@ -1067,6 +1073,8 @@ async def test_method_publish_with_all_params(self, async_client: AsyncCourier)
notification = await async_client.notifications.publish(
id="id",
version="v321669910225",
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert notification is None
diff --git a/tests/api_resources/test_profiles.py b/tests/api_resources/test_profiles.py
index a3859583..b90cb5f0 100644
--- a/tests/api_resources/test_profiles.py
+++ b/tests/api_resources/test_profiles.py
@@ -30,6 +30,17 @@ def test_method_create(self, client: Courier) -> None:
)
assert_matches_type(ProfileCreateResponse, profile, path=["response"])
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_create_with_all_params(self, client: Courier) -> None:
+ profile = client.profiles.create(
+ user_id="user_id",
+ profile={"foo": "bar"},
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
+ )
+ assert_matches_type(ProfileCreateResponse, profile, path=["response"])
+
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
def test_raw_response_create(self, client: Courier) -> None:
@@ -282,6 +293,17 @@ async def test_method_create(self, async_client: AsyncCourier) -> None:
)
assert_matches_type(ProfileCreateResponse, profile, path=["response"])
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncCourier) -> None:
+ profile = await async_client.profiles.create(
+ user_id="user_id",
+ profile={"foo": "bar"},
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
+ )
+ assert_matches_type(ProfileCreateResponse, profile, path=["response"])
+
@pytest.mark.skip(reason="Mock server tests are disabled")
@parametrize
async def test_raw_response_create(self, async_client: AsyncCourier) -> None:
diff --git a/tests/api_resources/test_providers.py b/tests/api_resources/test_providers.py
index 5915dced..10e8d743 100644
--- a/tests/api_resources/test_providers.py
+++ b/tests/api_resources/test_providers.py
@@ -36,6 +36,8 @@ def test_method_create_with_all_params(self, client: Courier) -> None:
alias="alias",
settings={"foo": "bar"},
title="title",
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(Provider, provider, path=["response"])
@@ -265,6 +267,8 @@ async def test_method_create_with_all_params(self, async_client: AsyncCourier) -
alias="alias",
settings={"foo": "bar"},
title="title",
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(Provider, provider, path=["response"])
diff --git a/tests/api_resources/test_routing_strategies.py b/tests/api_resources/test_routing_strategies.py
index 8d314b65..2a19e86e 100644
--- a/tests/api_resources/test_routing_strategies.py
+++ b/tests/api_resources/test_routing_strategies.py
@@ -82,6 +82,8 @@ def test_method_create_with_all_params(self, client: Courier) -> None:
}
},
tags=["production", "email"],
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(RoutingStrategyGetResponse, routing_strategy, path=["response"])
@@ -474,6 +476,8 @@ async def test_method_create_with_all_params(self, async_client: AsyncCourier) -
}
},
tags=["production", "email"],
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(RoutingStrategyGetResponse, routing_strategy, path=["response"])
diff --git a/tests/api_resources/test_send.py b/tests/api_resources/test_send.py
index 81bb3fad..7f92c54c 100644
--- a/tests/api_resources/test_send.py
+++ b/tests/api_resources/test_send.py
@@ -150,6 +150,8 @@ def test_method_message_with_all_params(self, client: Courier) -> None:
"user_id": "user_id",
},
},
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(SendMessageResponse, send, path=["response"])
@@ -318,6 +320,8 @@ async def test_method_message_with_all_params(self, async_client: AsyncCourier)
"user_id": "user_id",
},
},
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(SendMessageResponse, send, path=["response"])
diff --git a/tests/api_resources/test_workspace_preferences.py b/tests/api_resources/test_workspace_preferences.py
index 791e1667..b3802b5e 100644
--- a/tests/api_resources/test_workspace_preferences.py
+++ b/tests/api_resources/test_workspace_preferences.py
@@ -37,6 +37,8 @@ def test_method_create_with_all_params(self, client: Courier) -> None:
description="description",
has_custom_routing=True,
routing_options=["direct_message"],
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(WorkspacePreferenceGetResponse, workspace_preference, path=["response"])
@@ -191,6 +193,8 @@ def test_method_publish_with_all_params(self, client: Courier) -> None:
brand_id="brand_id",
description="description",
heading="heading",
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(PublishPreferencesResponse, workspace_preference, path=["response"])
@@ -296,6 +300,8 @@ async def test_method_create_with_all_params(self, async_client: AsyncCourier) -
description="description",
has_custom_routing=True,
routing_options=["direct_message"],
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(WorkspacePreferenceGetResponse, workspace_preference, path=["response"])
@@ -450,6 +456,8 @@ async def test_method_publish_with_all_params(self, async_client: AsyncCourier)
brand_id="brand_id",
description="description",
heading="heading",
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(PublishPreferencesResponse, workspace_preference, path=["response"])
diff --git a/tests/api_resources/users/test_preferences.py b/tests/api_resources/users/test_preferences.py
index 8d96730e..f791c4f5 100644
--- a/tests/api_resources/users/test_preferences.py
+++ b/tests/api_resources/users/test_preferences.py
@@ -195,6 +195,8 @@ def test_method_bulk_update_with_all_params(self, client: Courier) -> None:
},
],
tenant_id="tenant_id",
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(PreferenceBulkUpdateResponse, preference, path=["response"])
@@ -636,6 +638,8 @@ async def test_method_bulk_update_with_all_params(self, async_client: AsyncCouri
},
],
tenant_id="tenant_id",
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(PreferenceBulkUpdateResponse, preference, path=["response"])
diff --git a/tests/api_resources/workspace_preferences/test_topics.py b/tests/api_resources/workspace_preferences/test_topics.py
index 5647167b..0d6e92de 100644
--- a/tests/api_resources/workspace_preferences/test_topics.py
+++ b/tests/api_resources/workspace_preferences/test_topics.py
@@ -39,6 +39,8 @@ def test_method_create_with_all_params(self, client: Courier) -> None:
include_unsubscribe_header=True,
routing_options=["direct_message"],
topic_data={"foo": "bar"},
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(WorkspacePreferenceTopicGetResponse, topic, path=["response"])
@@ -334,6 +336,8 @@ async def test_method_create_with_all_params(self, async_client: AsyncCourier) -
include_unsubscribe_header=True,
routing_options=["direct_message"],
topic_data={"foo": "bar"},
+ idempotency_key="order-ORD-456-user-123",
+ x_idempotency_expiration="1785312000",
)
assert_matches_type(WorkspacePreferenceTopicGetResponse, topic, path=["response"])