diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 2ecf001..e20563e 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "7.13.0"
+ ".": "7.14.0"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index 1692bab..6ee7bef 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 117
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/courier/courier-545ac9b590445e90e11113a1bdeb893a94389d69d95e0a7e5c6450bb15f5453a.yml
-openapi_spec_hash: 9e243ec62800fb4a2e443eb6481afa30
-config_hash: 10bd597dd6cc89023541bc551b6532b8
+configured_endpoints: 119
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/courier/courier-a64bb97c3455b0689de7f6a297ba1dc1e747561ce310ddb18b9c4a5f4d3d510a.yml
+openapi_spec_hash: 6a3b89f3ea7600e784902f61680f8f1a
+config_hash: 822a92efc80e63cdb2d496dbd6176620
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6b657bc..3387b19 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
# Changelog
+## 7.14.0 (2026-06-12)
+
+Full Changelog: [v7.13.0...v7.14.0](https://github.com/trycourier/courier-python/compare/v7.13.0...v7.14.0)
+
+### Features
+
+* **digests:** document digest REST endpoints in OpenAPI spec ([d88d2ae](https://github.com/trycourier/courier-python/commit/d88d2ae2d08af0abb7334e5ba1228619a85e9e3b))
+
## 7.13.0 (2026-05-28)
Full Changelog: [v7.12.0...v7.13.0](https://github.com/trycourier/courier-python/compare/v7.12.0...v7.13.0)
diff --git a/api.md b/api.md
index d564293..cc16218 100644
--- a/api.md
+++ b/api.md
@@ -310,6 +310,21 @@ Methods:
- client.bulk.retrieve_job(job_id) -> BulkRetrieveJobResponse
- client.bulk.run_job(job_id) -> None
+# Digests
+
+Types:
+
+```python
+from courier.types import DigestCategory, DigestInstance, DigestInstanceListResponse
+```
+
+## Schedules
+
+Methods:
+
+- client.digests.schedules.list_instances(schedule_id, \*\*params) -> DigestInstanceListResponse
+- client.digests.schedules.release(schedule_id) -> None
+
# Inbound
Types:
diff --git a/pyproject.toml b/pyproject.toml
index a828b21..10d2fb0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "trycourier"
-version = "7.13.0"
+version = "7.14.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 30317ee..64fa136 100644
--- a/src/courier/_client.py
+++ b/src/courier/_client.py
@@ -42,6 +42,7 @@
lists,
users,
brands,
+ digests,
inbound,
tenants,
journeys,
@@ -68,6 +69,7 @@
from .resources.users.users import UsersResource, AsyncUsersResource
from .resources.audit_events import AuditEventsResource, AsyncAuditEventsResource
from .resources.translations import TranslationsResource, AsyncTranslationsResource
+ from .resources.digests.digests import DigestsResource, AsyncDigestsResource
from .resources.tenants.tenants import TenantsResource, AsyncTenantsResource
from .resources.journeys.journeys import JourneysResource, AsyncJourneysResource
from .resources.profiles.profiles import ProfilesResource, AsyncProfilesResource
@@ -197,6 +199,12 @@ def bulk(self) -> BulkResource:
return BulkResource(self)
+ @cached_property
+ def digests(self) -> DigestsResource:
+ from .resources.digests import DigestsResource
+
+ return DigestsResource(self)
+
@cached_property
def inbound(self) -> InboundResource:
from .resources.inbound import InboundResource
@@ -488,6 +496,12 @@ def bulk(self) -> AsyncBulkResource:
return AsyncBulkResource(self)
+ @cached_property
+ def digests(self) -> AsyncDigestsResource:
+ from .resources.digests import AsyncDigestsResource
+
+ return AsyncDigestsResource(self)
+
@cached_property
def inbound(self) -> AsyncInboundResource:
from .resources.inbound import AsyncInboundResource
@@ -721,6 +735,12 @@ def bulk(self) -> bulk.BulkResourceWithRawResponse:
return BulkResourceWithRawResponse(self._client.bulk)
+ @cached_property
+ def digests(self) -> digests.DigestsResourceWithRawResponse:
+ from .resources.digests import DigestsResourceWithRawResponse
+
+ return DigestsResourceWithRawResponse(self._client.digests)
+
@cached_property
def inbound(self) -> inbound.InboundResourceWithRawResponse:
from .resources.inbound import InboundResourceWithRawResponse
@@ -842,6 +862,12 @@ def bulk(self) -> bulk.AsyncBulkResourceWithRawResponse:
return AsyncBulkResourceWithRawResponse(self._client.bulk)
+ @cached_property
+ def digests(self) -> digests.AsyncDigestsResourceWithRawResponse:
+ from .resources.digests import AsyncDigestsResourceWithRawResponse
+
+ return AsyncDigestsResourceWithRawResponse(self._client.digests)
+
@cached_property
def inbound(self) -> inbound.AsyncInboundResourceWithRawResponse:
from .resources.inbound import AsyncInboundResourceWithRawResponse
@@ -963,6 +989,12 @@ def bulk(self) -> bulk.BulkResourceWithStreamingResponse:
return BulkResourceWithStreamingResponse(self._client.bulk)
+ @cached_property
+ def digests(self) -> digests.DigestsResourceWithStreamingResponse:
+ from .resources.digests import DigestsResourceWithStreamingResponse
+
+ return DigestsResourceWithStreamingResponse(self._client.digests)
+
@cached_property
def inbound(self) -> inbound.InboundResourceWithStreamingResponse:
from .resources.inbound import InboundResourceWithStreamingResponse
@@ -1084,6 +1116,12 @@ def bulk(self) -> bulk.AsyncBulkResourceWithStreamingResponse:
return AsyncBulkResourceWithStreamingResponse(self._client.bulk)
+ @cached_property
+ def digests(self) -> digests.AsyncDigestsResourceWithStreamingResponse:
+ from .resources.digests import AsyncDigestsResourceWithStreamingResponse
+
+ return AsyncDigestsResourceWithStreamingResponse(self._client.digests)
+
@cached_property
def inbound(self) -> inbound.AsyncInboundResourceWithStreamingResponse:
from .resources.inbound import AsyncInboundResourceWithStreamingResponse
diff --git a/src/courier/_version.py b/src/courier/_version.py
index 79fa551..070136e 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.13.0" # x-release-please-version
+__version__ = "7.14.0" # x-release-please-version
diff --git a/src/courier/resources/__init__.py b/src/courier/resources/__init__.py
index 99d3168..3933d0a 100644
--- a/src/courier/resources/__init__.py
+++ b/src/courier/resources/__init__.py
@@ -48,6 +48,14 @@
BrandsResourceWithStreamingResponse,
AsyncBrandsResourceWithStreamingResponse,
)
+from .digests import (
+ DigestsResource,
+ AsyncDigestsResource,
+ DigestsResourceWithRawResponse,
+ AsyncDigestsResourceWithRawResponse,
+ DigestsResourceWithStreamingResponse,
+ AsyncDigestsResourceWithStreamingResponse,
+)
from .inbound import (
InboundResource,
AsyncInboundResource,
@@ -208,6 +216,12 @@
"AsyncBulkResourceWithRawResponse",
"BulkResourceWithStreamingResponse",
"AsyncBulkResourceWithStreamingResponse",
+ "DigestsResource",
+ "AsyncDigestsResource",
+ "DigestsResourceWithRawResponse",
+ "AsyncDigestsResourceWithRawResponse",
+ "DigestsResourceWithStreamingResponse",
+ "AsyncDigestsResourceWithStreamingResponse",
"InboundResource",
"AsyncInboundResource",
"InboundResourceWithRawResponse",
diff --git a/src/courier/resources/digests/__init__.py b/src/courier/resources/digests/__init__.py
new file mode 100644
index 0000000..d1ec65b
--- /dev/null
+++ b/src/courier/resources/digests/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .digests import (
+ DigestsResource,
+ AsyncDigestsResource,
+ DigestsResourceWithRawResponse,
+ AsyncDigestsResourceWithRawResponse,
+ DigestsResourceWithStreamingResponse,
+ AsyncDigestsResourceWithStreamingResponse,
+)
+from .schedules import (
+ SchedulesResource,
+ AsyncSchedulesResource,
+ SchedulesResourceWithRawResponse,
+ AsyncSchedulesResourceWithRawResponse,
+ SchedulesResourceWithStreamingResponse,
+ AsyncSchedulesResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "SchedulesResource",
+ "AsyncSchedulesResource",
+ "SchedulesResourceWithRawResponse",
+ "AsyncSchedulesResourceWithRawResponse",
+ "SchedulesResourceWithStreamingResponse",
+ "AsyncSchedulesResourceWithStreamingResponse",
+ "DigestsResource",
+ "AsyncDigestsResource",
+ "DigestsResourceWithRawResponse",
+ "AsyncDigestsResourceWithRawResponse",
+ "DigestsResourceWithStreamingResponse",
+ "AsyncDigestsResourceWithStreamingResponse",
+]
diff --git a/src/courier/resources/digests/digests.py b/src/courier/resources/digests/digests.py
new file mode 100644
index 0000000..b50742c
--- /dev/null
+++ b/src/courier/resources/digests/digests.py
@@ -0,0 +1,102 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from ..._compat import cached_property
+from .schedules import (
+ SchedulesResource,
+ AsyncSchedulesResource,
+ SchedulesResourceWithRawResponse,
+ AsyncSchedulesResourceWithRawResponse,
+ SchedulesResourceWithStreamingResponse,
+ AsyncSchedulesResourceWithStreamingResponse,
+)
+from ..._resource import SyncAPIResource, AsyncAPIResource
+
+__all__ = ["DigestsResource", "AsyncDigestsResource"]
+
+
+class DigestsResource(SyncAPIResource):
+ @cached_property
+ def schedules(self) -> SchedulesResource:
+ return SchedulesResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> DigestsResourceWithRawResponse:
+ """
+ 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 DigestsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> DigestsResourceWithStreamingResponse:
+ """
+ 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 DigestsResourceWithStreamingResponse(self)
+
+
+class AsyncDigestsResource(AsyncAPIResource):
+ @cached_property
+ def schedules(self) -> AsyncSchedulesResource:
+ return AsyncSchedulesResource(self._client)
+
+ @cached_property
+ def with_raw_response(self) -> AsyncDigestsResourceWithRawResponse:
+ """
+ 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 AsyncDigestsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncDigestsResourceWithStreamingResponse:
+ """
+ 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 AsyncDigestsResourceWithStreamingResponse(self)
+
+
+class DigestsResourceWithRawResponse:
+ def __init__(self, digests: DigestsResource) -> None:
+ self._digests = digests
+
+ @cached_property
+ def schedules(self) -> SchedulesResourceWithRawResponse:
+ return SchedulesResourceWithRawResponse(self._digests.schedules)
+
+
+class AsyncDigestsResourceWithRawResponse:
+ def __init__(self, digests: AsyncDigestsResource) -> None:
+ self._digests = digests
+
+ @cached_property
+ def schedules(self) -> AsyncSchedulesResourceWithRawResponse:
+ return AsyncSchedulesResourceWithRawResponse(self._digests.schedules)
+
+
+class DigestsResourceWithStreamingResponse:
+ def __init__(self, digests: DigestsResource) -> None:
+ self._digests = digests
+
+ @cached_property
+ def schedules(self) -> SchedulesResourceWithStreamingResponse:
+ return SchedulesResourceWithStreamingResponse(self._digests.schedules)
+
+
+class AsyncDigestsResourceWithStreamingResponse:
+ def __init__(self, digests: AsyncDigestsResource) -> None:
+ self._digests = digests
+
+ @cached_property
+ def schedules(self) -> AsyncSchedulesResourceWithStreamingResponse:
+ return AsyncSchedulesResourceWithStreamingResponse(self._digests.schedules)
diff --git a/src/courier/resources/digests/schedules.py b/src/courier/resources/digests/schedules.py
new file mode 100644
index 0000000..d92f765
--- /dev/null
+++ b/src/courier/resources/digests/schedules.py
@@ -0,0 +1,287 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
+from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.digests import schedule_list_instances_params
+from ...types.digest_instance_list_response import DigestInstanceListResponse
+
+__all__ = ["SchedulesResource", "AsyncSchedulesResource"]
+
+
+class SchedulesResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> SchedulesResourceWithRawResponse:
+ """
+ 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 SchedulesResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> SchedulesResourceWithStreamingResponse:
+ """
+ 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 SchedulesResourceWithStreamingResponse(self)
+
+ def list_instances(
+ self,
+ schedule_id: str,
+ *,
+ cursor: str | Omit = omit,
+ limit: int | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> 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.
+
+ Args:
+ cursor: A cursor token from a previous response, used to fetch the next page of results.
+
+ limit: The maximum number of digest instances to return. Defaults to 20, with a maximum
+ of 100.
+
+ 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 schedule_id:
+ raise ValueError(f"Expected a non-empty value for `schedule_id` but received {schedule_id!r}")
+ return self._get(
+ path_template("/digests/schedules/{schedule_id}/instances", schedule_id=schedule_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "cursor": cursor,
+ "limit": limit,
+ },
+ schedule_list_instances_params.ScheduleListInstancesParams,
+ ),
+ ),
+ cast_to=DigestInstanceListResponse,
+ )
+
+ def release(
+ self,
+ schedule_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Send a digest now instead of waiting for its scheduled time, so your users get
+ what they have collected so far right away.
+
+ 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 schedule_id:
+ raise ValueError(f"Expected a non-empty value for `schedule_id` but received {schedule_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._post(
+ path_template("/digests/schedules/{schedule_id}/trigger", schedule_id=schedule_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+
+class AsyncSchedulesResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncSchedulesResourceWithRawResponse:
+ """
+ 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 AsyncSchedulesResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncSchedulesResourceWithStreamingResponse:
+ """
+ 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 AsyncSchedulesResourceWithStreamingResponse(self)
+
+ async def list_instances(
+ self,
+ schedule_id: str,
+ *,
+ cursor: str | Omit = omit,
+ limit: int | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> 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.
+
+ Args:
+ cursor: A cursor token from a previous response, used to fetch the next page of results.
+
+ limit: The maximum number of digest instances to return. Defaults to 20, with a maximum
+ of 100.
+
+ 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 schedule_id:
+ raise ValueError(f"Expected a non-empty value for `schedule_id` but received {schedule_id!r}")
+ return await self._get(
+ path_template("/digests/schedules/{schedule_id}/instances", schedule_id=schedule_id),
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "cursor": cursor,
+ "limit": limit,
+ },
+ schedule_list_instances_params.ScheduleListInstancesParams,
+ ),
+ ),
+ cast_to=DigestInstanceListResponse,
+ )
+
+ async def release(
+ self,
+ schedule_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Send a digest now instead of waiting for its scheduled time, so your users get
+ what they have collected so far right away.
+
+ 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 schedule_id:
+ raise ValueError(f"Expected a non-empty value for `schedule_id` but received {schedule_id!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._post(
+ path_template("/digests/schedules/{schedule_id}/trigger", schedule_id=schedule_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+
+class SchedulesResourceWithRawResponse:
+ def __init__(self, schedules: SchedulesResource) -> None:
+ self._schedules = schedules
+
+ self.list_instances = to_raw_response_wrapper(
+ schedules.list_instances,
+ )
+ self.release = to_raw_response_wrapper(
+ schedules.release,
+ )
+
+
+class AsyncSchedulesResourceWithRawResponse:
+ def __init__(self, schedules: AsyncSchedulesResource) -> None:
+ self._schedules = schedules
+
+ self.list_instances = async_to_raw_response_wrapper(
+ schedules.list_instances,
+ )
+ self.release = async_to_raw_response_wrapper(
+ schedules.release,
+ )
+
+
+class SchedulesResourceWithStreamingResponse:
+ def __init__(self, schedules: SchedulesResource) -> None:
+ self._schedules = schedules
+
+ self.list_instances = to_streamed_response_wrapper(
+ schedules.list_instances,
+ )
+ self.release = to_streamed_response_wrapper(
+ schedules.release,
+ )
+
+
+class AsyncSchedulesResourceWithStreamingResponse:
+ def __init__(self, schedules: AsyncSchedulesResource) -> None:
+ self._schedules = schedules
+
+ self.list_instances = async_to_streamed_response_wrapper(
+ schedules.list_instances,
+ )
+ self.release = async_to_streamed_response_wrapper(
+ schedules.release,
+ )
diff --git a/src/courier/types/__init__.py b/src/courier/types/__init__.py
index 89b0b95..22ef85d 100644
--- a/src/courier/types/__init__.py
+++ b/src/courier/types/__init__.py
@@ -117,6 +117,8 @@
from .brand_settings import BrandSettings as BrandSettings
from .brand_snippets import BrandSnippets as BrandSnippets
from .brand_template import BrandTemplate as BrandTemplate
+from .digest_category import DigestCategory as DigestCategory
+from .digest_instance import DigestInstance as DigestInstance
from .journey_ai_node import JourneyAINode as JourneyAINode
from .message_details import MessageDetails as MessageDetails
from .base_check_param import BaseCheckParam as BaseCheckParam
@@ -236,6 +238,7 @@
from .put_tenant_template_response import PutTenantTemplateResponse as PutTenantTemplateResponse
from .routing_strategy_list_params import RoutingStrategyListParams as RoutingStrategyListParams
from .subscription_topic_new_param import SubscriptionTopicNewParam as SubscriptionTopicNewParam
+from .digest_instance_list_response import DigestInstanceListResponse as DigestInstanceListResponse
from .journey_condition_group_param import JourneyConditionGroupParam as JourneyConditionGroupParam
from .journey_fetch_get_delete_node import JourneyFetchGetDeleteNode as JourneyFetchGetDeleteNode
from .journey_template_get_response import JourneyTemplateGetResponse as JourneyTemplateGetResponse
diff --git a/src/courier/types/digest_category.py b/src/courier/types/digest_category.py
new file mode 100644
index 0000000..700bbfa
--- /dev/null
+++ b/src/courier/types/digest_category.py
@@ -0,0 +1,22 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from .._models import BaseModel
+
+__all__ = ["DigestCategory"]
+
+
+class DigestCategory(BaseModel):
+ category_key: str
+ """The key that identifies the category within the digest."""
+
+ retain: Literal["first", "last", "highest", "lowest", "none"]
+ """
+ Which events to keep when the number of events exceeds the retention limit for
+ the category.
+ """
+
+ sort_key: Optional[str] = None
+ """The data key used to order events when `retain` is `highest` or `lowest`."""
diff --git a/src/courier/types/digest_instance.py b/src/courier/types/digest_instance.py
new file mode 100644
index 0000000..e279712
--- /dev/null
+++ b/src/courier/types/digest_instance.py
@@ -0,0 +1,42 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, List, Optional
+from typing_extensions import Literal
+
+from .._models import BaseModel
+from .digest_category import DigestCategory
+
+__all__ = ["DigestInstance"]
+
+
+class DigestInstance(BaseModel):
+ digest_instance_id: str
+ """A unique identifier for the digest instance."""
+
+ event_count: int
+ """The total number of events received for this instance."""
+
+ status: Literal["IN_PROGRESS", "COMPLETED"]
+ """The status of the digest instance.
+
+ `IN_PROGRESS` instances are still accumulating events; `COMPLETED` instances
+ have been released.
+ """
+
+ user_id: str
+ """The ID of the user this digest instance belongs to."""
+
+ categories: Optional[List[DigestCategory]] = None
+ """The categories configured for the digest."""
+
+ category_key_counts: Optional[Dict[str, int]] = None
+ """A map of category key to the number of events received for that category."""
+
+ created_at: Optional[str] = None
+ """An ISO 8601 timestamp of when the digest instance was created."""
+
+ disabled: Optional[bool] = None
+ """Whether the digest instance has been disabled."""
+
+ tenant_id: Optional[str] = None
+ """The ID of the tenant this digest instance belongs to, if any."""
diff --git a/src/courier/types/digest_instance_list_response.py b/src/courier/types/digest_instance_list_response.py
new file mode 100644
index 0000000..cc1b11e
--- /dev/null
+++ b/src/courier/types/digest_instance_list_response.py
@@ -0,0 +1,32 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Optional
+from typing_extensions import Literal
+
+from .._models import BaseModel
+from .digest_instance import DigestInstance
+
+__all__ = ["DigestInstanceListResponse"]
+
+
+class DigestInstanceListResponse(BaseModel):
+ has_more: bool
+ """Whether there are more digest instances to fetch using the cursor."""
+
+ items: List[DigestInstance]
+ """The digest instances for this page of results."""
+
+ type: Literal["list"]
+ """Always `list` for a paginated list response."""
+
+ cursor: Optional[str] = None
+ """
+ A cursor token for fetching the next page of results, or null when there are
+ none.
+ """
+
+ next_url: Optional[str] = None
+ """The path to fetch the next page of results, or null when there are none."""
+
+ url: Optional[str] = None
+ """The path of the current request."""
diff --git a/src/courier/types/digests/__init__.py b/src/courier/types/digests/__init__.py
new file mode 100644
index 0000000..5b1c71f
--- /dev/null
+++ b/src/courier/types/digests/__init__.py
@@ -0,0 +1,5 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .schedule_list_instances_params import ScheduleListInstancesParams as ScheduleListInstancesParams
diff --git a/src/courier/types/digests/schedule_list_instances_params.py b/src/courier/types/digests/schedule_list_instances_params.py
new file mode 100644
index 0000000..e1c820d
--- /dev/null
+++ b/src/courier/types/digests/schedule_list_instances_params.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import TypedDict
+
+__all__ = ["ScheduleListInstancesParams"]
+
+
+class ScheduleListInstancesParams(TypedDict, total=False):
+ cursor: str
+ """
+ A cursor token from a previous response, used to fetch the next page of results.
+ """
+
+ limit: int
+ """The maximum number of digest instances to return.
+
+ Defaults to 20, with a maximum of 100.
+ """
diff --git a/tests/api_resources/digests/__init__.py b/tests/api_resources/digests/__init__.py
new file mode 100644
index 0000000..fd8019a
--- /dev/null
+++ b/tests/api_resources/digests/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/digests/test_schedules.py b/tests/api_resources/digests/test_schedules.py
new file mode 100644
index 0000000..eac3666
--- /dev/null
+++ b/tests/api_resources/digests/test_schedules.py
@@ -0,0 +1,212 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from courier import Courier, AsyncCourier
+from tests.utils import assert_matches_type
+from courier.types import DigestInstanceListResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestSchedules:
+ 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_list_instances(self, client: Courier) -> None:
+ schedule = client.digests.schedules.list_instances(
+ schedule_id="schedule_id",
+ )
+ assert_matches_type(DigestInstanceListResponse, schedule, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_instances_with_all_params(self, client: Courier) -> None:
+ schedule = client.digests.schedules.list_instances(
+ schedule_id="schedule_id",
+ cursor="cursor",
+ limit=100,
+ )
+ assert_matches_type(DigestInstanceListResponse, schedule, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list_instances(self, client: Courier) -> None:
+ response = client.digests.schedules.with_raw_response.list_instances(
+ schedule_id="schedule_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ schedule = response.parse()
+ assert_matches_type(DigestInstanceListResponse, schedule, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list_instances(self, client: Courier) -> None:
+ with client.digests.schedules.with_streaming_response.list_instances(
+ schedule_id="schedule_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ schedule = response.parse()
+ assert_matches_type(DigestInstanceListResponse, schedule, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_list_instances(self, client: Courier) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `schedule_id` but received ''"):
+ client.digests.schedules.with_raw_response.list_instances(
+ schedule_id="",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_release(self, client: Courier) -> None:
+ schedule = client.digests.schedules.release(
+ "schedule_id",
+ )
+ assert schedule is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_release(self, client: Courier) -> None:
+ response = client.digests.schedules.with_raw_response.release(
+ "schedule_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ schedule = response.parse()
+ assert schedule is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_release(self, client: Courier) -> None:
+ with client.digests.schedules.with_streaming_response.release(
+ "schedule_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ schedule = response.parse()
+ assert schedule is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_release(self, client: Courier) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `schedule_id` but received ''"):
+ client.digests.schedules.with_raw_response.release(
+ "",
+ )
+
+
+class TestAsyncSchedules:
+ 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_list_instances(self, async_client: AsyncCourier) -> None:
+ schedule = await async_client.digests.schedules.list_instances(
+ schedule_id="schedule_id",
+ )
+ assert_matches_type(DigestInstanceListResponse, schedule, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list_instances_with_all_params(self, async_client: AsyncCourier) -> None:
+ schedule = await async_client.digests.schedules.list_instances(
+ schedule_id="schedule_id",
+ cursor="cursor",
+ limit=100,
+ )
+ assert_matches_type(DigestInstanceListResponse, schedule, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_list_instances(self, async_client: AsyncCourier) -> None:
+ response = await async_client.digests.schedules.with_raw_response.list_instances(
+ schedule_id="schedule_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ schedule = await response.parse()
+ assert_matches_type(DigestInstanceListResponse, schedule, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_list_instances(self, async_client: AsyncCourier) -> None:
+ async with async_client.digests.schedules.with_streaming_response.list_instances(
+ schedule_id="schedule_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ schedule = await response.parse()
+ assert_matches_type(DigestInstanceListResponse, schedule, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_list_instances(self, async_client: AsyncCourier) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `schedule_id` but received ''"):
+ await async_client.digests.schedules.with_raw_response.list_instances(
+ schedule_id="",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_release(self, async_client: AsyncCourier) -> None:
+ schedule = await async_client.digests.schedules.release(
+ "schedule_id",
+ )
+ assert schedule is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_release(self, async_client: AsyncCourier) -> None:
+ response = await async_client.digests.schedules.with_raw_response.release(
+ "schedule_id",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ schedule = await response.parse()
+ assert schedule is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_release(self, async_client: AsyncCourier) -> None:
+ async with async_client.digests.schedules.with_streaming_response.release(
+ "schedule_id",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ schedule = await response.parse()
+ assert schedule 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_release(self, async_client: AsyncCourier) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `schedule_id` but received ''"):
+ await async_client.digests.schedules.with_raw_response.release(
+ "",
+ )