diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8e9a429..e8bfe76 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 1.13.0
+
+Adds custom icons and interruption levels for Push Notifications. Set `icon` to show a service logo or avatar on the notification, and `interruption_level` to deliver it as `passive`, `active`, or `time-sensitive`.
+
## 1.12.0
Adds the new `value` Live Activity type.
diff --git a/README.md b/README.md
index 68c8bc8..b949f7d 100644
--- a/README.md
+++ b/README.md
@@ -44,6 +44,20 @@ activitysmith.notifications.send(
)
```
+### Custom Push Notification Icons
+
+
+
+Set `icon` to a publicly accessible HTTPS image URL to show an avatar or service logo alongside a smaller ActivitySmith app icon. With a custom icon, iOS may omit the subtitle from the notification.
+
+```python
+activitysmith.notifications.send(
+ title="MRR just crossed $10,000 🎉",
+ message="Up 18% this month from 31 new subscriptions.",
+ icon="https://cdn.activitysmith.com/integrations/icons/stripe.png",
+)
+```
+
### Rich Push Notifications with Media

@@ -134,23 +148,85 @@ activitysmith.notifications.send(
)
```
+### Push Notification Interruption Levels
+
+
+
+Use `interruption_level` to choose how urgently iOS presents a Push Notification. Omit it for normal delivery. Explicit `active` has the same behavior as the default.
+
+| Value | Behavior |
+| --- | --- |
+| `passive` | Appears quietly in the notification list, without sound or waking the screen. |
+| `active` | Normal delivery, subject to the device's notification settings and Focus. This is the default. |
+| `time-sensitive` | Can bypass Scheduled Summary and Focus when the user allows Time Sensitive Notifications for ActivitySmith. |
+
+See Apple's [interruption levels](https://developer.apple.com/documentation/usernotifications/unnotificationinterruptionlevel) for the system behavior.
+
+The SDK also exports these values as `PushInterruptionLevel.PASSIVE`, `PushInterruptionLevel.ACTIVE`, and `PushInterruptionLevel.TIME_SENSITIVE`.
+
+#### Passive
+
+```python
+activitysmith.notifications.send(
+ title="Daily summary",
+ message="All scheduled jobs completed successfully.",
+ interruption_level="passive",
+)
+```
+
+#### Active (default)
+
+```python
+activitysmith.notifications.send(
+ title="Deployment complete",
+ message="Your latest changes are live.",
+ interruption_level="active",
+)
+```
+
+Removing the interruption level from this example produces the same delivery behavior.
+
+#### Time Sensitive
+
+```python
+activitysmith.notifications.send(
+ title="🚨 checkout-api is down",
+ message="500 errors on 62% of requests. Customers can't complete payments.",
+ interruption_level="time-sensitive",
+)
+```
+
## Live Activities
Choose the Live Activity type that matches what you want to show:
--  **Value**: Show a single value on your Lock Screen, such as revenue, a queue count, or a temperature.
+
+
+**Value**: Show a single value on your Lock Screen, such as revenue, a queue count, or a temperature.
+
+
+
+**Stats**: Show up to 8 labeled values on your Lock Screen, from revenue and orders to uptime and conversion.
+
+
+
+**Alert**: Show status updates with a clear message, badge, and icon. When you add an action button, `color` controls the button tint.
+
+
+
+**Metrics**: Track two related values with segmented bars, such as CPU and memory.
--  **Stats**: Show up to 8 labeled values on your Lock Screen, from revenue and orders to uptime and conversion.
+
--  **Alert**: Show status updates with a clear message, badge, and icon. When you add an action button, `color` controls the button tint.
+**Segmented Progress**: Show progress through a known set of steps, like build, test, deploy, and verify.
--  **Metrics**: Track two related values with segmented bars, such as CPU and memory.
+
--  **Segmented Progress**: Show progress through a known set of steps, like build, test, deploy, and verify.
+**Progress**: Show percentage progress for jobs that move continuously toward completion.
--  **Progress**: Show percentage progress for jobs that move continuously toward completion.
+
--  **Timer**: Count down from a duration, or count up from 00:00 while a job runs.
+**Timer**: Count down from a duration, or count up from 00:00 while a job runs.
### Start & Update Live Activity
diff --git a/activitysmith/__init__.py b/activitysmith/__init__.py
index 4672fc5..bae1404 100644
--- a/activitysmith/__init__.py
+++ b/activitysmith/__init__.py
@@ -1,6 +1,7 @@
from .client import (
ActivitySmith,
LiveActivityColor,
+ PushInterruptionLevel,
action,
alert_badge,
alert_icon,
@@ -11,6 +12,7 @@
__all__ = [
"ActivitySmith",
"LiveActivityColor",
+ "PushInterruptionLevel",
"action",
"alert_badge",
"alert_icon",
diff --git a/activitysmith/client.py b/activitysmith/client.py
index 07187f4..39682db 100644
--- a/activitysmith/client.py
+++ b/activitysmith/client.py
@@ -16,7 +16,7 @@
from .normalization import normalize_live_activity_request, normalize_metric_request
-SDK_VERSION = "1.12.0"
+SDK_VERSION = "1.13.0"
SDK_HEADER_NAME = "X-ActivitySmith-SDK"
SDK_HEADER_VALUE = f"python-v{SDK_VERSION}"
@@ -44,9 +44,17 @@ def _has_actions(request: Any) -> bool:
return True
+_PUSH_INTERRUPTION_LEVELS = ("passive", "active", "time-sensitive")
+
+
def _validate_push_request(request: Any) -> Any:
if _has_media(request) and _has_actions(request):
raise ValueError("ActivitySmith: media cannot be combined with actions")
+ level = _request_value(request, "interruption_level")
+ if hasattr(level, "value"):
+ level = level.value
+ if level is not None and level not in _PUSH_INTERRUPTION_LEVELS:
+ raise ValueError("ActivitySmith: interruption_level must be passive, active, or time-sensitive")
return request
@@ -222,6 +230,8 @@ def _build_push_request(
channels: Any | None = None,
tags: Any | None = None,
metadata: Any | None = None,
+ icon: Any | None = None,
+ interruption_level: Any | None = None,
) -> Any:
request_fields = _compact_dict(
{
@@ -235,6 +245,8 @@ def _build_push_request(
"channels": channels,
"tags": tags,
"metadata": metadata,
+ "icon": icon,
+ "interruption_level": interruption_level,
}
)
@@ -254,6 +266,14 @@ def _build_push_request(
return normalized
+class PushInterruptionLevel:
+ """Push Notification interruption levels."""
+
+ PASSIVE = "passive"
+ ACTIVE = "active"
+ TIME_SENSITIVE = "time-sensitive"
+
+
class LiveActivityColor:
LIME = "lime"
GREEN = "green"
@@ -390,6 +410,8 @@ def send(
channels: Any | None = None,
tags: Any | None = None,
metadata: Any | None = None,
+ icon: Any | None = None,
+ interruption_level: Any | None = None,
):
request = _build_push_request(
request,
@@ -403,6 +425,8 @@ def send(
channels=channels,
tags=tags,
metadata=metadata,
+ icon=icon,
+ interruption_level=interruption_level,
)
normalized = normalize_metadata_request(_validate_push_request(_normalize_channels_target(request)))
return self._api.send_push_notification(
diff --git a/activitysmith_openapi/__init__.py b/activitysmith_openapi/__init__.py
index be074a0..da2e10f 100644
--- a/activitysmith_openapi/__init__.py
+++ b/activitysmith_openapi/__init__.py
@@ -14,7 +14,7 @@
""" # noqa: E501
-__version__ = "1.12.0"
+__version__ = "1.13.0"
# import apis into sdk package
from activitysmith_openapi.api.app_icon_badges_api import AppIconBadgesApi
@@ -41,6 +41,8 @@
from activitysmith_openapi.models.app_icon_badge_count_update_request import AppIconBadgeCountUpdateRequest
from activitysmith_openapi.models.app_icon_badge_count_update_response import AppIconBadgeCountUpdateResponse
from activitysmith_openapi.models.bad_request_error import BadRequestError
+from activitysmith_openapi.models.billing_blocked_error import BillingBlockedError
+from activitysmith_openapi.models.billing_blocked_error_trial_period import BillingBlockedErrorTrialPeriod
from activitysmith_openapi.models.channel_target import ChannelTarget
from activitysmith_openapi.models.content_state_end import ContentStateEnd
from activitysmith_openapi.models.content_state_start import ContentStateStart
@@ -71,6 +73,7 @@
from activitysmith_openapi.models.metric_value_update_response import MetricValueUpdateResponse
from activitysmith_openapi.models.no_recipients_error import NoRecipientsError
from activitysmith_openapi.models.not_found_error import NotFoundError
+from activitysmith_openapi.models.push_interruption_level import PushInterruptionLevel
from activitysmith_openapi.models.push_notification_action import PushNotificationAction
from activitysmith_openapi.models.push_notification_action_type import PushNotificationActionType
from activitysmith_openapi.models.push_notification_request import PushNotificationRequest
diff --git a/activitysmith_openapi/api/app_icon_badges_api.py b/activitysmith_openapi/api/app_icon_badges_api.py
index 812dc28..0053614 100644
--- a/activitysmith_openapi/api/app_icon_badges_api.py
+++ b/activitysmith_openapi/api/app_icon_badges_api.py
@@ -93,6 +93,7 @@ def update_app_icon_badge_count(
_response_types_map: Dict[str, Optional[str]] = {
'200': "AppIconBadgeCountUpdateResponse",
'400': "BadRequestError",
+ '402': "BillingBlockedError",
'403': "ForbiddenError",
'422': "UpdateAppIconBadgeCount422Response",
'429': "RateLimitError",
@@ -165,6 +166,7 @@ def update_app_icon_badge_count_with_http_info(
_response_types_map: Dict[str, Optional[str]] = {
'200': "AppIconBadgeCountUpdateResponse",
'400': "BadRequestError",
+ '402': "BillingBlockedError",
'403': "ForbiddenError",
'422': "UpdateAppIconBadgeCount422Response",
'429': "RateLimitError",
@@ -237,6 +239,7 @@ def update_app_icon_badge_count_without_preload_content(
_response_types_map: Dict[str, Optional[str]] = {
'200': "AppIconBadgeCountUpdateResponse",
'400': "BadRequestError",
+ '402': "BillingBlockedError",
'403': "ForbiddenError",
'422': "UpdateAppIconBadgeCount422Response",
'429': "RateLimitError",
diff --git a/activitysmith_openapi/api/live_activities_api.py b/activitysmith_openapi/api/live_activities_api.py
index 05991eb..db54610 100644
--- a/activitysmith_openapi/api/live_activities_api.py
+++ b/activitysmith_openapi/api/live_activities_api.py
@@ -682,8 +682,9 @@ def reconcile_live_activity_stream(
_response_types_map: Dict[str, Optional[str]] = {
'200': "LiveActivityStreamPutResponse",
'400': "BadRequestError",
+ '402': "BillingBlockedError",
'403': "ForbiddenError",
- '404': "NoRecipientsError",
+ '422': "NoRecipientsError",
'429': "SendPushNotification429Response",
}
response_data = self.api_client.call_api(
@@ -757,8 +758,9 @@ def reconcile_live_activity_stream_with_http_info(
_response_types_map: Dict[str, Optional[str]] = {
'200': "LiveActivityStreamPutResponse",
'400': "BadRequestError",
+ '402': "BillingBlockedError",
'403': "ForbiddenError",
- '404': "NoRecipientsError",
+ '422': "NoRecipientsError",
'429': "SendPushNotification429Response",
}
response_data = self.api_client.call_api(
@@ -832,8 +834,9 @@ def reconcile_live_activity_stream_without_preload_content(
_response_types_map: Dict[str, Optional[str]] = {
'200': "LiveActivityStreamPutResponse",
'400': "BadRequestError",
+ '402': "BillingBlockedError",
'403': "ForbiddenError",
- '404': "NoRecipientsError",
+ '422': "NoRecipientsError",
'429': "SendPushNotification429Response",
}
response_data = self.api_client.call_api(
@@ -977,8 +980,9 @@ def start_live_activity(
_response_types_map: Dict[str, Optional[str]] = {
'200': "LiveActivityStartResponse",
'400': "BadRequestError",
+ '402': "BillingBlockedError",
'403': "ForbiddenError",
- '404': "NoRecipientsError",
+ '422': "NoRecipientsError",
'429': "SendPushNotification429Response",
}
response_data = self.api_client.call_api(
@@ -1048,8 +1052,9 @@ def start_live_activity_with_http_info(
_response_types_map: Dict[str, Optional[str]] = {
'200': "LiveActivityStartResponse",
'400': "BadRequestError",
+ '402': "BillingBlockedError",
'403': "ForbiddenError",
- '404': "NoRecipientsError",
+ '422': "NoRecipientsError",
'429': "SendPushNotification429Response",
}
response_data = self.api_client.call_api(
@@ -1119,8 +1124,9 @@ def start_live_activity_without_preload_content(
_response_types_map: Dict[str, Optional[str]] = {
'200': "LiveActivityStartResponse",
'400': "BadRequestError",
+ '402': "BillingBlockedError",
'403': "ForbiddenError",
- '404': "NoRecipientsError",
+ '422': "NoRecipientsError",
'429': "SendPushNotification429Response",
}
response_data = self.api_client.call_api(
diff --git a/activitysmith_openapi/api/push_notifications_api.py b/activitysmith_openapi/api/push_notifications_api.py
index 63bce4b..4a65371 100644
--- a/activitysmith_openapi/api/push_notifications_api.py
+++ b/activitysmith_openapi/api/push_notifications_api.py
@@ -56,7 +56,7 @@ def send_push_notification(
) -> PushNotificationResponse:
"""Send a push notification
- Sends a push notification to devices matched by API key scope and optional target channels. Supports optional redirection URL, optional media preview or playback when the notification is expanded, and up to 4 interactive actions. `media` cannot be combined with `actions`. Optional tags to organize and filter notification history.
+ Sends a push notification to devices matched by API key scope and optional target channels. Supports optional redirection URL, optional media preview or playback when the notification is expanded, and up to 4 interactive actions. `media` cannot be combined with `actions`. Optional `icon` shows a custom image, such as a service logo or avatar, next to a smaller app icon. Optional `interruption_level` sets how urgently iOS presents the notification. Optional tags to organize and filter notification history.
:param push_notification_request: (required)
:type push_notification_request: PushNotificationRequest
@@ -93,8 +93,9 @@ def send_push_notification(
_response_types_map: Dict[str, Optional[str]] = {
'200': "PushNotificationResponse",
'400': "BadRequestError",
+ '402': "BillingBlockedError",
'403': "ForbiddenError",
- '404': "NoRecipientsError",
+ '422': "NoRecipientsError",
'429': "SendPushNotification429Response",
}
response_data = self.api_client.call_api(
@@ -127,7 +128,7 @@ def send_push_notification_with_http_info(
) -> ApiResponse[PushNotificationResponse]:
"""Send a push notification
- Sends a push notification to devices matched by API key scope and optional target channels. Supports optional redirection URL, optional media preview or playback when the notification is expanded, and up to 4 interactive actions. `media` cannot be combined with `actions`. Optional tags to organize and filter notification history.
+ Sends a push notification to devices matched by API key scope and optional target channels. Supports optional redirection URL, optional media preview or playback when the notification is expanded, and up to 4 interactive actions. `media` cannot be combined with `actions`. Optional `icon` shows a custom image, such as a service logo or avatar, next to a smaller app icon. Optional `interruption_level` sets how urgently iOS presents the notification. Optional tags to organize and filter notification history.
:param push_notification_request: (required)
:type push_notification_request: PushNotificationRequest
@@ -164,8 +165,9 @@ def send_push_notification_with_http_info(
_response_types_map: Dict[str, Optional[str]] = {
'200': "PushNotificationResponse",
'400': "BadRequestError",
+ '402': "BillingBlockedError",
'403': "ForbiddenError",
- '404': "NoRecipientsError",
+ '422': "NoRecipientsError",
'429': "SendPushNotification429Response",
}
response_data = self.api_client.call_api(
@@ -198,7 +200,7 @@ def send_push_notification_without_preload_content(
) -> RESTResponseType:
"""Send a push notification
- Sends a push notification to devices matched by API key scope and optional target channels. Supports optional redirection URL, optional media preview or playback when the notification is expanded, and up to 4 interactive actions. `media` cannot be combined with `actions`. Optional tags to organize and filter notification history.
+ Sends a push notification to devices matched by API key scope and optional target channels. Supports optional redirection URL, optional media preview or playback when the notification is expanded, and up to 4 interactive actions. `media` cannot be combined with `actions`. Optional `icon` shows a custom image, such as a service logo or avatar, next to a smaller app icon. Optional `interruption_level` sets how urgently iOS presents the notification. Optional tags to organize and filter notification history.
:param push_notification_request: (required)
:type push_notification_request: PushNotificationRequest
@@ -235,8 +237,9 @@ def send_push_notification_without_preload_content(
_response_types_map: Dict[str, Optional[str]] = {
'200': "PushNotificationResponse",
'400': "BadRequestError",
+ '402': "BillingBlockedError",
'403': "ForbiddenError",
- '404': "NoRecipientsError",
+ '422': "NoRecipientsError",
'429': "SendPushNotification429Response",
}
response_data = self.api_client.call_api(
diff --git a/activitysmith_openapi/api_client.py b/activitysmith_openapi/api_client.py
index 0b0e3ef..ce1b024 100644
--- a/activitysmith_openapi/api_client.py
+++ b/activitysmith_openapi/api_client.py
@@ -88,7 +88,7 @@ def __init__(
self.default_headers[header_name] = header_value
self.cookie = cookie
# Set default User-Agent.
- self.user_agent = 'OpenAPI-Generator/1.12.0/python'
+ self.user_agent = 'OpenAPI-Generator/1.13.0/python'
self.client_side_validation = configuration.client_side_validation
def __enter__(self):
diff --git a/activitysmith_openapi/configuration.py b/activitysmith_openapi/configuration.py
index 541b52f..4ea33f8 100644
--- a/activitysmith_openapi/configuration.py
+++ b/activitysmith_openapi/configuration.py
@@ -400,7 +400,7 @@ def to_debug_report(self):
"OS: {env}\n"\
"Python Version: {pyversion}\n"\
"Version of the API: 1.0.0\n"\
- "SDK Package Version: 1.12.0".\
+ "SDK Package Version: 1.13.0".\
format(env=sys.platform, pyversion=sys.version)
def get_host_settings(self):
diff --git a/activitysmith_openapi/docs/AppIconBadgesApi.md b/activitysmith_openapi/docs/AppIconBadgesApi.md
index 2f63367..0241b41 100644
--- a/activitysmith_openapi/docs/AppIconBadgesApi.md
+++ b/activitysmith_openapi/docs/AppIconBadgesApi.md
@@ -84,6 +84,7 @@ Name | Type | Description | Notes
|-------------|-------------|------------------|
**200** | App Icon Badge Count updated | * RateLimit - Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.
* RateLimit-Policy - Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.
|
**400** | Bad request | * RateLimit - Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.
* RateLimit-Policy - Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.
|
+**402** | Billing access required | - |
**403** | Forbidden | * RateLimit - Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.
* RateLimit-Policy - Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.
|
**422** | No matching devices found, or a targeted device needs to reconnect | * RateLimit - Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.
* RateLimit-Policy - Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.
|
**429** | Rate limit exceeded | * RateLimit - Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.
* RateLimit-Policy - Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.
* Retry-After - Seconds to wait before retrying after a request-rate 429. Live Activity capacity errors are separate and require freeing device capacity; they do not provide a timed retry guarantee.
|
diff --git a/activitysmith_openapi/docs/BillingBlockedError.md b/activitysmith_openapi/docs/BillingBlockedError.md
new file mode 100644
index 0000000..43eebc0
--- /dev/null
+++ b/activitysmith_openapi/docs/BillingBlockedError.md
@@ -0,0 +1,32 @@
+# BillingBlockedError
+
+
+## Properties
+
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**error** | **str** | |
+**message** | **str** | |
+**trial_period** | [**BillingBlockedErrorTrialPeriod**](BillingBlockedErrorTrialPeriod.md) | | [optional]
+**upgrade_url** | **str** | |
+
+## Example
+
+```python
+from activitysmith_openapi.models.billing_blocked_error import BillingBlockedError
+
+# TODO update the JSON string below
+json = "{}"
+# create an instance of BillingBlockedError from a JSON string
+billing_blocked_error_instance = BillingBlockedError.from_json(json)
+# print the JSON string representation of the object
+print(BillingBlockedError.to_json())
+
+# convert the object into a dict
+billing_blocked_error_dict = billing_blocked_error_instance.to_dict()
+# create an instance of BillingBlockedError from a dict
+billing_blocked_error_from_dict = BillingBlockedError.from_dict(billing_blocked_error_dict)
+```
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/activitysmith_openapi/docs/BillingBlockedErrorTrialPeriod.md b/activitysmith_openapi/docs/BillingBlockedErrorTrialPeriod.md
new file mode 100644
index 0000000..105e046
--- /dev/null
+++ b/activitysmith_openapi/docs/BillingBlockedErrorTrialPeriod.md
@@ -0,0 +1,30 @@
+# BillingBlockedErrorTrialPeriod
+
+
+## Properties
+
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**started_at** | **datetime** | |
+**ends_at** | **datetime** | |
+
+## Example
+
+```python
+from activitysmith_openapi.models.billing_blocked_error_trial_period import BillingBlockedErrorTrialPeriod
+
+# TODO update the JSON string below
+json = "{}"
+# create an instance of BillingBlockedErrorTrialPeriod from a JSON string
+billing_blocked_error_trial_period_instance = BillingBlockedErrorTrialPeriod.from_json(json)
+# print the JSON string representation of the object
+print(BillingBlockedErrorTrialPeriod.to_json())
+
+# convert the object into a dict
+billing_blocked_error_trial_period_dict = billing_blocked_error_trial_period_instance.to_dict()
+# create an instance of BillingBlockedErrorTrialPeriod from a dict
+billing_blocked_error_trial_period_from_dict = BillingBlockedErrorTrialPeriod.from_dict(billing_blocked_error_trial_period_dict)
+```
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/activitysmith_openapi/docs/LiveActivitiesApi.md b/activitysmith_openapi/docs/LiveActivitiesApi.md
index 29e401a..bac4b47 100644
--- a/activitysmith_openapi/docs/LiveActivitiesApi.md
+++ b/activitysmith_openapi/docs/LiveActivitiesApi.md
@@ -255,8 +255,9 @@ Name | Type | Description | Notes
|-------------|-------------|------------------|
**200** | Stream reconciled | * RateLimit - Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.
* RateLimit-Policy - Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.
|
**400** | Bad request (invalid stream_key, payload, action, or channel targeting input) | * RateLimit - Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.
* RateLimit-Policy - Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.
|
+**402** | Trial expired, trial allowance exhausted, or paid usage cap reached | - |
**403** | Forbidden (API key scope or channel assignment violation) | * RateLimit - Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.
* RateLimit-Policy - Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.
|
-**404** | No recipients found for effective channel target | * RateLimit - Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.
* RateLimit-Policy - Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.
|
+**422** | No recipients found for effective channel target | * RateLimit - Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.
* RateLimit-Policy - Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.
|
**429** | Rate limit exceeded | * RateLimit - Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.
* RateLimit-Policy - Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.
* Retry-After - Seconds to wait before retrying after a request-rate 429. Live Activity capacity errors are separate and require freeing device capacity; they do not provide a timed retry guarantee.
|
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
@@ -338,8 +339,9 @@ Name | Type | Description | Notes
|-------------|-------------|------------------|
**200** | Live Activity started | * RateLimit - Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.
* RateLimit-Policy - Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.
|
**400** | Bad request (invalid payload or channel targeting input) | * RateLimit - Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.
* RateLimit-Policy - Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.
|
+**402** | Billing access required | - |
**403** | Forbidden (API key scope or channel assignment violation) | * RateLimit - Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.
* RateLimit-Policy - Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.
|
-**404** | No recipients found for effective channel target | * RateLimit - Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.
* RateLimit-Policy - Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.
|
+**422** | No recipients found for effective channel target | * RateLimit - Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.
* RateLimit-Policy - Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.
|
**429** | Rate limit exceeded | * RateLimit - Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.
* RateLimit-Policy - Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.
* Retry-After - Seconds to wait before retrying after a request-rate 429. Live Activity capacity errors are separate and require freeing device capacity; they do not provide a timed retry guarantee.
|
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
diff --git a/activitysmith_openapi/docs/PushInterruptionLevel.md b/activitysmith_openapi/docs/PushInterruptionLevel.md
new file mode 100644
index 0000000..245c58b
--- /dev/null
+++ b/activitysmith_openapi/docs/PushInterruptionLevel.md
@@ -0,0 +1,15 @@
+# PushInterruptionLevel
+
+How urgently iOS presents the notification. `passive` arrives quietly in the notification list without sound or lighting up the screen. `active` is normal delivery and the default when omitted. `time-sensitive` can break through Focus and Scheduled Summary when the user allows Time Sensitive Notifications for ActivitySmith.
+
+## Enum
+
+* `PASSIVE` (value: `'passive'`)
+
+* `ACTIVE` (value: `'active'`)
+
+* `TIME_MINUS_SENSITIVE` (value: `'time-sensitive'`)
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/activitysmith_openapi/docs/PushNotificationRequest.md b/activitysmith_openapi/docs/PushNotificationRequest.md
index 1c45c9e..eccf650 100644
--- a/activitysmith_openapi/docs/PushNotificationRequest.md
+++ b/activitysmith_openapi/docs/PushNotificationRequest.md
@@ -9,6 +9,8 @@ Name | Type | Description | Notes
**title** | **str** | |
**message** | **str** | | [optional]
**subtitle** | **str** | | [optional]
+**icon** | **str** | Optional HTTPS image URL without credentials for a custom notification icon. If the image cannot be loaded, the app icon is used. iOS may omit subtitle when displaying a custom icon. | [optional]
+**interruption_level** | [**PushInterruptionLevel**](PushInterruptionLevel.md) | Optional. How urgently iOS presents the notification: `passive` (quiet, no sound or screen wake), `active` (default), or `time-sensitive` (can break through Focus and Scheduled Summary when allowed). Omit for normal delivery. | [optional]
**media** | **str** | Optional HTTPS URL for an image, audio file, or video that users can preview or play when they expand the notification. If `redirection` is omitted, tapping the notification opens this URL. Cannot be combined with `actions`. | [optional]
**redirection** | **str** | Optional HTTP, HTTPS, Shortcuts, or installed app URL opened when the user taps the notification body. Custom schemes such as spotify:// and spotify:track:123 require iOS 1.13.4 build 2 or later and an installed handler; no web fallback is provided. Internal and executable schemes are blocked. Overrides the default tap target from media. | [optional]
**actions** | [**List[PushNotificationAction]**](PushNotificationAction.md) | Optional interactive actions shown when users expand the notification. Cannot be combined with `media`. | [optional]
diff --git a/activitysmith_openapi/docs/PushNotificationResponse.md b/activitysmith_openapi/docs/PushNotificationResponse.md
index 86e8e37..1e78b7f 100644
--- a/activitysmith_openapi/docs/PushNotificationResponse.md
+++ b/activitysmith_openapi/docs/PushNotificationResponse.md
@@ -1,12 +1,13 @@
# PushNotificationResponse
+Apple APNs acceptance result. Acceptance does not confirm device delivery or presentation. Alerts remain eligible for APNs delivery retries for 24 hours; device settings and APNs storage policies still apply.
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
-**success** | **bool** | |
-**devices_notified** | **int** | | [optional]
+**success** | **bool** | True when APNs accepts the notification for at least one targeted device. |
+**devices_notified** | **int** | Number of device tokens for which APNs accepted the notification, without confirming on-device delivery. | [optional]
**users_notified** | **int** | | [optional]
**effective_channel_slugs** | **List[str]** | | [optional]
**tags** | **List[str]** | Optional tags to organize and filter notification history. | [optional]
diff --git a/activitysmith_openapi/docs/PushNotificationsApi.md b/activitysmith_openapi/docs/PushNotificationsApi.md
index 5dab075..ef3b424 100644
--- a/activitysmith_openapi/docs/PushNotificationsApi.md
+++ b/activitysmith_openapi/docs/PushNotificationsApi.md
@@ -12,7 +12,7 @@ Method | HTTP request | Description
Send a push notification
-Sends a push notification to devices matched by API key scope and optional target channels. Supports optional redirection URL, optional media preview or playback when the notification is expanded, and up to 4 interactive actions. `media` cannot be combined with `actions`. Optional tags to organize and filter notification history.
+Sends a push notification to devices matched by API key scope and optional target channels. Supports optional redirection URL, optional media preview or playback when the notification is expanded, and up to 4 interactive actions. `media` cannot be combined with `actions`. Optional `icon` shows a custom image, such as a service logo or avatar, next to a smaller app icon. Optional `interruption_level` sets how urgently iOS presents the notification. Optional tags to organize and filter notification history.
### Example
@@ -84,8 +84,9 @@ Name | Type | Description | Notes
|-------------|-------------|------------------|
**200** | Push notification sent | * RateLimit - Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.
* RateLimit-Policy - Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.
|
**400** | Bad request (invalid payload or channel targeting input) | * RateLimit - Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.
* RateLimit-Policy - Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.
|
+**402** | Billing access required | - |
**403** | Forbidden (API key scope or channel assignment violation) | * RateLimit - Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.
* RateLimit-Policy - Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.
|
-**404** | No recipients found for effective channel target | * RateLimit - Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.
* RateLimit-Policy - Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.
|
+**422** | No recipients found for effective channel target | * RateLimit - Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.
* RateLimit-Policy - Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.
|
**429** | Rate limit exceeded | * RateLimit - Current request quotas using IETF draft-8 structured fields. Each named policy includes r (remaining requests) and t (seconds until reset). Multiple policies may apply.
* RateLimit-Policy - Each named policy includes q (request quota) and w (window in seconds). Each feature has an independent IP limit of 600 requests per 60 seconds. Independent account limits per 60 seconds are: Push Notifications 60, badges 60, Live Activities 300, and metric updates 300.
* Retry-After - Seconds to wait before retrying after a request-rate 429. Live Activity capacity errors are separate and require freeing device capacity; they do not provide a timed retry guarantee.
|
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
diff --git a/activitysmith_openapi/models/__init__.py b/activitysmith_openapi/models/__init__.py
index fd0ed6e..6fd8af3 100644
--- a/activitysmith_openapi/models/__init__.py
+++ b/activitysmith_openapi/models/__init__.py
@@ -21,6 +21,8 @@
from activitysmith_openapi.models.app_icon_badge_count_update_request import AppIconBadgeCountUpdateRequest
from activitysmith_openapi.models.app_icon_badge_count_update_response import AppIconBadgeCountUpdateResponse
from activitysmith_openapi.models.bad_request_error import BadRequestError
+from activitysmith_openapi.models.billing_blocked_error import BillingBlockedError
+from activitysmith_openapi.models.billing_blocked_error_trial_period import BillingBlockedErrorTrialPeriod
from activitysmith_openapi.models.channel_target import ChannelTarget
from activitysmith_openapi.models.content_state_end import ContentStateEnd
from activitysmith_openapi.models.content_state_start import ContentStateStart
@@ -51,6 +53,7 @@
from activitysmith_openapi.models.metric_value_update_response import MetricValueUpdateResponse
from activitysmith_openapi.models.no_recipients_error import NoRecipientsError
from activitysmith_openapi.models.not_found_error import NotFoundError
+from activitysmith_openapi.models.push_interruption_level import PushInterruptionLevel
from activitysmith_openapi.models.push_notification_action import PushNotificationAction
from activitysmith_openapi.models.push_notification_action_type import PushNotificationActionType
from activitysmith_openapi.models.push_notification_request import PushNotificationRequest
diff --git a/activitysmith_openapi/models/billing_blocked_error.py b/activitysmith_openapi/models/billing_blocked_error.py
new file mode 100644
index 0000000..58be5c8
--- /dev/null
+++ b/activitysmith_openapi/models/billing_blocked_error.py
@@ -0,0 +1,104 @@
+# coding: utf-8
+
+"""
+ ActivitySmith API
+
+ Send push notifications and Live Activities to your own devices via a single API key.
+
+ The version of the OpenAPI document: 1.0.0
+ Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+ Do not edit the class manually.
+""" # noqa: E501
+
+
+from __future__ import annotations
+import pprint
+import re # noqa: F401
+import json
+
+from pydantic import BaseModel, ConfigDict, StrictStr, field_validator
+from typing import Any, ClassVar, Dict, List, Optional
+from activitysmith_openapi.models.billing_blocked_error_trial_period import BillingBlockedErrorTrialPeriod
+from typing import Optional, Set
+from typing_extensions import Self
+
+class BillingBlockedError(BaseModel):
+ """
+ BillingBlockedError
+ """ # noqa: E501
+ error: StrictStr
+ message: StrictStr
+ trial_period: Optional[BillingBlockedErrorTrialPeriod] = None
+ upgrade_url: StrictStr
+ __properties: ClassVar[List[str]] = ["error", "message", "trial_period", "upgrade_url"]
+
+ @field_validator('error')
+ def error_validate_enum(cls, value):
+ """Validates the enum"""
+ if value not in set(['trial_expired', 'trial_exhausted', 'billing_overage_cap_reached']):
+ raise ValueError("must be one of enum values ('trial_expired', 'trial_exhausted', 'billing_overage_cap_reached')")
+ return value
+
+ model_config = ConfigDict(
+ populate_by_name=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
+ return json.dumps(self.to_dict())
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of BillingBlockedError from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([
+ ])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ # override the default output from pydantic by calling `to_dict()` of trial_period
+ if self.trial_period:
+ _dict['trial_period'] = self.trial_period.to_dict()
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of BillingBlockedError from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate({
+ "error": obj.get("error"),
+ "message": obj.get("message"),
+ "trial_period": BillingBlockedErrorTrialPeriod.from_dict(obj["trial_period"]) if obj.get("trial_period") is not None else None,
+ "upgrade_url": obj.get("upgrade_url")
+ })
+ return _obj
+
+
diff --git a/activitysmith_openapi/models/billing_blocked_error_trial_period.py b/activitysmith_openapi/models/billing_blocked_error_trial_period.py
new file mode 100644
index 0000000..1b14616
--- /dev/null
+++ b/activitysmith_openapi/models/billing_blocked_error_trial_period.py
@@ -0,0 +1,90 @@
+# coding: utf-8
+
+"""
+ ActivitySmith API
+
+ Send push notifications and Live Activities to your own devices via a single API key.
+
+ The version of the OpenAPI document: 1.0.0
+ Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+ Do not edit the class manually.
+""" # noqa: E501
+
+
+from __future__ import annotations
+import pprint
+import re # noqa: F401
+import json
+
+from datetime import datetime
+from pydantic import BaseModel, ConfigDict
+from typing import Any, ClassVar, Dict, List
+from typing import Optional, Set
+from typing_extensions import Self
+
+class BillingBlockedErrorTrialPeriod(BaseModel):
+ """
+ BillingBlockedErrorTrialPeriod
+ """ # noqa: E501
+ started_at: datetime
+ ends_at: datetime
+ __properties: ClassVar[List[str]] = ["started_at", "ends_at"]
+
+ model_config = ConfigDict(
+ populate_by_name=True,
+ validate_assignment=True,
+ protected_namespaces=(),
+ )
+
+
+ def to_str(self) -> str:
+ """Returns the string representation of the model using alias"""
+ return pprint.pformat(self.model_dump(by_alias=True))
+
+ def to_json(self) -> str:
+ """Returns the JSON representation of the model using alias"""
+ # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
+ return json.dumps(self.to_dict())
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Optional[Self]:
+ """Create an instance of BillingBlockedErrorTrialPeriod from a JSON string"""
+ return cls.from_dict(json.loads(json_str))
+
+ def to_dict(self) -> Dict[str, Any]:
+ """Return the dictionary representation of the model using alias.
+
+ This has the following differences from calling pydantic's
+ `self.model_dump(by_alias=True)`:
+
+ * `None` is only added to the output dict for nullable fields that
+ were set at model initialization. Other fields with value `None`
+ are ignored.
+ """
+ excluded_fields: Set[str] = set([
+ ])
+
+ _dict = self.model_dump(
+ by_alias=True,
+ exclude=excluded_fields,
+ exclude_none=True,
+ )
+ return _dict
+
+ @classmethod
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
+ """Create an instance of BillingBlockedErrorTrialPeriod from a dict"""
+ if obj is None:
+ return None
+
+ if not isinstance(obj, dict):
+ return cls.model_validate(obj)
+
+ _obj = cls.model_validate({
+ "started_at": obj.get("started_at"),
+ "ends_at": obj.get("ends_at")
+ })
+ return _obj
+
+
diff --git a/activitysmith_openapi/models/push_interruption_level.py b/activitysmith_openapi/models/push_interruption_level.py
new file mode 100644
index 0000000..d488fa9
--- /dev/null
+++ b/activitysmith_openapi/models/push_interruption_level.py
@@ -0,0 +1,38 @@
+# coding: utf-8
+
+"""
+ ActivitySmith API
+
+ Send push notifications and Live Activities to your own devices via a single API key.
+
+ The version of the OpenAPI document: 1.0.0
+ Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+ Do not edit the class manually.
+""" # noqa: E501
+
+
+from __future__ import annotations
+import json
+from enum import Enum
+from typing_extensions import Self
+
+
+class PushInterruptionLevel(str, Enum):
+ """
+ How urgently iOS presents the notification. `passive` arrives quietly in the notification list without sound or lighting up the screen. `active` is normal delivery and the default when omitted. `time-sensitive` can break through Focus and Scheduled Summary when the user allows Time Sensitive Notifications for ActivitySmith.
+ """
+
+ """
+ allowed enum values
+ """
+ PASSIVE = 'passive'
+ ACTIVE = 'active'
+ TIME_MINUS_SENSITIVE = 'time-sensitive'
+
+ @classmethod
+ def from_json(cls, json_str: str) -> Self:
+ """Create an instance of PushInterruptionLevel from a JSON string"""
+ return cls(json.loads(json_str))
+
+
diff --git a/activitysmith_openapi/models/push_notification_request.py b/activitysmith_openapi/models/push_notification_request.py
index c62d06c..f71c50c 100644
--- a/activitysmith_openapi/models/push_notification_request.py
+++ b/activitysmith_openapi/models/push_notification_request.py
@@ -22,6 +22,7 @@
from typing_extensions import Annotated
from activitysmith_openapi.models.channel_target import ChannelTarget
from activitysmith_openapi.models.metadata_value import MetadataValue
+from activitysmith_openapi.models.push_interruption_level import PushInterruptionLevel
from activitysmith_openapi.models.push_notification_action import PushNotificationAction
from typing import Optional, Set
from typing_extensions import Self
@@ -34,6 +35,8 @@ class PushNotificationRequest(BaseModel):
title: StrictStr
message: Optional[StrictStr] = None
subtitle: Optional[StrictStr] = None
+ icon: Optional[Annotated[str, Field(strict=True)]] = Field(default=None, description="Optional HTTPS image URL without credentials for a custom notification icon. If the image cannot be loaded, the app icon is used. iOS may omit subtitle when displaying a custom icon.")
+ interruption_level: Optional[PushInterruptionLevel] = Field(default=None, description="Optional. How urgently iOS presents the notification: `passive` (quiet, no sound or screen wake), `active` (default), or `time-sensitive` (can break through Focus and Scheduled Summary when allowed). Omit for normal delivery.")
media: Optional[Annotated[str, Field(strict=True)]] = Field(default=None, description="Optional HTTPS URL for an image, audio file, or video that users can preview or play when they expand the notification. If `redirection` is omitted, tapping the notification opens this URL. Cannot be combined with `actions`.")
redirection: Optional[Annotated[str, Field(strict=True, max_length=2048)]] = Field(default=None, description="Optional HTTP, HTTPS, Shortcuts, or installed app URL opened when the user taps the notification body. Custom schemes such as spotify:// and spotify:track:123 require iOS 1.13.4 build 2 or later and an installed handler; no web fallback is provided. Internal and executable schemes are blocked. Overrides the default tap target from media.")
actions: Optional[Annotated[List[PushNotificationAction], Field(max_length=4)]] = Field(default=None, description="Optional interactive actions shown when users expand the notification. Cannot be combined with `media`.")
@@ -43,7 +46,17 @@ class PushNotificationRequest(BaseModel):
target: Optional[ChannelTarget] = None
tags: Optional[List[Annotated[str, Field(min_length=1, strict=True, max_length=64)]]] = Field(default=None, description="Optional tags to organize and filter notification history.")
additional_properties: Dict[str, Any] = {}
- __properties: ClassVar[List[str]] = ["metadata", "title", "message", "subtitle", "media", "redirection", "actions", "payload", "badge", "sound", "target", "tags"]
+ __properties: ClassVar[List[str]] = ["metadata", "title", "message", "subtitle", "icon", "interruption_level", "media", "redirection", "actions", "payload", "badge", "sound", "target", "tags"]
+
+ @field_validator('icon')
+ def icon_validate_regular_expression(cls, value):
+ """Validates the regular expression"""
+ if value is None:
+ return value
+
+ if not re.match(r"^https:\/\/", value):
+ raise ValueError(r"must validate the regular expression /^https:\/\//")
+ return value
@field_validator('media')
def media_validate_regular_expression(cls, value):
@@ -149,6 +162,8 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"title": obj.get("title"),
"message": obj.get("message"),
"subtitle": obj.get("subtitle"),
+ "icon": obj.get("icon"),
+ "interruption_level": obj.get("interruption_level"),
"media": obj.get("media"),
"redirection": obj.get("redirection"),
"actions": [PushNotificationAction.from_dict(_item) for _item in obj["actions"]] if obj.get("actions") is not None else None,
diff --git a/activitysmith_openapi/models/push_notification_response.py b/activitysmith_openapi/models/push_notification_response.py
index de1c376..816142d 100644
--- a/activitysmith_openapi/models/push_notification_response.py
+++ b/activitysmith_openapi/models/push_notification_response.py
@@ -26,10 +26,10 @@
class PushNotificationResponse(BaseModel):
"""
- PushNotificationResponse
+ Apple APNs acceptance result. Acceptance does not confirm device delivery or presentation. Alerts remain eligible for APNs delivery retries for 24 hours; device settings and APNs storage policies still apply.
""" # noqa: E501
- success: StrictBool
- devices_notified: Optional[StrictInt] = None
+ success: StrictBool = Field(description="True when APNs accepts the notification for at least one targeted device.")
+ devices_notified: Optional[StrictInt] = Field(default=None, description="Number of device tokens for which APNs accepted the notification, without confirming on-device delivery.")
users_notified: Optional[StrictInt] = None
effective_channel_slugs: Optional[List[StrictStr]] = None
tags: Optional[List[Annotated[str, Field(min_length=1, strict=True, max_length=64)]]] = Field(default=None, description="Optional tags to organize and filter notification history.")
diff --git a/activitysmith_openapi/openapi-source.json b/activitysmith_openapi/openapi-source.json
index aeeecd6..a320213 100644
--- a/activitysmith_openapi/openapi-source.json
+++ b/activitysmith_openapi/openapi-source.json
@@ -1,6 +1,6 @@
{
"repository": "ActivitySmithHQ/activitysmith-backend",
- "commit": "6832df646de7de8a6aaf0110960c37074b513c48",
+ "commit": "ce15d69eeb25776a3ae395e77d8b907ff7ba0709",
"path": "openapi.json",
- "sha256": "9ae85ad6127cfa2d50c33261459f5f9e85e011939d01ac4a9d507fa6ed11aa61"
+ "sha256": "b113d50e06d6581707d9472dda6e371b996441f58dceba87a88f79cb96508a7d"
}
diff --git a/activitysmith_openapi/test/test_billing_blocked_error.py b/activitysmith_openapi/test/test_billing_blocked_error.py
new file mode 100644
index 0000000..952c2a9
--- /dev/null
+++ b/activitysmith_openapi/test/test_billing_blocked_error.py
@@ -0,0 +1,59 @@
+# coding: utf-8
+
+"""
+ ActivitySmith API
+
+ Send push notifications and Live Activities to your own devices via a single API key.
+
+ The version of the OpenAPI document: 1.0.0
+ Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+ Do not edit the class manually.
+""" # noqa: E501
+
+
+import unittest
+
+from activitysmith_openapi.models.billing_blocked_error import BillingBlockedError
+
+class TestBillingBlockedError(unittest.TestCase):
+ """BillingBlockedError unit test stubs"""
+
+ def setUp(self):
+ pass
+
+ def tearDown(self):
+ pass
+
+ def make_instance(self, include_optional) -> BillingBlockedError:
+ """Test BillingBlockedError
+ include_optional is a boolean, when False only required
+ params are included, when True both required and
+ optional params are included """
+ # uncomment below to create an instance of `BillingBlockedError`
+ """
+ model = BillingBlockedError()
+ if include_optional:
+ return BillingBlockedError(
+ error = 'trial_expired',
+ message = '',
+ trial_period = activitysmith_openapi.models.billing_blocked_error_trial_period.BillingBlockedError_trial_period(
+ started_at = datetime.datetime.strptime('2013-10-20 19:20:30.00', '%Y-%m-%d %H:%M:%S.%f'),
+ ends_at = datetime.datetime.strptime('2013-10-20 19:20:30.00', '%Y-%m-%d %H:%M:%S.%f'), ),
+ upgrade_url = ''
+ )
+ else:
+ return BillingBlockedError(
+ error = 'trial_expired',
+ message = '',
+ upgrade_url = '',
+ )
+ """
+
+ def testBillingBlockedError(self):
+ """Test BillingBlockedError"""
+ # inst_req_only = self.make_instance(include_optional=False)
+ # inst_req_and_optional = self.make_instance(include_optional=True)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/activitysmith_openapi/test/test_billing_blocked_error_trial_period.py b/activitysmith_openapi/test/test_billing_blocked_error_trial_period.py
new file mode 100644
index 0000000..34d9d35
--- /dev/null
+++ b/activitysmith_openapi/test/test_billing_blocked_error_trial_period.py
@@ -0,0 +1,54 @@
+# coding: utf-8
+
+"""
+ ActivitySmith API
+
+ Send push notifications and Live Activities to your own devices via a single API key.
+
+ The version of the OpenAPI document: 1.0.0
+ Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+ Do not edit the class manually.
+""" # noqa: E501
+
+
+import unittest
+
+from activitysmith_openapi.models.billing_blocked_error_trial_period import BillingBlockedErrorTrialPeriod
+
+class TestBillingBlockedErrorTrialPeriod(unittest.TestCase):
+ """BillingBlockedErrorTrialPeriod unit test stubs"""
+
+ def setUp(self):
+ pass
+
+ def tearDown(self):
+ pass
+
+ def make_instance(self, include_optional) -> BillingBlockedErrorTrialPeriod:
+ """Test BillingBlockedErrorTrialPeriod
+ include_optional is a boolean, when False only required
+ params are included, when True both required and
+ optional params are included """
+ # uncomment below to create an instance of `BillingBlockedErrorTrialPeriod`
+ """
+ model = BillingBlockedErrorTrialPeriod()
+ if include_optional:
+ return BillingBlockedErrorTrialPeriod(
+ started_at = datetime.datetime.strptime('2013-10-20 19:20:30.00', '%Y-%m-%d %H:%M:%S.%f'),
+ ends_at = datetime.datetime.strptime('2013-10-20 19:20:30.00', '%Y-%m-%d %H:%M:%S.%f')
+ )
+ else:
+ return BillingBlockedErrorTrialPeriod(
+ started_at = datetime.datetime.strptime('2013-10-20 19:20:30.00', '%Y-%m-%d %H:%M:%S.%f'),
+ ends_at = datetime.datetime.strptime('2013-10-20 19:20:30.00', '%Y-%m-%d %H:%M:%S.%f'),
+ )
+ """
+
+ def testBillingBlockedErrorTrialPeriod(self):
+ """Test BillingBlockedErrorTrialPeriod"""
+ # inst_req_only = self.make_instance(include_optional=False)
+ # inst_req_and_optional = self.make_instance(include_optional=True)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/activitysmith_openapi/test/test_push_interruption_level.py b/activitysmith_openapi/test/test_push_interruption_level.py
new file mode 100644
index 0000000..aa87199
--- /dev/null
+++ b/activitysmith_openapi/test/test_push_interruption_level.py
@@ -0,0 +1,33 @@
+# coding: utf-8
+
+"""
+ ActivitySmith API
+
+ Send push notifications and Live Activities to your own devices via a single API key.
+
+ The version of the OpenAPI document: 1.0.0
+ Generated by OpenAPI Generator (https://openapi-generator.tech)
+
+ Do not edit the class manually.
+""" # noqa: E501
+
+
+import unittest
+
+from activitysmith_openapi.models.push_interruption_level import PushInterruptionLevel
+
+class TestPushInterruptionLevel(unittest.TestCase):
+ """PushInterruptionLevel unit test stubs"""
+
+ def setUp(self):
+ pass
+
+ def tearDown(self):
+ pass
+
+ def testPushInterruptionLevel(self):
+ """Test PushInterruptionLevel"""
+ # inst = PushInterruptionLevel()
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/activitysmith_openapi/test/test_push_notification_request.py b/activitysmith_openapi/test/test_push_notification_request.py
index 803435e..8ed2458 100644
--- a/activitysmith_openapi/test/test_push_notification_request.py
+++ b/activitysmith_openapi/test/test_push_notification_request.py
@@ -41,6 +41,8 @@ def make_instance(self, include_optional) -> PushNotificationRequest:
title = '',
message = '',
subtitle = '',
+ icon = 'https:/',
+ interruption_level = 'passive',
media = 'https:/',
redirection = 'A5bTTFjjMRwg.Zbs8YayHLrJdgMvb:',
actions = [
diff --git a/pyproject.toml b/pyproject.toml
index 4501f59..d390f77 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "activitysmith"
-version = "1.12.0"
+version = "1.13.0"
description = "Official ActivitySmith Python SDK"
readme = "README.md"
requires-python = ">=3.9"
diff --git a/tests/test_value_serialization.py b/tests/test_value_serialization.py
index 9c62212..55ed52d 100644
--- a/tests/test_value_serialization.py
+++ b/tests/test_value_serialization.py
@@ -164,3 +164,48 @@ def test_prominent_value_serializes_without_losing_format(requests, value, metho
getattr(activitysmith.live_activities, method)(*args, request)
assert requests[-1]["content_state"]["value"] == value
assert isinstance(requests[-1]["content_state"]["value"], str) == isinstance(value, str)
+
+
+@pytest.mark.parametrize("form", ["dict", "named"])
+def test_push_icon_serialization(requests, form):
+ client = ActivitySmith(api_key="test")
+ fields = {"title": "New Download", "icon": "https://cdn.example.com/avatar.png"}
+ with pytest.raises(RequestCaptured):
+ if form == "named":
+ client.notifications.send(**fields)
+ else:
+ client.notifications.send(fields)
+ assert requests[-1]["icon"] == "https://cdn.example.com/avatar.png"
+
+
+@pytest.mark.parametrize("level", ["passive", "active", "time-sensitive"])
+@pytest.mark.parametrize("form", ["dict", "named", "constant"])
+def test_push_interruption_level_serialization(requests, level, form):
+ from activitysmith import PushInterruptionLevel
+
+ client = ActivitySmith(api_key="test")
+ if form == "constant":
+ level = {"passive": PushInterruptionLevel.PASSIVE, "active": PushInterruptionLevel.ACTIVE,
+ "time-sensitive": PushInterruptionLevel.TIME_SENSITIVE}[level]
+ fields = {"title": "Deploy", "interruption_level": level}
+ with pytest.raises(RequestCaptured):
+ if form == "dict":
+ client.notifications.send(fields)
+ else:
+ client.notifications.send(**fields)
+ assert requests[-1]["interruption_level"] == level
+
+
+def test_push_interruption_level_omitted_by_default(requests):
+ client = ActivitySmith(api_key="test")
+ with pytest.raises(RequestCaptured):
+ client.notifications.send(title="Deploy")
+ assert "interruption_level" not in requests[-1]
+
+
+@pytest.mark.parametrize("level", ["critical", "timeSensitive", "default"])
+def test_push_rejects_unsupported_interruption_level(requests, level):
+ client = ActivitySmith(api_key="test")
+ with pytest.raises(ValueError, match="interruption_level must be passive, active, or time-sensitive"):
+ client.notifications.send(title="Outage", interruption_level=level)
+ assert requests == []