diff --git a/.stats.yml b/.stats.yml
index 1c9b72d..d24d405 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1 +1 @@
-configured_endpoints: 61
+configured_endpoints: 64
diff --git a/api.md b/api.md
index b0a85d4..e2e7808 100644
--- a/api.md
+++ b/api.md
@@ -242,6 +242,20 @@ Methods:
- client.payments.cancel(payment_id) -> Payment
- client.payments.credentials(payment_id) -> PaymentCredentialsResponse
+# BlockedHandles
+
+Types:
+
+```python
+from linq.types import BlockedHandleEntry, BlockedHandleListResponse, BlockedHandleBlockResponse
+```
+
+Methods:
+
+- client.blocked_handles.list() -> BlockedHandleListResponse
+- client.blocked_handles.block(\*\*params) -> BlockedHandleBlockResponse
+- client.blocked_handles.unblock(\*\*params) -> None
+
# Experiences
Types:
diff --git a/src/linq/_client.py b/src/linq/_client.py
index 10d9dde..769366f 100644
--- a/src/linq/_client.py
+++ b/src/linq/_client.py
@@ -48,6 +48,7 @@
phonenumbers,
phone_numbers,
webhook_events,
+ blocked_handles,
payment_handles,
available_number,
payment_requests,
@@ -64,6 +65,7 @@
from .resources.phonenumbers import PhonenumbersResource, AsyncPhonenumbersResource
from .resources.phone_numbers import PhoneNumbersResource, AsyncPhoneNumbersResource
from .resources.webhook_events import WebhookEventsResource, AsyncWebhookEventsResource
+ from .resources.blocked_handles import BlockedHandlesResource, AsyncBlockedHandlesResource
from .resources.payment_handles import PaymentHandlesResource, AsyncPaymentHandlesResource
from .resources.available_number import AvailableNumberResource, AsyncAvailableNumberResource
from .resources.payment_requests import PaymentRequestsResource, AsyncPaymentRequestsResource
@@ -607,6 +609,20 @@ def payments(self) -> PaymentsResource:
return PaymentsResource(self)
+ @cached_property
+ def blocked_handles(self) -> BlockedHandlesResource:
+ """Block handles — phone numbers, email addresses, SMS short codes, or
+ sender IDs.
+
+ Inbound messages from a blocked handle are dropped before
+ they reach your webhooks, and direct sends to a blocked handle are
+ rejected with `403` (error code `2026`). Group sends that include
+ unblocked members are not restricted.
+ """
+ from .resources.blocked_handles import BlockedHandlesResource
+
+ return BlockedHandlesResource(self)
+
@cached_property
def experiences(self) -> ExperiencesResource:
"""
@@ -1561,6 +1577,20 @@ def payments(self) -> AsyncPaymentsResource:
return AsyncPaymentsResource(self)
+ @cached_property
+ def blocked_handles(self) -> AsyncBlockedHandlesResource:
+ """Block handles — phone numbers, email addresses, SMS short codes, or
+ sender IDs.
+
+ Inbound messages from a blocked handle are dropped before
+ they reach your webhooks, and direct sends to a blocked handle are
+ rejected with `403` (error code `2026`). Group sends that include
+ unblocked members are not restricted.
+ """
+ from .resources.blocked_handles import AsyncBlockedHandlesResource
+
+ return AsyncBlockedHandlesResource(self)
+
@cached_property
def experiences(self) -> AsyncExperiencesResource:
"""
@@ -2449,6 +2479,20 @@ def payments(self) -> payments.PaymentsResourceWithRawResponse:
return PaymentsResourceWithRawResponse(self._client.payments)
+ @cached_property
+ def blocked_handles(self) -> blocked_handles.BlockedHandlesResourceWithRawResponse:
+ """Block handles — phone numbers, email addresses, SMS short codes, or
+ sender IDs.
+
+ Inbound messages from a blocked handle are dropped before
+ they reach your webhooks, and direct sends to a blocked handle are
+ rejected with `403` (error code `2026`). Group sends that include
+ unblocked members are not restricted.
+ """
+ from .resources.blocked_handles import BlockedHandlesResourceWithRawResponse
+
+ return BlockedHandlesResourceWithRawResponse(self._client.blocked_handles)
+
@cached_property
def experiences(self) -> experiences.ExperiencesResourceWithRawResponse:
"""
@@ -3209,6 +3253,20 @@ def payments(self) -> payments.AsyncPaymentsResourceWithRawResponse:
return AsyncPaymentsResourceWithRawResponse(self._client.payments)
+ @cached_property
+ def blocked_handles(self) -> blocked_handles.AsyncBlockedHandlesResourceWithRawResponse:
+ """Block handles — phone numbers, email addresses, SMS short codes, or
+ sender IDs.
+
+ Inbound messages from a blocked handle are dropped before
+ they reach your webhooks, and direct sends to a blocked handle are
+ rejected with `403` (error code `2026`). Group sends that include
+ unblocked members are not restricted.
+ """
+ from .resources.blocked_handles import AsyncBlockedHandlesResourceWithRawResponse
+
+ return AsyncBlockedHandlesResourceWithRawResponse(self._client.blocked_handles)
+
@cached_property
def experiences(self) -> experiences.AsyncExperiencesResourceWithRawResponse:
"""
@@ -3969,6 +4027,20 @@ def payments(self) -> payments.PaymentsResourceWithStreamingResponse:
return PaymentsResourceWithStreamingResponse(self._client.payments)
+ @cached_property
+ def blocked_handles(self) -> blocked_handles.BlockedHandlesResourceWithStreamingResponse:
+ """Block handles — phone numbers, email addresses, SMS short codes, or
+ sender IDs.
+
+ Inbound messages from a blocked handle are dropped before
+ they reach your webhooks, and direct sends to a blocked handle are
+ rejected with `403` (error code `2026`). Group sends that include
+ unblocked members are not restricted.
+ """
+ from .resources.blocked_handles import BlockedHandlesResourceWithStreamingResponse
+
+ return BlockedHandlesResourceWithStreamingResponse(self._client.blocked_handles)
+
@cached_property
def experiences(self) -> experiences.ExperiencesResourceWithStreamingResponse:
"""
@@ -4729,6 +4801,20 @@ def payments(self) -> payments.AsyncPaymentsResourceWithStreamingResponse:
return AsyncPaymentsResourceWithStreamingResponse(self._client.payments)
+ @cached_property
+ def blocked_handles(self) -> blocked_handles.AsyncBlockedHandlesResourceWithStreamingResponse:
+ """Block handles — phone numbers, email addresses, SMS short codes, or
+ sender IDs.
+
+ Inbound messages from a blocked handle are dropped before
+ they reach your webhooks, and direct sends to a blocked handle are
+ rejected with `403` (error code `2026`). Group sends that include
+ unblocked members are not restricted.
+ """
+ from .resources.blocked_handles import AsyncBlockedHandlesResourceWithStreamingResponse
+
+ return AsyncBlockedHandlesResourceWithStreamingResponse(self._client.blocked_handles)
+
@cached_property
def experiences(self) -> experiences.AsyncExperiencesResourceWithStreamingResponse:
"""
diff --git a/src/linq/resources/__init__.py b/src/linq/resources/__init__.py
index 018caad..ae80049 100644
--- a/src/linq/resources/__init__.py
+++ b/src/linq/resources/__init__.py
@@ -81,6 +81,14 @@
WebhookEventsResourceWithStreamingResponse,
AsyncWebhookEventsResourceWithStreamingResponse,
)
+from .blocked_handles import (
+ BlockedHandlesResource,
+ AsyncBlockedHandlesResource,
+ BlockedHandlesResourceWithRawResponse,
+ AsyncBlockedHandlesResourceWithRawResponse,
+ BlockedHandlesResourceWithStreamingResponse,
+ AsyncBlockedHandlesResourceWithStreamingResponse,
+)
from .payment_handles import (
PaymentHandlesResource,
AsyncPaymentHandlesResource,
@@ -183,6 +191,12 @@
"AsyncPaymentsResourceWithRawResponse",
"PaymentsResourceWithStreamingResponse",
"AsyncPaymentsResourceWithStreamingResponse",
+ "BlockedHandlesResource",
+ "AsyncBlockedHandlesResource",
+ "BlockedHandlesResourceWithRawResponse",
+ "AsyncBlockedHandlesResourceWithRawResponse",
+ "BlockedHandlesResourceWithStreamingResponse",
+ "AsyncBlockedHandlesResourceWithStreamingResponse",
"ExperiencesResource",
"AsyncExperiencesResource",
"ExperiencesResourceWithRawResponse",
diff --git a/src/linq/resources/blocked_handles.py b/src/linq/resources/blocked_handles.py
new file mode 100644
index 0000000..d8e74be
--- /dev/null
+++ b/src/linq/resources/blocked_handles.py
@@ -0,0 +1,368 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..types import blocked_handle_block_params, blocked_handle_unblock_params
+from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
+from .._utils import 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.blocked_handle_list_response import BlockedHandleListResponse
+from ..types.blocked_handle_block_response import BlockedHandleBlockResponse
+
+__all__ = ["BlockedHandlesResource", "AsyncBlockedHandlesResource"]
+
+
+class BlockedHandlesResource(SyncAPIResource):
+ """Block handles — phone numbers, email addresses, SMS short codes, or
+ sender IDs.
+
+ Inbound messages from a blocked handle are dropped before
+ they reach your webhooks, and direct sends to a blocked handle are
+ rejected with `403` (error code `2026`). Group sends that include
+ unblocked members are not restricted.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> BlockedHandlesResourceWithRawResponse:
+ """
+ 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/linq-team/linq-python#accessing-raw-response-data-eg-headers
+ """
+ return BlockedHandlesResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> BlockedHandlesResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/linq-team/linq-python#with_streaming_response
+ """
+ return BlockedHandlesResourceWithStreamingResponse(self)
+
+ def list(
+ self,
+ *,
+ # 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,
+ ) -> BlockedHandleListResponse:
+ """Returns all handles you have blocked.
+
+ Inbound messages from a blocked handle are
+ dropped and produce no webhooks, and direct sends to a blocked handle are
+ rejected with `403` (error code `2026`). Group sends that include unblocked
+ members are not restricted.
+ """
+ return self._get(
+ "/v3/blocked_handles",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=BlockedHandleListResponse,
+ )
+
+ def block(
+ self,
+ *,
+ handle: str,
+ reason: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> BlockedHandleBlockResponse:
+ """
+ Blocks a handle — an E.164 phone number, an email address (iMessage sender), an
+ SMS short code (e.g. `262966`), or an alphanumeric sender ID. Inbound messages
+ from it are dropped and produce no webhooks, and direct sends to it are rejected
+ with `403` (error code `2026`); group sends that include unblocked members are
+ not restricted. Blocking is idempotent — re-blocking an already blocked handle
+ returns the existing entry.
+
+ Args:
+ handle: The handle to block: an E.164 phone number, an email address, an SMS short code
+ (3-8 digits), or an alphanumeric sender ID.
+
+ reason: Optional free-text note on why the handle was blocked
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/v3/blocked_handles",
+ body=maybe_transform(
+ {
+ "handle": handle,
+ "reason": reason,
+ },
+ blocked_handle_block_params.BlockedHandleBlockParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=BlockedHandleBlockResponse,
+ )
+
+ def unblock(
+ self,
+ *,
+ handle: 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:
+ """Removes a handle from your blocklist.
+
+ Inbound messages from it will be delivered
+ again and sends to it are allowed again. The handle goes in the request body,
+ mirroring block — no URL encoding needed.
+
+ Args:
+ handle: The handle to unblock
+
+ 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
+ """
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._delete(
+ "/v3/blocked_handles",
+ body=maybe_transform({"handle": handle}, blocked_handle_unblock_params.BlockedHandleUnblockParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+
+class AsyncBlockedHandlesResource(AsyncAPIResource):
+ """Block handles — phone numbers, email addresses, SMS short codes, or
+ sender IDs.
+
+ Inbound messages from a blocked handle are dropped before
+ they reach your webhooks, and direct sends to a blocked handle are
+ rejected with `403` (error code `2026`). Group sends that include
+ unblocked members are not restricted.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> AsyncBlockedHandlesResourceWithRawResponse:
+ """
+ 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/linq-team/linq-python#accessing-raw-response-data-eg-headers
+ """
+ return AsyncBlockedHandlesResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncBlockedHandlesResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/linq-team/linq-python#with_streaming_response
+ """
+ return AsyncBlockedHandlesResourceWithStreamingResponse(self)
+
+ async def list(
+ self,
+ *,
+ # 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,
+ ) -> BlockedHandleListResponse:
+ """Returns all handles you have blocked.
+
+ Inbound messages from a blocked handle are
+ dropped and produce no webhooks, and direct sends to a blocked handle are
+ rejected with `403` (error code `2026`). Group sends that include unblocked
+ members are not restricted.
+ """
+ return await self._get(
+ "/v3/blocked_handles",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=BlockedHandleListResponse,
+ )
+
+ async def block(
+ self,
+ *,
+ handle: str,
+ reason: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> BlockedHandleBlockResponse:
+ """
+ Blocks a handle — an E.164 phone number, an email address (iMessage sender), an
+ SMS short code (e.g. `262966`), or an alphanumeric sender ID. Inbound messages
+ from it are dropped and produce no webhooks, and direct sends to it are rejected
+ with `403` (error code `2026`); group sends that include unblocked members are
+ not restricted. Blocking is idempotent — re-blocking an already blocked handle
+ returns the existing entry.
+
+ Args:
+ handle: The handle to block: an E.164 phone number, an email address, an SMS short code
+ (3-8 digits), or an alphanumeric sender ID.
+
+ reason: Optional free-text note on why the handle was blocked
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/v3/blocked_handles",
+ body=await async_maybe_transform(
+ {
+ "handle": handle,
+ "reason": reason,
+ },
+ blocked_handle_block_params.BlockedHandleBlockParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=BlockedHandleBlockResponse,
+ )
+
+ async def unblock(
+ self,
+ *,
+ handle: 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:
+ """Removes a handle from your blocklist.
+
+ Inbound messages from it will be delivered
+ again and sends to it are allowed again. The handle goes in the request body,
+ mirroring block — no URL encoding needed.
+
+ Args:
+ handle: The handle to unblock
+
+ 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
+ """
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._delete(
+ "/v3/blocked_handles",
+ body=await async_maybe_transform(
+ {"handle": handle}, blocked_handle_unblock_params.BlockedHandleUnblockParams
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
+
+class BlockedHandlesResourceWithRawResponse:
+ def __init__(self, blocked_handles: BlockedHandlesResource) -> None:
+ self._blocked_handles = blocked_handles
+
+ self.list = to_raw_response_wrapper(
+ blocked_handles.list,
+ )
+ self.block = to_raw_response_wrapper(
+ blocked_handles.block,
+ )
+ self.unblock = to_raw_response_wrapper(
+ blocked_handles.unblock,
+ )
+
+
+class AsyncBlockedHandlesResourceWithRawResponse:
+ def __init__(self, blocked_handles: AsyncBlockedHandlesResource) -> None:
+ self._blocked_handles = blocked_handles
+
+ self.list = async_to_raw_response_wrapper(
+ blocked_handles.list,
+ )
+ self.block = async_to_raw_response_wrapper(
+ blocked_handles.block,
+ )
+ self.unblock = async_to_raw_response_wrapper(
+ blocked_handles.unblock,
+ )
+
+
+class BlockedHandlesResourceWithStreamingResponse:
+ def __init__(self, blocked_handles: BlockedHandlesResource) -> None:
+ self._blocked_handles = blocked_handles
+
+ self.list = to_streamed_response_wrapper(
+ blocked_handles.list,
+ )
+ self.block = to_streamed_response_wrapper(
+ blocked_handles.block,
+ )
+ self.unblock = to_streamed_response_wrapper(
+ blocked_handles.unblock,
+ )
+
+
+class AsyncBlockedHandlesResourceWithStreamingResponse:
+ def __init__(self, blocked_handles: AsyncBlockedHandlesResource) -> None:
+ self._blocked_handles = blocked_handles
+
+ self.list = async_to_streamed_response_wrapper(
+ blocked_handles.list,
+ )
+ self.block = async_to_streamed_response_wrapper(
+ blocked_handles.block,
+ )
+ self.unblock = async_to_streamed_response_wrapper(
+ blocked_handles.unblock,
+ )
diff --git a/src/linq/resources/messages/messages.py b/src/linq/resources/messages/messages.py
index 601bd85..8fa2309 100644
--- a/src/linq/resources/messages/messages.py
+++ b/src/linq/resources/messages/messages.py
@@ -580,6 +580,7 @@ def update_app_card(
message_id: str,
*,
layout: message_update_app_card_params.Layout,
+ action: message_update_app_card_params.Action | Omit = omit,
fallback_text: str | Omit = omit,
interactive: bool | Omit = omit,
url: str | Omit = omit,
@@ -630,6 +631,13 @@ def update_app_card(
an image there is nothing to overlay — so setting either without `image_url` is
rejected.
+ action: Invokes an action on an experience — a third party that renders inside Linq's
+ iMessage app. Linq resolves the recipient's connection, mints any session the
+ action needs, composes the card and sends it; none of that is visible to you.
+
+ Call `GET /v3/experiences/{experience}` for the actions you may invoke and the
+ fields each accepts.
+
fallback_text: Text shown on surfaces that cannot render the card (notifications, lock screen).
Defaults to the caption when omitted.
@@ -645,6 +653,8 @@ def update_app_card(
url: URL the recipient's app opens when they tap the updated card.
+ Mutually exclusive with `action` and `raw_payload_data`.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -660,6 +670,7 @@ def update_app_card(
body=maybe_transform(
{
"layout": layout,
+ "action": action,
"fallback_text": fallback_text,
"interactive": interactive,
"url": url,
@@ -1210,6 +1221,7 @@ async def update_app_card(
message_id: str,
*,
layout: message_update_app_card_params.Layout,
+ action: message_update_app_card_params.Action | Omit = omit,
fallback_text: str | Omit = omit,
interactive: bool | Omit = omit,
url: str | Omit = omit,
@@ -1260,6 +1272,13 @@ async def update_app_card(
an image there is nothing to overlay — so setting either without `image_url` is
rejected.
+ action: Invokes an action on an experience — a third party that renders inside Linq's
+ iMessage app. Linq resolves the recipient's connection, mints any session the
+ action needs, composes the card and sends it; none of that is visible to you.
+
+ Call `GET /v3/experiences/{experience}` for the actions you may invoke and the
+ fields each accepts.
+
fallback_text: Text shown on surfaces that cannot render the card (notifications, lock screen).
Defaults to the caption when omitted.
@@ -1275,6 +1294,8 @@ async def update_app_card(
url: URL the recipient's app opens when they tap the updated card.
+ Mutually exclusive with `action` and `raw_payload_data`.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -1290,6 +1311,7 @@ async def update_app_card(
body=await async_maybe_transform(
{
"layout": layout,
+ "action": action,
"fallback_text": fallback_text,
"interactive": interactive,
"url": url,
diff --git a/src/linq/types/__init__.py b/src/linq/types/__init__.py
index e5b78f2..57e5153 100644
--- a/src/linq/types/__init__.py
+++ b/src/linq/types/__init__.py
@@ -29,6 +29,7 @@
from .chat_update_params import ChatUpdateParams as ChatUpdateParams
from .webhook_event_type import WebhookEventType as WebhookEventType
from .reaction_event_base import ReactionEventBase as ReactionEventBase
+from .blocked_handle_entry import BlockedHandleEntry as BlockedHandleEntry
from .chat_create_response import ChatCreateResponse as ChatCreateResponse
from .chat_update_response import ChatUpdateResponse as ChatUpdateResponse
from .message_effect_param import MessageEffectParam as MessageEffectParam
@@ -58,12 +59,14 @@
from .phone_number_list_response import PhoneNumberListResponse as PhoneNumberListResponse
from .phone_number_update_params import PhoneNumberUpdateParams as PhoneNumberUpdateParams
from .schemas_text_part_response import SchemasTextPartResponse as SchemasTextPartResponse
+from .blocked_handle_block_params import BlockedHandleBlockParams as BlockedHandleBlockParams
from .capability_check_RCS_params import CapabilityCheckRCSParams as CapabilityCheckRCSParams
from .message_add_reaction_params import MessageAddReactionParams as MessageAddReactionParams
from .payment_request_list_params import PaymentRequestListParams as PaymentRequestListParams
from .schemas_media_part_response import SchemasMediaPartResponse as SchemasMediaPartResponse
from .webhook_event_list_response import WebhookEventListResponse as WebhookEventListResponse
from .attachment_retrieve_response import AttachmentRetrieveResponse as AttachmentRetrieveResponse
+from .blocked_handle_list_response import BlockedHandleListResponse as BlockedHandleListResponse
from .chat_send_voicememo_response import ChatSendVoicememoResponse as ChatSendVoicememoResponse
from .contact_card_retrieve_params import ContactCardRetrieveParams as ContactCardRetrieveParams
from .experience_retrieve_response import ExperienceRetrieveResponse as ExperienceRetrieveResponse
@@ -73,6 +76,8 @@
from .payment_handle_verify_params import PaymentHandleVerifyParams as PaymentHandleVerifyParams
from .phone_number_update_response import PhoneNumberUpdateResponse as PhoneNumberUpdateResponse
from .reaction_added_webhook_event import ReactionAddedWebhookEvent as ReactionAddedWebhookEvent
+from .blocked_handle_block_response import BlockedHandleBlockResponse as BlockedHandleBlockResponse
+from .blocked_handle_unblock_params import BlockedHandleUnblockParams as BlockedHandleUnblockParams
from .message_add_reaction_response import MessageAddReactionResponse as MessageAddReactionResponse
from .payment_request_create_params import PaymentRequestCreateParams as PaymentRequestCreateParams
from .payment_request_list_response import PaymentRequestListResponse as PaymentRequestListResponse
diff --git a/src/linq/types/attachment_create_response.py b/src/linq/types/attachment_create_response.py
index dbe180b..c6dd48e 100644
--- a/src/linq/types/attachment_create_response.py
+++ b/src/linq/types/attachment_create_response.py
@@ -37,5 +37,7 @@ class AttachmentCreateResponse(BaseModel):
"""Presigned URL for uploading the file.
PUT the raw binary file content to this URL with the `required_headers`. Do not
- JSON-encode or multipart-wrap the body. Expires after 15 minutes.
+ JSON-encode or multipart-wrap the body. Expires after 15 minutes. Treat the URL
+ as opaque — the hostname depends on partner configuration and is the same across
+ sandbox and production.
"""
diff --git a/src/linq/types/blocked_handle_block_params.py b/src/linq/types/blocked_handle_block_params.py
new file mode 100644
index 0000000..a77d96c
--- /dev/null
+++ b/src/linq/types/blocked_handle_block_params.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["BlockedHandleBlockParams"]
+
+
+class BlockedHandleBlockParams(TypedDict, total=False):
+ handle: Required[str]
+ """
+ The handle to block: an E.164 phone number, an email address, an SMS short code
+ (3-8 digits), or an alphanumeric sender ID.
+ """
+
+ reason: str
+ """Optional free-text note on why the handle was blocked"""
diff --git a/src/linq/types/blocked_handle_block_response.py b/src/linq/types/blocked_handle_block_response.py
new file mode 100644
index 0000000..e963152
--- /dev/null
+++ b/src/linq/types/blocked_handle_block_response.py
@@ -0,0 +1,10 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .._models import BaseModel
+from .blocked_handle_entry import BlockedHandleEntry
+
+__all__ = ["BlockedHandleBlockResponse"]
+
+
+class BlockedHandleBlockResponse(BaseModel):
+ blocked_handle: BlockedHandleEntry
diff --git a/src/linq/types/blocked_handle_entry.py b/src/linq/types/blocked_handle_entry.py
new file mode 100644
index 0000000..afe182b
--- /dev/null
+++ b/src/linq/types/blocked_handle_entry.py
@@ -0,0 +1,22 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from datetime import datetime
+
+from .._models import BaseModel
+
+__all__ = ["BlockedHandleEntry"]
+
+
+class BlockedHandleEntry(BaseModel):
+ blocked_at: datetime
+ """When the handle was blocked"""
+
+ handle: str
+ """
+ The blocked handle, normalized (E.164 phone, lowercased email, short code, or
+ sender ID)
+ """
+
+ reason: Optional[str] = None
+ """Optional note recorded when the handle was blocked"""
diff --git a/src/linq/types/blocked_handle_list_response.py b/src/linq/types/blocked_handle_list_response.py
new file mode 100644
index 0000000..2d1d23e
--- /dev/null
+++ b/src/linq/types/blocked_handle_list_response.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+
+from .._models import BaseModel
+from .blocked_handle_entry import BlockedHandleEntry
+
+__all__ = ["BlockedHandleListResponse"]
+
+
+class BlockedHandleListResponse(BaseModel):
+ blocked_handles: List[BlockedHandleEntry]
+ """All handles blocked by the partner, newest first"""
diff --git a/src/linq/types/blocked_handle_unblock_params.py b/src/linq/types/blocked_handle_unblock_params.py
new file mode 100644
index 0000000..b27de4a
--- /dev/null
+++ b/src/linq/types/blocked_handle_unblock_params.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["BlockedHandleUnblockParams"]
+
+
+class BlockedHandleUnblockParams(TypedDict, total=False):
+ handle: Required[str]
+ """The handle to unblock"""
diff --git a/src/linq/types/chat.py b/src/linq/types/chat.py
index f8f5d1f..01a8246 100644
--- a/src/linq/types/chat.py
+++ b/src/linq/types/chat.py
@@ -30,14 +30,14 @@ class HealthStatus(BaseModel):
See the [Chat Health guide](/guides/chats/chat-health) for what each value means
and how to react. `doc_url` deep-links to the relevant section.
- `OPTED_OUT` is terminal — the recipient sent `STOP`, `UNSUBSCRIBE`, `OPTOUT`,
- `CANCEL`, `END`, or `QUIT`. The keyword must be the whole trimmed message, never
- part of a longer one: `STOP` counts, `please stop` does not. Most keywords must
- match exactly, including case. `OPT OUT` is the exception — it matches in any
- casing, with or without the space or a hyphen, so `opt out`, `Opt-Out` and
- `optout` all count. It clears if they later send `START`, `OPTIN`, or `UNSTOP`,
- or if they keep replying on the chat — sustained two-way conversation is treated
- as a sign the stop keyword was a false positive.
+ `OPTED_OUT` — the recipient sent `STOP`, `UNSUBSCRIBE`, `OPTOUT`, `CANCEL`,
+ `END`, or `QUIT`. The keyword must be the whole trimmed message, never part of a
+ longer one: `STOP` counts, `please stop` does not. Most keywords must match
+ exactly, including case. `OPT OUT` is the exception — it matches in any casing,
+ with or without the space or a hyphen, so `opt out`, `Opt-Out` and `optout` all
+ count. It clears as soon as they reply again: any later message from them that
+ is not itself an opt-out keyword opts them back in immediately — a reply in any
+ conversation with you counts, the same way the block does.
Linq enforces this: while a recipient is opted out, every send to them is
rejected with `403` (error code `2024`) before the message is queued, across
diff --git a/src/linq/types/chat_create_response.py b/src/linq/types/chat_create_response.py
index e1c34ed..fd09d4e 100644
--- a/src/linq/types/chat_create_response.py
+++ b/src/linq/types/chat_create_response.py
@@ -31,14 +31,14 @@ class ChatHealthStatus(BaseModel):
See the [Chat Health guide](/guides/chats/chat-health) for what each value means
and how to react. `doc_url` deep-links to the relevant section.
- `OPTED_OUT` is terminal — the recipient sent `STOP`, `UNSUBSCRIBE`, `OPTOUT`,
- `CANCEL`, `END`, or `QUIT`. The keyword must be the whole trimmed message, never
- part of a longer one: `STOP` counts, `please stop` does not. Most keywords must
- match exactly, including case. `OPT OUT` is the exception — it matches in any
- casing, with or without the space or a hyphen, so `opt out`, `Opt-Out` and
- `optout` all count. It clears if they later send `START`, `OPTIN`, or `UNSTOP`,
- or if they keep replying on the chat — sustained two-way conversation is treated
- as a sign the stop keyword was a false positive.
+ `OPTED_OUT` — the recipient sent `STOP`, `UNSUBSCRIBE`, `OPTOUT`, `CANCEL`,
+ `END`, or `QUIT`. The keyword must be the whole trimmed message, never part of a
+ longer one: `STOP` counts, `please stop` does not. Most keywords must match
+ exactly, including case. `OPT OUT` is the exception — it matches in any casing,
+ with or without the space or a hyphen, so `opt out`, `Opt-Out` and `optout` all
+ count. It clears as soon as they reply again: any later message from them that
+ is not itself an opt-out keyword opts them back in immediately — a reply in any
+ conversation with you counts, the same way the block does.
Linq enforces this: while a recipient is opted out, every send to them is
rejected with `403` (error code `2024`) before the message is queued, across
diff --git a/src/linq/types/chat_created_webhook_event.py b/src/linq/types/chat_created_webhook_event.py
index 0522edf..4f7c726 100644
--- a/src/linq/types/chat_created_webhook_event.py
+++ b/src/linq/types/chat_created_webhook_event.py
@@ -31,14 +31,14 @@ class DataHealthStatus(BaseModel):
See the [Chat Health guide](/guides/chats/chat-health) for what each value means
and how to react. `doc_url` deep-links to the relevant section.
- `OPTED_OUT` is terminal — the recipient sent `STOP`, `UNSUBSCRIBE`, `OPTOUT`,
- `CANCEL`, `END`, or `QUIT`. The keyword must be the whole trimmed message, never
- part of a longer one: `STOP` counts, `please stop` does not. Most keywords must
- match exactly, including case. `OPT OUT` is the exception — it matches in any
- casing, with or without the space or a hyphen, so `opt out`, `Opt-Out` and
- `optout` all count. It clears if they later send `START`, `OPTIN`, or `UNSTOP`,
- or if they keep replying on the chat — sustained two-way conversation is treated
- as a sign the stop keyword was a false positive.
+ `OPTED_OUT` — the recipient sent `STOP`, `UNSUBSCRIBE`, `OPTOUT`, `CANCEL`,
+ `END`, or `QUIT`. The keyword must be the whole trimmed message, never part of a
+ longer one: `STOP` counts, `please stop` does not. Most keywords must match
+ exactly, including case. `OPT OUT` is the exception — it matches in any casing,
+ with or without the space or a hyphen, so `opt out`, `Opt-Out` and `optout` all
+ count. It clears as soon as they reply again: any later message from them that
+ is not itself an opt-out keyword opts them back in immediately — a reply in any
+ conversation with you counts, the same way the block does.
Linq enforces this: while a recipient is opted out, every send to them is
rejected with `403` (error code `2024`) before the message is queued, across
diff --git a/src/linq/types/chat_group_icon_update_failed_webhook_event.py b/src/linq/types/chat_group_icon_update_failed_webhook_event.py
index b15a421..e159d6b 100644
--- a/src/linq/types/chat_group_icon_update_failed_webhook_event.py
+++ b/src/linq/types/chat_group_icon_update_failed_webhook_event.py
@@ -18,7 +18,13 @@ class Data(BaseModel):
"""Chat identifier (UUID) of the group chat"""
error_code: int
- """Error codes in webhook failure events (3007, 4001, 4005)."""
+ """Error codes in webhook failure events.
+
+ The possible set varies by event: message.failed can carry 3007, 4001, 4002,
+ 4005, 4006, 4007, or 4008; the group update failure events
+ (chat.group_name_update_failed, chat.group_icon_update_failed) carry 3007
+ or 4001.
+ """
failed_at: datetime
"""When the failure was detected"""
diff --git a/src/linq/types/chat_group_name_update_failed_webhook_event.py b/src/linq/types/chat_group_name_update_failed_webhook_event.py
index bfedbf6..cbe5927 100644
--- a/src/linq/types/chat_group_name_update_failed_webhook_event.py
+++ b/src/linq/types/chat_group_name_update_failed_webhook_event.py
@@ -18,7 +18,13 @@ class Data(BaseModel):
"""Chat identifier (UUID) of the group chat"""
error_code: int
- """Error codes in webhook failure events (3007, 4001, 4005)."""
+ """Error codes in webhook failure events.
+
+ The possible set varies by event: message.failed can carry 3007, 4001, 4002,
+ 4005, 4006, 4007, or 4008; the group update failure events
+ (chat.group_name_update_failed, chat.group_icon_update_failed) carry 3007
+ or 4001.
+ """
failed_at: datetime
"""When the failure was detected"""
diff --git a/src/linq/types/message_edited_webhook_event.py b/src/linq/types/message_edited_webhook_event.py
index 4b52d49..60a08a0 100644
--- a/src/linq/types/message_edited_webhook_event.py
+++ b/src/linq/types/message_edited_webhook_event.py
@@ -29,14 +29,14 @@ class DataChatHealthStatus(BaseModel):
See the [Chat Health guide](/guides/chats/chat-health) for what each value means
and how to react. `doc_url` deep-links to the relevant section.
- `OPTED_OUT` is terminal — the recipient sent `STOP`, `UNSUBSCRIBE`, `OPTOUT`,
- `CANCEL`, `END`, or `QUIT`. The keyword must be the whole trimmed message, never
- part of a longer one: `STOP` counts, `please stop` does not. Most keywords must
- match exactly, including case. `OPT OUT` is the exception — it matches in any
- casing, with or without the space or a hyphen, so `opt out`, `Opt-Out` and
- `optout` all count. It clears if they later send `START`, `OPTIN`, or `UNSTOP`,
- or if they keep replying on the chat — sustained two-way conversation is treated
- as a sign the stop keyword was a false positive.
+ `OPTED_OUT` — the recipient sent `STOP`, `UNSUBSCRIBE`, `OPTOUT`, `CANCEL`,
+ `END`, or `QUIT`. The keyword must be the whole trimmed message, never part of a
+ longer one: `STOP` counts, `please stop` does not. Most keywords must match
+ exactly, including case. `OPT OUT` is the exception — it matches in any casing,
+ with or without the space or a hyphen, so `opt out`, `Opt-Out` and `optout` all
+ count. It clears as soon as they reply again: any later message from them that
+ is not itself an opt-out keyword opts them back in immediately — a reply in any
+ conversation with you counts, the same way the block does.
Linq enforces this: while a recipient is opted out, every send to them is
rejected with `403` (error code `2024`) before the message is queued, across
diff --git a/src/linq/types/message_event_v2.py b/src/linq/types/message_event_v2.py
index fa20f95..8be2236 100644
--- a/src/linq/types/message_event_v2.py
+++ b/src/linq/types/message_event_v2.py
@@ -44,14 +44,14 @@ class ChatHealthStatus(BaseModel):
See the [Chat Health guide](/guides/chats/chat-health) for what each value means
and how to react. `doc_url` deep-links to the relevant section.
- `OPTED_OUT` is terminal — the recipient sent `STOP`, `UNSUBSCRIBE`, `OPTOUT`,
- `CANCEL`, `END`, or `QUIT`. The keyword must be the whole trimmed message, never
- part of a longer one: `STOP` counts, `please stop` does not. Most keywords must
- match exactly, including case. `OPT OUT` is the exception — it matches in any
- casing, with or without the space or a hyphen, so `opt out`, `Opt-Out` and
- `optout` all count. It clears if they later send `START`, `OPTIN`, or `UNSTOP`,
- or if they keep replying on the chat — sustained two-way conversation is treated
- as a sign the stop keyword was a false positive.
+ `OPTED_OUT` — the recipient sent `STOP`, `UNSUBSCRIBE`, `OPTOUT`, `CANCEL`,
+ `END`, or `QUIT`. The keyword must be the whole trimmed message, never part of a
+ longer one: `STOP` counts, `please stop` does not. Most keywords must match
+ exactly, including case. `OPT OUT` is the exception — it matches in any casing,
+ with or without the space or a hyphen, so `opt out`, `Opt-Out` and `optout` all
+ count. It clears as soon as they reply again: any later message from them that
+ is not itself an opt-out keyword opts them back in immediately — a reply in any
+ conversation with you counts, the same way the block does.
Linq enforces this: while a recipient is opted out, every send to them is
rejected with `403` (error code `2024`) before the message is queued, across
diff --git a/src/linq/types/message_failed_webhook_event.py b/src/linq/types/message_failed_webhook_event.py
index 1989409..d576b79 100644
--- a/src/linq/types/message_failed_webhook_event.py
+++ b/src/linq/types/message_failed_webhook_event.py
@@ -2,9 +2,11 @@
from typing import Optional
from datetime import datetime
+from typing_extensions import Literal
from .._models import BaseModel
from .webhook_event_type import WebhookEventType
+from .shared.service_type import ServiceType
__all__ = ["MessageFailedWebhookEvent", "Data"]
@@ -18,7 +20,13 @@ class Data(BaseModel):
"""
code: int
- """Error codes in webhook failure events (3007, 4001, 4005)."""
+ """Error codes in webhook failure events.
+
+ The possible set varies by event: message.failed can carry 3007, 4001, 4002,
+ 4005, 4006, 4007, or 4008; the group update failure events
+ (chat.group_name_update_failed, chat.group_icon_update_failed) carry 3007
+ or 4001.
+ """
failed_at: datetime
"""When the failure was detected"""
@@ -26,12 +34,28 @@ class Data(BaseModel):
chat_id: Optional[str] = None
"""Chat identifier (UUID)"""
+ detail_code: Optional[int] = None
+ """
+ Opaque diagnostic code identifying the specific failure class within `code`.
+ Values are not enumerated and may change without notice — log it and include it
+ in support requests, but do not branch on it.
+ """
+
message_id: Optional[str] = None
"""Message identifier (UUID)"""
+ preferred_service: Optional[Literal["iMessage", "SMS", "RCS", "auto"]] = None
+ """Preferred messaging service type.
+
+ Includes "auto" for default fallback behavior.
+ """
+
reason: Optional[str] = None
"""Human-readable description of the failure"""
+ service: Optional[ServiceType] = None
+ """Messaging service type"""
+
class MessageFailedWebhookEvent(BaseModel):
"""Complete webhook payload for message.failed events"""
diff --git a/src/linq/types/message_update_app_card_params.py b/src/linq/types/message_update_app_card_params.py
index d01337d..32bd023 100644
--- a/src/linq/types/message_update_app_card_params.py
+++ b/src/linq/types/message_update_app_card_params.py
@@ -2,9 +2,10 @@
from __future__ import annotations
+from typing import Dict
from typing_extensions import Required, TypedDict
-__all__ = ["MessageUpdateAppCardParams", "Layout"]
+__all__ = ["MessageUpdateAppCardParams", "Layout", "Action"]
class MessageUpdateAppCardParams(TypedDict, total=False):
@@ -27,6 +28,16 @@ class MessageUpdateAppCardParams(TypedDict, total=False):
rejected.
"""
+ action: Action
+ """
+ Invokes an action on an experience — a third party that renders inside Linq's
+ iMessage app. Linq resolves the recipient's connection, mints any session the
+ action needs, composes the card and sends it; none of that is visible to you.
+
+ Call `GET /v3/experiences/{experience}` for the actions you may invoke and the
+ fields each accepts.
+ """
+
fallback_text: str
"""Text shown on surfaces that cannot render the card (notifications, lock screen).
@@ -47,7 +58,10 @@ class MessageUpdateAppCardParams(TypedDict, total=False):
"""
url: str
- """URL the recipient's app opens when they tap the updated card."""
+ """URL the recipient's app opens when they tap the updated card.
+
+ Mutually exclusive with `action` and `raw_payload_data`.
+ """
class Layout(TypedDict, total=False):
@@ -100,3 +114,30 @@ class Layout(TypedDict, total=False):
trailing_subcaption: str
"""Label shown below `trailing_caption`, on the right."""
+
+
+class Action(TypedDict, total=False):
+ """
+ Invokes an action on an experience — a third party that renders inside
+ Linq's iMessage app. Linq resolves the recipient's connection, mints any
+ session the action needs, composes the card and sends it; none of that
+ is visible to you.
+
+ Call `GET /v3/experiences/{experience}` for the actions you may invoke
+ and the fields each accepts.
+ """
+
+ action: Required[str]
+ """Which of its actions, e.g. `attach_card`."""
+
+ experience: Required[str]
+ """The experience to invoke, e.g. `agentcard`."""
+
+ params: Dict[str, object]
+ """Values for the fields this action exposes.
+
+ Keys are exactly the field names listed for the action — no mapping, no nesting.
+
+ Display copy only, except a `url`-type field — that value sets the destination,
+ and must be an absolute `https` URL.
+ """
diff --git a/tests/api_resources/test_blocked_handles.py b/tests/api_resources/test_blocked_handles.py
new file mode 100644
index 0000000..4a1a617
--- /dev/null
+++ b/tests/api_resources/test_blocked_handles.py
@@ -0,0 +1,237 @@
+# 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 linq import LinqAPIV3, AsyncLinqAPIV3
+from linq.types import (
+ BlockedHandleListResponse,
+ BlockedHandleBlockResponse,
+)
+from tests.utils import assert_matches_type
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestBlockedHandles:
+ 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(self, client: LinqAPIV3) -> None:
+ blocked_handle = client.blocked_handles.list()
+ assert_matches_type(BlockedHandleListResponse, blocked_handle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: LinqAPIV3) -> None:
+ response = client.blocked_handles.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ blocked_handle = response.parse()
+ assert_matches_type(BlockedHandleListResponse, blocked_handle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: LinqAPIV3) -> None:
+ with client.blocked_handles.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ blocked_handle = response.parse()
+ assert_matches_type(BlockedHandleListResponse, blocked_handle, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_block(self, client: LinqAPIV3) -> None:
+ blocked_handle = client.blocked_handles.block(
+ handle="+12025551234",
+ )
+ assert_matches_type(BlockedHandleBlockResponse, blocked_handle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_block_with_all_params(self, client: LinqAPIV3) -> None:
+ blocked_handle = client.blocked_handles.block(
+ handle="+12025551234",
+ reason="spam",
+ )
+ assert_matches_type(BlockedHandleBlockResponse, blocked_handle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_block(self, client: LinqAPIV3) -> None:
+ response = client.blocked_handles.with_raw_response.block(
+ handle="+12025551234",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ blocked_handle = response.parse()
+ assert_matches_type(BlockedHandleBlockResponse, blocked_handle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_block(self, client: LinqAPIV3) -> None:
+ with client.blocked_handles.with_streaming_response.block(
+ handle="+12025551234",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ blocked_handle = response.parse()
+ assert_matches_type(BlockedHandleBlockResponse, blocked_handle, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_unblock(self, client: LinqAPIV3) -> None:
+ blocked_handle = client.blocked_handles.unblock(
+ handle="+12025551234",
+ )
+ assert blocked_handle is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_unblock(self, client: LinqAPIV3) -> None:
+ response = client.blocked_handles.with_raw_response.unblock(
+ handle="+12025551234",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ blocked_handle = response.parse()
+ assert blocked_handle is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_unblock(self, client: LinqAPIV3) -> None:
+ with client.blocked_handles.with_streaming_response.unblock(
+ handle="+12025551234",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ blocked_handle = response.parse()
+ assert blocked_handle is None
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncBlockedHandles:
+ 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(self, async_client: AsyncLinqAPIV3) -> None:
+ blocked_handle = await async_client.blocked_handles.list()
+ assert_matches_type(BlockedHandleListResponse, blocked_handle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncLinqAPIV3) -> None:
+ response = await async_client.blocked_handles.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ blocked_handle = await response.parse()
+ assert_matches_type(BlockedHandleListResponse, blocked_handle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncLinqAPIV3) -> None:
+ async with async_client.blocked_handles.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ blocked_handle = await response.parse()
+ assert_matches_type(BlockedHandleListResponse, blocked_handle, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_block(self, async_client: AsyncLinqAPIV3) -> None:
+ blocked_handle = await async_client.blocked_handles.block(
+ handle="+12025551234",
+ )
+ assert_matches_type(BlockedHandleBlockResponse, blocked_handle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_block_with_all_params(self, async_client: AsyncLinqAPIV3) -> None:
+ blocked_handle = await async_client.blocked_handles.block(
+ handle="+12025551234",
+ reason="spam",
+ )
+ assert_matches_type(BlockedHandleBlockResponse, blocked_handle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_block(self, async_client: AsyncLinqAPIV3) -> None:
+ response = await async_client.blocked_handles.with_raw_response.block(
+ handle="+12025551234",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ blocked_handle = await response.parse()
+ assert_matches_type(BlockedHandleBlockResponse, blocked_handle, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_block(self, async_client: AsyncLinqAPIV3) -> None:
+ async with async_client.blocked_handles.with_streaming_response.block(
+ handle="+12025551234",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ blocked_handle = await response.parse()
+ assert_matches_type(BlockedHandleBlockResponse, blocked_handle, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_unblock(self, async_client: AsyncLinqAPIV3) -> None:
+ blocked_handle = await async_client.blocked_handles.unblock(
+ handle="+12025551234",
+ )
+ assert blocked_handle is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_unblock(self, async_client: AsyncLinqAPIV3) -> None:
+ response = await async_client.blocked_handles.with_raw_response.unblock(
+ handle="+12025551234",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ blocked_handle = await response.parse()
+ assert blocked_handle is None
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_unblock(self, async_client: AsyncLinqAPIV3) -> None:
+ async with async_client.blocked_handles.with_streaming_response.unblock(
+ handle="+12025551234",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ blocked_handle = await response.parse()
+ assert blocked_handle is None
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_messages.py b/tests/api_resources/test_messages.py
index 51beb7e..8fe3d38 100644
--- a/tests/api_resources/test_messages.py
+++ b/tests/api_resources/test_messages.py
@@ -387,6 +387,11 @@ def test_method_update_app_card_with_all_params(self, client: LinqAPIV3) -> None
"trailing_caption": "2 min",
"trailing_subcaption": "expires",
},
+ action={
+ "action": "attach_card",
+ "experience": "agentcard",
+ "params": {"foo": "bar"},
+ },
fallback_text="Score update",
interactive=True,
url="https://app.example.com/card?game=7f3a&move=2",
@@ -800,6 +805,11 @@ async def test_method_update_app_card_with_all_params(self, async_client: AsyncL
"trailing_caption": "2 min",
"trailing_subcaption": "expires",
},
+ action={
+ "action": "attach_card",
+ "experience": "agentcard",
+ "params": {"foo": "bar"},
+ },
fallback_text="Score update",
interactive=True,
url="https://app.example.com/card?game=7f3a&move=2",
diff --git a/uv.lock b/uv.lock
index bdb9df0..266f215 100644
--- a/uv.lock
+++ b/uv.lock
@@ -530,7 +530,7 @@ wheels = [
[[package]]
name = "linq-python"
-version = "0.19.1"
+version = "0.20.0"
source = { editable = "." }
dependencies = [
{ name = "anyio" },