diff --git a/.stats.yml b/.stats.yml
index 03b0268..1c9b72d 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1 +1 @@
-configured_endpoints: 57
+configured_endpoints: 61
diff --git a/api.md b/api.md
index 18a3ba5..b0a85d4 100644
--- a/api.md
+++ b/api.md
@@ -88,6 +88,18 @@ Methods:
- client.chats.location.retrieve(chat_id) -> GetChatLocationResponse
- client.chats.location.request(chat_id) -> LocationRequestResponse
+## Polls
+
+Types:
+
+```python
+from linq.types.chats import Poll, PollEnvelope
+```
+
+Methods:
+
+- client.chats.polls.create(chat_id, \*\*params) -> PollEnvelope
+
# Messages
Types:
@@ -105,13 +117,21 @@ from linq.types import (
Methods:
-- client.messages.create(\*\*params) -> MessageCreateResponse
-- client.messages.retrieve(message_id) -> Message
-- client.messages.update(message_id, \*\*params) -> Message
-- client.messages.delete(message_id) -> None
-- client.messages.add_reaction(message_id, \*\*params) -> MessageAddReactionResponse
-- client.messages.list_messages_thread(message_id, \*\*params) -> SyncListMessagesPagination[Message]
-- client.messages.update_app_card(message_id, \*\*params) -> MessageUpdateAppCardResponse
+- client.messages.create(\*\*params) -> MessageCreateResponse
+- client.messages.retrieve(message_id) -> Message
+- client.messages.update(message_id, \*\*params) -> Message
+- client.messages.delete(message_id) -> None
+- client.messages.add_reaction(message_id, \*\*params) -> MessageAddReactionResponse
+- client.messages.list_messages_thread(message_id, \*\*params) -> SyncListMessagesPagination[Message]
+- client.messages.update_app_card(message_id, \*\*params) -> MessageUpdateAppCardResponse
+
+## Poll
+
+Methods:
+
+- client.messages.poll.retrieve(message_id) -> PollEnvelope
+- client.messages.poll.add_options(message_id, \*\*params) -> PollEnvelope
+- client.messages.poll.vote(message_id, \*\*params) -> PollEnvelope
# Attachments
diff --git a/src/linq/_client.py b/src/linq/_client.py
index 68bc120..10d9dde 100644
--- a/src/linq/_client.py
+++ b/src/linq/_client.py
@@ -21,6 +21,7 @@
)
from ._utils import (
is_given,
+ is_mapping,
is_mapping_t,
get_async_library,
)
@@ -53,7 +54,6 @@
payment_providers,
webhook_subscriptions,
)
- from .resources.messages import MessagesResource, AsyncMessagesResource
from .resources.payments import PaymentsResource, AsyncPaymentsResource
from .resources.webhooks import WebhooksResource, AsyncWebhooksResource
from .resources.capability import CapabilityResource, AsyncCapabilityResource
@@ -67,6 +67,7 @@
from .resources.payment_handles import PaymentHandlesResource, AsyncPaymentHandlesResource
from .resources.available_number import AvailableNumberResource, AsyncAvailableNumberResource
from .resources.payment_requests import PaymentRequestsResource, AsyncPaymentRequestsResource
+ from .resources.messages.messages import MessagesResource, AsyncMessagesResource
from .resources.payment_providers import PaymentProvidersResource, AsyncPaymentProvidersResource
from .resources.webhook_subscriptions import WebhookSubscriptionsResource, AsyncWebhookSubscriptionsResource
@@ -1009,30 +1010,31 @@ def _make_status_error(
body: object,
response: httpx.Response,
) -> APIStatusError:
+ data = body.get("error", body) if is_mapping(body) else body
if response.status_code == 400:
- return _exceptions.BadRequestError(err_msg, response=response, body=body)
+ return _exceptions.BadRequestError(err_msg, response=response, body=data)
if response.status_code == 401:
- return _exceptions.AuthenticationError(err_msg, response=response, body=body)
+ return _exceptions.AuthenticationError(err_msg, response=response, body=data)
if response.status_code == 403:
- return _exceptions.PermissionDeniedError(err_msg, response=response, body=body)
+ return _exceptions.PermissionDeniedError(err_msg, response=response, body=data)
if response.status_code == 404:
- return _exceptions.NotFoundError(err_msg, response=response, body=body)
+ return _exceptions.NotFoundError(err_msg, response=response, body=data)
if response.status_code == 409:
- return _exceptions.ConflictError(err_msg, response=response, body=body)
+ return _exceptions.ConflictError(err_msg, response=response, body=data)
if response.status_code == 422:
- return _exceptions.UnprocessableEntityError(err_msg, response=response, body=body)
+ return _exceptions.UnprocessableEntityError(err_msg, response=response, body=data)
if response.status_code == 429:
- return _exceptions.RateLimitError(err_msg, response=response, body=body)
+ return _exceptions.RateLimitError(err_msg, response=response, body=data)
if response.status_code >= 500:
- return _exceptions.InternalServerError(err_msg, response=response, body=body)
- return APIStatusError(err_msg, response=response, body=body)
+ return _exceptions.InternalServerError(err_msg, response=response, body=data)
+ return APIStatusError(err_msg, response=response, body=data)
class AsyncLinqAPIV3(AsyncAPIClient):
@@ -1962,30 +1964,31 @@ def _make_status_error(
body: object,
response: httpx.Response,
) -> APIStatusError:
+ data = body.get("error", body) if is_mapping(body) else body
if response.status_code == 400:
- return _exceptions.BadRequestError(err_msg, response=response, body=body)
+ return _exceptions.BadRequestError(err_msg, response=response, body=data)
if response.status_code == 401:
- return _exceptions.AuthenticationError(err_msg, response=response, body=body)
+ return _exceptions.AuthenticationError(err_msg, response=response, body=data)
if response.status_code == 403:
- return _exceptions.PermissionDeniedError(err_msg, response=response, body=body)
+ return _exceptions.PermissionDeniedError(err_msg, response=response, body=data)
if response.status_code == 404:
- return _exceptions.NotFoundError(err_msg, response=response, body=body)
+ return _exceptions.NotFoundError(err_msg, response=response, body=data)
if response.status_code == 409:
- return _exceptions.ConflictError(err_msg, response=response, body=body)
+ return _exceptions.ConflictError(err_msg, response=response, body=data)
if response.status_code == 422:
- return _exceptions.UnprocessableEntityError(err_msg, response=response, body=body)
+ return _exceptions.UnprocessableEntityError(err_msg, response=response, body=data)
if response.status_code == 429:
- return _exceptions.RateLimitError(err_msg, response=response, body=body)
+ return _exceptions.RateLimitError(err_msg, response=response, body=data)
if response.status_code >= 500:
- return _exceptions.InternalServerError(err_msg, response=response, body=body)
- return APIStatusError(err_msg, response=response, body=body)
+ return _exceptions.InternalServerError(err_msg, response=response, body=data)
+ return APIStatusError(err_msg, response=response, body=data)
class LinqAPIV3WithRawResponse:
diff --git a/src/linq/_exceptions.py b/src/linq/_exceptions.py
index f7ad2e2..7a80fc2 100644
--- a/src/linq/_exceptions.py
+++ b/src/linq/_exceptions.py
@@ -2,10 +2,14 @@
from __future__ import annotations
+from typing import Any, Optional, cast
from typing_extensions import Literal
import httpx
+from ._utils import is_dict
+from ._models import construct_type
+
__all__ = [
"BadRequestError",
"AuthenticationError",
@@ -37,12 +41,31 @@ class APIError(LinqApiv3Error):
If there was no response associated with this error then it will be `None`.
"""
- def __init__(self, message: str, request: httpx.Request, *, body: object | None) -> None: # noqa: ARG002
+ code: Optional[int]
+ """Linq API error code."""
+ doc_url: Optional[str]
+ """Link to documentation for this error code"""
+ retry_after: Optional[int] = None
+ """Number of seconds to wait before retrying.
+
+ Only present on 429 rate limit errors.
+ """
+
+ def __init__(self, message: str, request: httpx.Request, *, body: object | None) -> None:
super().__init__(message)
self.request = request
self.message = message
self.body = body
+ if is_dict(body):
+ self.code = cast(Any, construct_type(type_=int, value=body.get("code")))
+ self.doc_url = cast(Any, construct_type(type_=str, value=body.get("doc_url")))
+ self.retry_after = cast(Any, construct_type(type_=Optional[int], value=body.get("retry_after")))
+ else:
+ self.code = None
+ self.doc_url = None
+ self.retry_after = None
+
class APIResponseValidationError(APIError):
response: httpx.Response
diff --git a/src/linq/resources/chats/__init__.py b/src/linq/resources/chats/__init__.py
index ed8f0d2..be17679 100644
--- a/src/linq/resources/chats/__init__.py
+++ b/src/linq/resources/chats/__init__.py
@@ -8,6 +8,14 @@
ChatsResourceWithStreamingResponse,
AsyncChatsResourceWithStreamingResponse,
)
+from .polls import (
+ PollsResource,
+ AsyncPollsResource,
+ PollsResourceWithRawResponse,
+ AsyncPollsResourceWithRawResponse,
+ PollsResourceWithStreamingResponse,
+ AsyncPollsResourceWithStreamingResponse,
+)
from .typing import (
TypingResource,
AsyncTypingResource,
@@ -66,6 +74,12 @@
"AsyncLocationResourceWithRawResponse",
"LocationResourceWithStreamingResponse",
"AsyncLocationResourceWithStreamingResponse",
+ "PollsResource",
+ "AsyncPollsResource",
+ "PollsResourceWithRawResponse",
+ "AsyncPollsResourceWithRawResponse",
+ "PollsResourceWithStreamingResponse",
+ "AsyncPollsResourceWithStreamingResponse",
"ChatsResource",
"AsyncChatsResource",
"ChatsResourceWithRawResponse",
diff --git a/src/linq/resources/chats/chats.py b/src/linq/resources/chats/chats.py
index 883eced..99d9544 100644
--- a/src/linq/resources/chats/chats.py
+++ b/src/linq/resources/chats/chats.py
@@ -4,6 +4,14 @@
import httpx
+from .polls import (
+ PollsResource,
+ AsyncPollsResource,
+ PollsResourceWithRawResponse,
+ AsyncPollsResourceWithRawResponse,
+ PollsResourceWithStreamingResponse,
+ AsyncPollsResourceWithStreamingResponse,
+)
from .typing import (
TypingResource,
AsyncTypingResource,
@@ -212,6 +220,69 @@ def location(self) -> LocationResource:
"""
return LocationResource(self._client)
+ @cached_property
+ def polls(self) -> PollsResource:
+ """Messages are individual communications within a chat thread.
+
+ Messages can include text, media attachments, rich link previews, special effects
+ (like confetti or fireworks), and reactions. All messages are associated with a
+ specific chat and sent from a phone number you own.
+
+ Messages support delivery status tracking, read receipts, and editing capabilities.
+
+ ## Rich Link Previews
+
+ Send a URL as a `link` part to deliver it with a rich preview card showing the
+ page's title, description, and image (when available). A `link` part must be the
+ **only** part in the message — it cannot be combined with text or media parts.
+ To send a URL without a preview card, include it in a `text` part instead.
+
+ **Limitations:**
+ - A `link` part cannot be combined with other parts in the same message.
+ - Maximum URL length: 2,048 characters.
+
+ ## Ephemeral Messages (Privacy Tier)
+
+ For regulated or sensitive conversations, opt in to the **ephemeral messages** tier by contacting your Linq support contact. When enabled, every message on the covered phone numbers is automatically given a fixed **24-hour retention window** — after that window the platform permanently deletes the message from Linq storage. There is no per-message flag; ephemerality is applied automatically based on your configuration.
+
+ You can request it at two scopes:
+
+ | Scope | Effect |
+ |---|---|
+ | **Partner-wide** | Every outbound and inbound message on every phone number under your account is retained for 24 hours, then deleted. |
+ | **Per phone number** | Only the specified phone numbers have their messages auto-deleted. The rest follow the standard message-retention policy. |
+
+ **Behavioral differences vs the standard default:**
+
+ | Aspect | Standard | Ephemeral |
+ |---|---|---|
+ | Retention | Retained per the standard message-retention policy | **Hard backstop: 24 hours** from when the message is created |
+ | After expiry | Message stays retrievable | Message is permanently deleted — `GET /v3/messages/{messageId}` returns `404` and it no longer appears in `GET /v3/chats/{chatId}/messages` |
+ | Content on expiry | N/A | Text, formatting, and attachment references are scrubbed; the message is gone, not blanked out |
+ | Cross-partner isolation | Enforced | Enforced |
+
+ **How the 24-hour window works:**
+
+ - The window is fixed at **24 hours from message creation** (`created_at`) and cannot be configured per message.
+ - It mirrors the ephemeral *attachments* 1-day backstop, so a message and any media it carries expire together.
+ - Expiry is delivery-independent — the clock starts when the message is created, not when it is delivered or read.
+
+ **What you observe:**
+
+ - **No expiry timestamp is exposed.** API responses and webhook payloads do not include the deletion time. If you need it, compute `created_at + 24h` yourself.
+ - **No deletion webhook is sent.** There is no `message.deleted` event — a message simply stops being retrievable once its window passes.
+ - **Delivery is unaffected.** Ephemeral messages send, deliver, and fire the usual `message.sent` / `message.received` and status webhooks exactly like standard messages. Only retention changes.
+
+ **When to choose ephemeral:**
+
+ - You have a compliance requirement that the platform must not retain message content beyond a short window.
+ - The conversation is high-sensitivity (PHI, financial, identity verification) and you do not want it sitting in storage long-term.
+ - Your application is the system of record — you capture what you need from the delivery webhook in real time and do not rely on reading message history back from Linq later.
+
+ **Important:** ephemeral applies in *both directions* — messages you send **and** messages received by the phone numbers in that scope. Because Linq can no longer return the message after 24 hours, persist anything you need to keep from the webhook payload at the time it is delivered.
+ """
+ return PollsResource(self._client)
+
@cached_property
def with_raw_response(self) -> ChatsResourceWithRawResponse:
"""
@@ -237,6 +308,7 @@ def create(
from_: str,
message: MessageContentParam,
to: SequenceNotStr[str],
+ override_optout: bool | 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,
@@ -334,6 +406,11 @@ def create(
to: Array of recipient handles (phone numbers in E.164 format or email addresses).
For individual chats, provide one recipient. For group chats, provide multiple.
+ override_optout: Send even though the recipient asked you to stop (`403`, error code `2024`).
+ Applies to this request only: the opt-out stays in place, so the next send
+ without this flag is rejected again. Every override is recorded against your API
+ key.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -349,6 +426,7 @@ def create(
"from_": from_,
"message": message,
"to": to,
+ "override_optout": override_optout,
},
chat_create_params.ChatCreateParams,
),
@@ -610,6 +688,7 @@ def send_voicememo(
chat_id: str,
*,
attachment_id: str | Omit = omit,
+ override_optout: bool | Omit = omit,
voice_memo_url: 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.
@@ -640,6 +719,11 @@ def send_voicememo(
Either `voice_memo_url` or `attachment_id` must be provided, but not both.
+ override_optout: Send even though the recipient asked you to stop (`403`, error code `2024`).
+ Applies to this request only: the opt-out stays in place, so the next send
+ without this flag is rejected again. Every override is recorded against your API
+ key.
+
voice_memo_url: URL of the voice memo audio file. Must be a publicly accessible HTTPS URL.
Either `voice_memo_url` or `attachment_id` must be provided, but not both.
@@ -659,6 +743,7 @@ def send_voicememo(
body=maybe_transform(
{
"attachment_id": attachment_id,
+ "override_optout": override_optout,
"voice_memo_url": voice_memo_url,
},
chat_send_voicememo_params.ChatSendVoicememoParams,
@@ -856,6 +941,69 @@ def location(self) -> AsyncLocationResource:
"""
return AsyncLocationResource(self._client)
+ @cached_property
+ def polls(self) -> AsyncPollsResource:
+ """Messages are individual communications within a chat thread.
+
+ Messages can include text, media attachments, rich link previews, special effects
+ (like confetti or fireworks), and reactions. All messages are associated with a
+ specific chat and sent from a phone number you own.
+
+ Messages support delivery status tracking, read receipts, and editing capabilities.
+
+ ## Rich Link Previews
+
+ Send a URL as a `link` part to deliver it with a rich preview card showing the
+ page's title, description, and image (when available). A `link` part must be the
+ **only** part in the message — it cannot be combined with text or media parts.
+ To send a URL without a preview card, include it in a `text` part instead.
+
+ **Limitations:**
+ - A `link` part cannot be combined with other parts in the same message.
+ - Maximum URL length: 2,048 characters.
+
+ ## Ephemeral Messages (Privacy Tier)
+
+ For regulated or sensitive conversations, opt in to the **ephemeral messages** tier by contacting your Linq support contact. When enabled, every message on the covered phone numbers is automatically given a fixed **24-hour retention window** — after that window the platform permanently deletes the message from Linq storage. There is no per-message flag; ephemerality is applied automatically based on your configuration.
+
+ You can request it at two scopes:
+
+ | Scope | Effect |
+ |---|---|
+ | **Partner-wide** | Every outbound and inbound message on every phone number under your account is retained for 24 hours, then deleted. |
+ | **Per phone number** | Only the specified phone numbers have their messages auto-deleted. The rest follow the standard message-retention policy. |
+
+ **Behavioral differences vs the standard default:**
+
+ | Aspect | Standard | Ephemeral |
+ |---|---|---|
+ | Retention | Retained per the standard message-retention policy | **Hard backstop: 24 hours** from when the message is created |
+ | After expiry | Message stays retrievable | Message is permanently deleted — `GET /v3/messages/{messageId}` returns `404` and it no longer appears in `GET /v3/chats/{chatId}/messages` |
+ | Content on expiry | N/A | Text, formatting, and attachment references are scrubbed; the message is gone, not blanked out |
+ | Cross-partner isolation | Enforced | Enforced |
+
+ **How the 24-hour window works:**
+
+ - The window is fixed at **24 hours from message creation** (`created_at`) and cannot be configured per message.
+ - It mirrors the ephemeral *attachments* 1-day backstop, so a message and any media it carries expire together.
+ - Expiry is delivery-independent — the clock starts when the message is created, not when it is delivered or read.
+
+ **What you observe:**
+
+ - **No expiry timestamp is exposed.** API responses and webhook payloads do not include the deletion time. If you need it, compute `created_at + 24h` yourself.
+ - **No deletion webhook is sent.** There is no `message.deleted` event — a message simply stops being retrievable once its window passes.
+ - **Delivery is unaffected.** Ephemeral messages send, deliver, and fire the usual `message.sent` / `message.received` and status webhooks exactly like standard messages. Only retention changes.
+
+ **When to choose ephemeral:**
+
+ - You have a compliance requirement that the platform must not retain message content beyond a short window.
+ - The conversation is high-sensitivity (PHI, financial, identity verification) and you do not want it sitting in storage long-term.
+ - Your application is the system of record — you capture what you need from the delivery webhook in real time and do not rely on reading message history back from Linq later.
+
+ **Important:** ephemeral applies in *both directions* — messages you send **and** messages received by the phone numbers in that scope. Because Linq can no longer return the message after 24 hours, persist anything you need to keep from the webhook payload at the time it is delivered.
+ """
+ return AsyncPollsResource(self._client)
+
@cached_property
def with_raw_response(self) -> AsyncChatsResourceWithRawResponse:
"""
@@ -881,6 +1029,7 @@ async def create(
from_: str,
message: MessageContentParam,
to: SequenceNotStr[str],
+ override_optout: bool | 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,
@@ -978,6 +1127,11 @@ async def create(
to: Array of recipient handles (phone numbers in E.164 format or email addresses).
For individual chats, provide one recipient. For group chats, provide multiple.
+ override_optout: Send even though the recipient asked you to stop (`403`, error code `2024`).
+ Applies to this request only: the opt-out stays in place, so the next send
+ without this flag is rejected again. Every override is recorded against your API
+ key.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -993,6 +1147,7 @@ async def create(
"from_": from_,
"message": message,
"to": to,
+ "override_optout": override_optout,
},
chat_create_params.ChatCreateParams,
),
@@ -1254,6 +1409,7 @@ async def send_voicememo(
chat_id: str,
*,
attachment_id: str | Omit = omit,
+ override_optout: bool | Omit = omit,
voice_memo_url: 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.
@@ -1284,6 +1440,11 @@ async def send_voicememo(
Either `voice_memo_url` or `attachment_id` must be provided, but not both.
+ override_optout: Send even though the recipient asked you to stop (`403`, error code `2024`).
+ Applies to this request only: the opt-out stays in place, so the next send
+ without this flag is rejected again. Every override is recorded against your API
+ key.
+
voice_memo_url: URL of the voice memo audio file. Must be a publicly accessible HTTPS URL.
Either `voice_memo_url` or `attachment_id` must be provided, but not both.
@@ -1303,6 +1464,7 @@ async def send_voicememo(
body=await async_maybe_transform(
{
"attachment_id": attachment_id,
+ "override_optout": override_optout,
"voice_memo_url": voice_memo_url,
},
chat_send_voicememo_params.ChatSendVoicememoParams,
@@ -1528,6 +1690,69 @@ def location(self) -> LocationResourceWithRawResponse:
"""
return LocationResourceWithRawResponse(self._chats.location)
+ @cached_property
+ def polls(self) -> PollsResourceWithRawResponse:
+ """Messages are individual communications within a chat thread.
+
+ Messages can include text, media attachments, rich link previews, special effects
+ (like confetti or fireworks), and reactions. All messages are associated with a
+ specific chat and sent from a phone number you own.
+
+ Messages support delivery status tracking, read receipts, and editing capabilities.
+
+ ## Rich Link Previews
+
+ Send a URL as a `link` part to deliver it with a rich preview card showing the
+ page's title, description, and image (when available). A `link` part must be the
+ **only** part in the message — it cannot be combined with text or media parts.
+ To send a URL without a preview card, include it in a `text` part instead.
+
+ **Limitations:**
+ - A `link` part cannot be combined with other parts in the same message.
+ - Maximum URL length: 2,048 characters.
+
+ ## Ephemeral Messages (Privacy Tier)
+
+ For regulated or sensitive conversations, opt in to the **ephemeral messages** tier by contacting your Linq support contact. When enabled, every message on the covered phone numbers is automatically given a fixed **24-hour retention window** — after that window the platform permanently deletes the message from Linq storage. There is no per-message flag; ephemerality is applied automatically based on your configuration.
+
+ You can request it at two scopes:
+
+ | Scope | Effect |
+ |---|---|
+ | **Partner-wide** | Every outbound and inbound message on every phone number under your account is retained for 24 hours, then deleted. |
+ | **Per phone number** | Only the specified phone numbers have their messages auto-deleted. The rest follow the standard message-retention policy. |
+
+ **Behavioral differences vs the standard default:**
+
+ | Aspect | Standard | Ephemeral |
+ |---|---|---|
+ | Retention | Retained per the standard message-retention policy | **Hard backstop: 24 hours** from when the message is created |
+ | After expiry | Message stays retrievable | Message is permanently deleted — `GET /v3/messages/{messageId}` returns `404` and it no longer appears in `GET /v3/chats/{chatId}/messages` |
+ | Content on expiry | N/A | Text, formatting, and attachment references are scrubbed; the message is gone, not blanked out |
+ | Cross-partner isolation | Enforced | Enforced |
+
+ **How the 24-hour window works:**
+
+ - The window is fixed at **24 hours from message creation** (`created_at`) and cannot be configured per message.
+ - It mirrors the ephemeral *attachments* 1-day backstop, so a message and any media it carries expire together.
+ - Expiry is delivery-independent — the clock starts when the message is created, not when it is delivered or read.
+
+ **What you observe:**
+
+ - **No expiry timestamp is exposed.** API responses and webhook payloads do not include the deletion time. If you need it, compute `created_at + 24h` yourself.
+ - **No deletion webhook is sent.** There is no `message.deleted` event — a message simply stops being retrievable once its window passes.
+ - **Delivery is unaffected.** Ephemeral messages send, deliver, and fire the usual `message.sent` / `message.received` and status webhooks exactly like standard messages. Only retention changes.
+
+ **When to choose ephemeral:**
+
+ - You have a compliance requirement that the platform must not retain message content beyond a short window.
+ - The conversation is high-sensitivity (PHI, financial, identity verification) and you do not want it sitting in storage long-term.
+ - Your application is the system of record — you capture what you need from the delivery webhook in real time and do not rely on reading message history back from Linq later.
+
+ **Important:** ephemeral applies in *both directions* — messages you send **and** messages received by the phone numbers in that scope. Because Linq can no longer return the message after 24 hours, persist anything you need to keep from the webhook payload at the time it is delivered.
+ """
+ return PollsResourceWithRawResponse(self._chats.polls)
+
class AsyncChatsResourceWithRawResponse:
def __init__(self, chats: AsyncChatsResource) -> None:
@@ -1705,6 +1930,69 @@ def location(self) -> AsyncLocationResourceWithRawResponse:
"""
return AsyncLocationResourceWithRawResponse(self._chats.location)
+ @cached_property
+ def polls(self) -> AsyncPollsResourceWithRawResponse:
+ """Messages are individual communications within a chat thread.
+
+ Messages can include text, media attachments, rich link previews, special effects
+ (like confetti or fireworks), and reactions. All messages are associated with a
+ specific chat and sent from a phone number you own.
+
+ Messages support delivery status tracking, read receipts, and editing capabilities.
+
+ ## Rich Link Previews
+
+ Send a URL as a `link` part to deliver it with a rich preview card showing the
+ page's title, description, and image (when available). A `link` part must be the
+ **only** part in the message — it cannot be combined with text or media parts.
+ To send a URL without a preview card, include it in a `text` part instead.
+
+ **Limitations:**
+ - A `link` part cannot be combined with other parts in the same message.
+ - Maximum URL length: 2,048 characters.
+
+ ## Ephemeral Messages (Privacy Tier)
+
+ For regulated or sensitive conversations, opt in to the **ephemeral messages** tier by contacting your Linq support contact. When enabled, every message on the covered phone numbers is automatically given a fixed **24-hour retention window** — after that window the platform permanently deletes the message from Linq storage. There is no per-message flag; ephemerality is applied automatically based on your configuration.
+
+ You can request it at two scopes:
+
+ | Scope | Effect |
+ |---|---|
+ | **Partner-wide** | Every outbound and inbound message on every phone number under your account is retained for 24 hours, then deleted. |
+ | **Per phone number** | Only the specified phone numbers have their messages auto-deleted. The rest follow the standard message-retention policy. |
+
+ **Behavioral differences vs the standard default:**
+
+ | Aspect | Standard | Ephemeral |
+ |---|---|---|
+ | Retention | Retained per the standard message-retention policy | **Hard backstop: 24 hours** from when the message is created |
+ | After expiry | Message stays retrievable | Message is permanently deleted — `GET /v3/messages/{messageId}` returns `404` and it no longer appears in `GET /v3/chats/{chatId}/messages` |
+ | Content on expiry | N/A | Text, formatting, and attachment references are scrubbed; the message is gone, not blanked out |
+ | Cross-partner isolation | Enforced | Enforced |
+
+ **How the 24-hour window works:**
+
+ - The window is fixed at **24 hours from message creation** (`created_at`) and cannot be configured per message.
+ - It mirrors the ephemeral *attachments* 1-day backstop, so a message and any media it carries expire together.
+ - Expiry is delivery-independent — the clock starts when the message is created, not when it is delivered or read.
+
+ **What you observe:**
+
+ - **No expiry timestamp is exposed.** API responses and webhook payloads do not include the deletion time. If you need it, compute `created_at + 24h` yourself.
+ - **No deletion webhook is sent.** There is no `message.deleted` event — a message simply stops being retrievable once its window passes.
+ - **Delivery is unaffected.** Ephemeral messages send, deliver, and fire the usual `message.sent` / `message.received` and status webhooks exactly like standard messages. Only retention changes.
+
+ **When to choose ephemeral:**
+
+ - You have a compliance requirement that the platform must not retain message content beyond a short window.
+ - The conversation is high-sensitivity (PHI, financial, identity verification) and you do not want it sitting in storage long-term.
+ - Your application is the system of record — you capture what you need from the delivery webhook in real time and do not rely on reading message history back from Linq later.
+
+ **Important:** ephemeral applies in *both directions* — messages you send **and** messages received by the phone numbers in that scope. Because Linq can no longer return the message after 24 hours, persist anything you need to keep from the webhook payload at the time it is delivered.
+ """
+ return AsyncPollsResourceWithRawResponse(self._chats.polls)
+
class ChatsResourceWithStreamingResponse:
def __init__(self, chats: ChatsResource) -> None:
@@ -1882,6 +2170,69 @@ def location(self) -> LocationResourceWithStreamingResponse:
"""
return LocationResourceWithStreamingResponse(self._chats.location)
+ @cached_property
+ def polls(self) -> PollsResourceWithStreamingResponse:
+ """Messages are individual communications within a chat thread.
+
+ Messages can include text, media attachments, rich link previews, special effects
+ (like confetti or fireworks), and reactions. All messages are associated with a
+ specific chat and sent from a phone number you own.
+
+ Messages support delivery status tracking, read receipts, and editing capabilities.
+
+ ## Rich Link Previews
+
+ Send a URL as a `link` part to deliver it with a rich preview card showing the
+ page's title, description, and image (when available). A `link` part must be the
+ **only** part in the message — it cannot be combined with text or media parts.
+ To send a URL without a preview card, include it in a `text` part instead.
+
+ **Limitations:**
+ - A `link` part cannot be combined with other parts in the same message.
+ - Maximum URL length: 2,048 characters.
+
+ ## Ephemeral Messages (Privacy Tier)
+
+ For regulated or sensitive conversations, opt in to the **ephemeral messages** tier by contacting your Linq support contact. When enabled, every message on the covered phone numbers is automatically given a fixed **24-hour retention window** — after that window the platform permanently deletes the message from Linq storage. There is no per-message flag; ephemerality is applied automatically based on your configuration.
+
+ You can request it at two scopes:
+
+ | Scope | Effect |
+ |---|---|
+ | **Partner-wide** | Every outbound and inbound message on every phone number under your account is retained for 24 hours, then deleted. |
+ | **Per phone number** | Only the specified phone numbers have their messages auto-deleted. The rest follow the standard message-retention policy. |
+
+ **Behavioral differences vs the standard default:**
+
+ | Aspect | Standard | Ephemeral |
+ |---|---|---|
+ | Retention | Retained per the standard message-retention policy | **Hard backstop: 24 hours** from when the message is created |
+ | After expiry | Message stays retrievable | Message is permanently deleted — `GET /v3/messages/{messageId}` returns `404` and it no longer appears in `GET /v3/chats/{chatId}/messages` |
+ | Content on expiry | N/A | Text, formatting, and attachment references are scrubbed; the message is gone, not blanked out |
+ | Cross-partner isolation | Enforced | Enforced |
+
+ **How the 24-hour window works:**
+
+ - The window is fixed at **24 hours from message creation** (`created_at`) and cannot be configured per message.
+ - It mirrors the ephemeral *attachments* 1-day backstop, so a message and any media it carries expire together.
+ - Expiry is delivery-independent — the clock starts when the message is created, not when it is delivered or read.
+
+ **What you observe:**
+
+ - **No expiry timestamp is exposed.** API responses and webhook payloads do not include the deletion time. If you need it, compute `created_at + 24h` yourself.
+ - **No deletion webhook is sent.** There is no `message.deleted` event — a message simply stops being retrievable once its window passes.
+ - **Delivery is unaffected.** Ephemeral messages send, deliver, and fire the usual `message.sent` / `message.received` and status webhooks exactly like standard messages. Only retention changes.
+
+ **When to choose ephemeral:**
+
+ - You have a compliance requirement that the platform must not retain message content beyond a short window.
+ - The conversation is high-sensitivity (PHI, financial, identity verification) and you do not want it sitting in storage long-term.
+ - Your application is the system of record — you capture what you need from the delivery webhook in real time and do not rely on reading message history back from Linq later.
+
+ **Important:** ephemeral applies in *both directions* — messages you send **and** messages received by the phone numbers in that scope. Because Linq can no longer return the message after 24 hours, persist anything you need to keep from the webhook payload at the time it is delivered.
+ """
+ return PollsResourceWithStreamingResponse(self._chats.polls)
+
class AsyncChatsResourceWithStreamingResponse:
def __init__(self, chats: AsyncChatsResource) -> None:
@@ -2058,3 +2409,66 @@ def location(self) -> AsyncLocationResourceWithStreamingResponse:
Messages conversation** with your number.
"""
return AsyncLocationResourceWithStreamingResponse(self._chats.location)
+
+ @cached_property
+ def polls(self) -> AsyncPollsResourceWithStreamingResponse:
+ """Messages are individual communications within a chat thread.
+
+ Messages can include text, media attachments, rich link previews, special effects
+ (like confetti or fireworks), and reactions. All messages are associated with a
+ specific chat and sent from a phone number you own.
+
+ Messages support delivery status tracking, read receipts, and editing capabilities.
+
+ ## Rich Link Previews
+
+ Send a URL as a `link` part to deliver it with a rich preview card showing the
+ page's title, description, and image (when available). A `link` part must be the
+ **only** part in the message — it cannot be combined with text or media parts.
+ To send a URL without a preview card, include it in a `text` part instead.
+
+ **Limitations:**
+ - A `link` part cannot be combined with other parts in the same message.
+ - Maximum URL length: 2,048 characters.
+
+ ## Ephemeral Messages (Privacy Tier)
+
+ For regulated or sensitive conversations, opt in to the **ephemeral messages** tier by contacting your Linq support contact. When enabled, every message on the covered phone numbers is automatically given a fixed **24-hour retention window** — after that window the platform permanently deletes the message from Linq storage. There is no per-message flag; ephemerality is applied automatically based on your configuration.
+
+ You can request it at two scopes:
+
+ | Scope | Effect |
+ |---|---|
+ | **Partner-wide** | Every outbound and inbound message on every phone number under your account is retained for 24 hours, then deleted. |
+ | **Per phone number** | Only the specified phone numbers have their messages auto-deleted. The rest follow the standard message-retention policy. |
+
+ **Behavioral differences vs the standard default:**
+
+ | Aspect | Standard | Ephemeral |
+ |---|---|---|
+ | Retention | Retained per the standard message-retention policy | **Hard backstop: 24 hours** from when the message is created |
+ | After expiry | Message stays retrievable | Message is permanently deleted — `GET /v3/messages/{messageId}` returns `404` and it no longer appears in `GET /v3/chats/{chatId}/messages` |
+ | Content on expiry | N/A | Text, formatting, and attachment references are scrubbed; the message is gone, not blanked out |
+ | Cross-partner isolation | Enforced | Enforced |
+
+ **How the 24-hour window works:**
+
+ - The window is fixed at **24 hours from message creation** (`created_at`) and cannot be configured per message.
+ - It mirrors the ephemeral *attachments* 1-day backstop, so a message and any media it carries expire together.
+ - Expiry is delivery-independent — the clock starts when the message is created, not when it is delivered or read.
+
+ **What you observe:**
+
+ - **No expiry timestamp is exposed.** API responses and webhook payloads do not include the deletion time. If you need it, compute `created_at + 24h` yourself.
+ - **No deletion webhook is sent.** There is no `message.deleted` event — a message simply stops being retrievable once its window passes.
+ - **Delivery is unaffected.** Ephemeral messages send, deliver, and fire the usual `message.sent` / `message.received` and status webhooks exactly like standard messages. Only retention changes.
+
+ **When to choose ephemeral:**
+
+ - You have a compliance requirement that the platform must not retain message content beyond a short window.
+ - The conversation is high-sensitivity (PHI, financial, identity verification) and you do not want it sitting in storage long-term.
+ - Your application is the system of record — you capture what you need from the delivery webhook in real time and do not rely on reading message history back from Linq later.
+
+ **Important:** ephemeral applies in *both directions* — messages you send **and** messages received by the phone numbers in that scope. Because Linq can no longer return the message after 24 hours, persist anything you need to keep from the webhook payload at the time it is delivered.
+ """
+ return AsyncPollsResourceWithStreamingResponse(self._chats.polls)
diff --git a/src/linq/resources/chats/messages.py b/src/linq/resources/chats/messages.py
index 68ebc24..5adba9d 100644
--- a/src/linq/resources/chats/messages.py
+++ b/src/linq/resources/chats/messages.py
@@ -159,6 +159,7 @@ def send(
chat_id: str,
*,
message: MessageContentParam,
+ override_optout: bool | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -221,6 +222,11 @@ def send(
iMessage app. Never both: an app card is the whole message (Apple's `MSMessage`
cannot coexist with text), so copy and a card are two sends, not one.
+ override_optout: Send even though the recipient asked you to stop (`403`, error code `2024`).
+ Applies to this request only: the opt-out stays in place, so the next send
+ without this flag is rejected again. Every override is recorded against your API
+ key.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -233,7 +239,13 @@ def send(
raise ValueError(f"Expected a non-empty value for `chat_id` but received {chat_id!r}")
return self._post(
path_template("/v3/chats/{chat_id}/messages", chat_id=chat_id),
- body=maybe_transform({"message": message}, message_send_params.MessageSendParams),
+ body=maybe_transform(
+ {
+ "message": message,
+ "override_optout": override_optout,
+ },
+ message_send_params.MessageSendParams,
+ ),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
@@ -376,6 +388,7 @@ async def send(
chat_id: str,
*,
message: MessageContentParam,
+ override_optout: bool | 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,
@@ -438,6 +451,11 @@ async def send(
iMessage app. Never both: an app card is the whole message (Apple's `MSMessage`
cannot coexist with text), so copy and a card are two sends, not one.
+ override_optout: Send even though the recipient asked you to stop (`403`, error code `2024`).
+ Applies to this request only: the opt-out stays in place, so the next send
+ without this flag is rejected again. Every override is recorded against your API
+ key.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -450,7 +468,13 @@ async def send(
raise ValueError(f"Expected a non-empty value for `chat_id` but received {chat_id!r}")
return await self._post(
path_template("/v3/chats/{chat_id}/messages", chat_id=chat_id),
- body=await async_maybe_transform({"message": message}, message_send_params.MessageSendParams),
+ body=await async_maybe_transform(
+ {
+ "message": message,
+ "override_optout": override_optout,
+ },
+ message_send_params.MessageSendParams,
+ ),
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
diff --git a/src/linq/resources/chats/polls.py b/src/linq/resources/chats/polls.py
new file mode 100644
index 0000000..e415401
--- /dev/null
+++ b/src/linq/resources/chats/polls.py
@@ -0,0 +1,309 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ..._types import Body, Query, Headers, NotGiven, not_given
+from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ...types.chats import poll_create_params
+from ..._base_client import make_request_options
+from ...types.chats.poll_envelope import PollEnvelope
+
+__all__ = ["PollsResource", "AsyncPollsResource"]
+
+
+class PollsResource(SyncAPIResource):
+ """Messages are individual communications within a chat thread.
+
+ Messages can include text, media attachments, rich link previews, special effects
+ (like confetti or fireworks), and reactions. All messages are associated with a
+ specific chat and sent from a phone number you own.
+
+ Messages support delivery status tracking, read receipts, and editing capabilities.
+
+ ## Rich Link Previews
+
+ Send a URL as a `link` part to deliver it with a rich preview card showing the
+ page's title, description, and image (when available). A `link` part must be the
+ **only** part in the message — it cannot be combined with text or media parts.
+ To send a URL without a preview card, include it in a `text` part instead.
+
+ **Limitations:**
+ - A `link` part cannot be combined with other parts in the same message.
+ - Maximum URL length: 2,048 characters.
+
+ ## Ephemeral Messages (Privacy Tier)
+
+ For regulated or sensitive conversations, opt in to the **ephemeral messages** tier by contacting your Linq support contact. When enabled, every message on the covered phone numbers is automatically given a fixed **24-hour retention window** — after that window the platform permanently deletes the message from Linq storage. There is no per-message flag; ephemerality is applied automatically based on your configuration.
+
+ You can request it at two scopes:
+
+ | Scope | Effect |
+ |---|---|
+ | **Partner-wide** | Every outbound and inbound message on every phone number under your account is retained for 24 hours, then deleted. |
+ | **Per phone number** | Only the specified phone numbers have their messages auto-deleted. The rest follow the standard message-retention policy. |
+
+ **Behavioral differences vs the standard default:**
+
+ | Aspect | Standard | Ephemeral |
+ |---|---|---|
+ | Retention | Retained per the standard message-retention policy | **Hard backstop: 24 hours** from when the message is created |
+ | After expiry | Message stays retrievable | Message is permanently deleted — `GET /v3/messages/{messageId}` returns `404` and it no longer appears in `GET /v3/chats/{chatId}/messages` |
+ | Content on expiry | N/A | Text, formatting, and attachment references are scrubbed; the message is gone, not blanked out |
+ | Cross-partner isolation | Enforced | Enforced |
+
+ **How the 24-hour window works:**
+
+ - The window is fixed at **24 hours from message creation** (`created_at`) and cannot be configured per message.
+ - It mirrors the ephemeral *attachments* 1-day backstop, so a message and any media it carries expire together.
+ - Expiry is delivery-independent — the clock starts when the message is created, not when it is delivered or read.
+
+ **What you observe:**
+
+ - **No expiry timestamp is exposed.** API responses and webhook payloads do not include the deletion time. If you need it, compute `created_at + 24h` yourself.
+ - **No deletion webhook is sent.** There is no `message.deleted` event — a message simply stops being retrievable once its window passes.
+ - **Delivery is unaffected.** Ephemeral messages send, deliver, and fire the usual `message.sent` / `message.received` and status webhooks exactly like standard messages. Only retention changes.
+
+ **When to choose ephemeral:**
+
+ - You have a compliance requirement that the platform must not retain message content beyond a short window.
+ - The conversation is high-sensitivity (PHI, financial, identity verification) and you do not want it sitting in storage long-term.
+ - Your application is the system of record — you capture what you need from the delivery webhook in real time and do not rely on reading message history back from Linq later.
+
+ **Important:** ephemeral applies in *both directions* — messages you send **and** messages received by the phone numbers in that scope. Because Linq can no longer return the message after 24 hours, persist anything you need to keep from the webhook payload at the time it is delivered.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> PollsResourceWithRawResponse:
+ """
+ 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 PollsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> PollsResourceWithStreamingResponse:
+ """
+ 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 PollsResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ chat_id: str,
+ *,
+ poll: poll_create_params.Poll,
+ # 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,
+ ) -> PollEnvelope:
+ """Create an iMessage poll in an existing chat and send it.
+
+ Polls are
+ iMessage-only.
+
+ The chat must already exist — **a poll cannot be the first message of a new
+ chat** (use `POST /v3/chats` for that). Options are **add-only and immutable**:
+ you can add options later via `POST /v3/messages/{messageId}/poll/options`, but
+ never edit or remove them.
+
+ Args:
+ poll: Poll content to create. A poll needs at least two options. Options are add-only
+ and immutable — there is no title/question (send that as a normal text message).
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not chat_id:
+ raise ValueError(f"Expected a non-empty value for `chat_id` but received {chat_id!r}")
+ return self._post(
+ path_template("/v3/chats/{chat_id}/polls", chat_id=chat_id),
+ body=maybe_transform({"poll": poll}, poll_create_params.PollCreateParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PollEnvelope,
+ )
+
+
+class AsyncPollsResource(AsyncAPIResource):
+ """Messages are individual communications within a chat thread.
+
+ Messages can include text, media attachments, rich link previews, special effects
+ (like confetti or fireworks), and reactions. All messages are associated with a
+ specific chat and sent from a phone number you own.
+
+ Messages support delivery status tracking, read receipts, and editing capabilities.
+
+ ## Rich Link Previews
+
+ Send a URL as a `link` part to deliver it with a rich preview card showing the
+ page's title, description, and image (when available). A `link` part must be the
+ **only** part in the message — it cannot be combined with text or media parts.
+ To send a URL without a preview card, include it in a `text` part instead.
+
+ **Limitations:**
+ - A `link` part cannot be combined with other parts in the same message.
+ - Maximum URL length: 2,048 characters.
+
+ ## Ephemeral Messages (Privacy Tier)
+
+ For regulated or sensitive conversations, opt in to the **ephemeral messages** tier by contacting your Linq support contact. When enabled, every message on the covered phone numbers is automatically given a fixed **24-hour retention window** — after that window the platform permanently deletes the message from Linq storage. There is no per-message flag; ephemerality is applied automatically based on your configuration.
+
+ You can request it at two scopes:
+
+ | Scope | Effect |
+ |---|---|
+ | **Partner-wide** | Every outbound and inbound message on every phone number under your account is retained for 24 hours, then deleted. |
+ | **Per phone number** | Only the specified phone numbers have their messages auto-deleted. The rest follow the standard message-retention policy. |
+
+ **Behavioral differences vs the standard default:**
+
+ | Aspect | Standard | Ephemeral |
+ |---|---|---|
+ | Retention | Retained per the standard message-retention policy | **Hard backstop: 24 hours** from when the message is created |
+ | After expiry | Message stays retrievable | Message is permanently deleted — `GET /v3/messages/{messageId}` returns `404` and it no longer appears in `GET /v3/chats/{chatId}/messages` |
+ | Content on expiry | N/A | Text, formatting, and attachment references are scrubbed; the message is gone, not blanked out |
+ | Cross-partner isolation | Enforced | Enforced |
+
+ **How the 24-hour window works:**
+
+ - The window is fixed at **24 hours from message creation** (`created_at`) and cannot be configured per message.
+ - It mirrors the ephemeral *attachments* 1-day backstop, so a message and any media it carries expire together.
+ - Expiry is delivery-independent — the clock starts when the message is created, not when it is delivered or read.
+
+ **What you observe:**
+
+ - **No expiry timestamp is exposed.** API responses and webhook payloads do not include the deletion time. If you need it, compute `created_at + 24h` yourself.
+ - **No deletion webhook is sent.** There is no `message.deleted` event — a message simply stops being retrievable once its window passes.
+ - **Delivery is unaffected.** Ephemeral messages send, deliver, and fire the usual `message.sent` / `message.received` and status webhooks exactly like standard messages. Only retention changes.
+
+ **When to choose ephemeral:**
+
+ - You have a compliance requirement that the platform must not retain message content beyond a short window.
+ - The conversation is high-sensitivity (PHI, financial, identity verification) and you do not want it sitting in storage long-term.
+ - Your application is the system of record — you capture what you need from the delivery webhook in real time and do not rely on reading message history back from Linq later.
+
+ **Important:** ephemeral applies in *both directions* — messages you send **and** messages received by the phone numbers in that scope. Because Linq can no longer return the message after 24 hours, persist anything you need to keep from the webhook payload at the time it is delivered.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> AsyncPollsResourceWithRawResponse:
+ """
+ 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 AsyncPollsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncPollsResourceWithStreamingResponse:
+ """
+ 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 AsyncPollsResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ chat_id: str,
+ *,
+ poll: poll_create_params.Poll,
+ # 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,
+ ) -> PollEnvelope:
+ """Create an iMessage poll in an existing chat and send it.
+
+ Polls are
+ iMessage-only.
+
+ The chat must already exist — **a poll cannot be the first message of a new
+ chat** (use `POST /v3/chats` for that). Options are **add-only and immutable**:
+ you can add options later via `POST /v3/messages/{messageId}/poll/options`, but
+ never edit or remove them.
+
+ Args:
+ poll: Poll content to create. A poll needs at least two options. Options are add-only
+ and immutable — there is no title/question (send that as a normal text message).
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not chat_id:
+ raise ValueError(f"Expected a non-empty value for `chat_id` but received {chat_id!r}")
+ return await self._post(
+ path_template("/v3/chats/{chat_id}/polls", chat_id=chat_id),
+ body=await async_maybe_transform({"poll": poll}, poll_create_params.PollCreateParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PollEnvelope,
+ )
+
+
+class PollsResourceWithRawResponse:
+ def __init__(self, polls: PollsResource) -> None:
+ self._polls = polls
+
+ self.create = to_raw_response_wrapper(
+ polls.create,
+ )
+
+
+class AsyncPollsResourceWithRawResponse:
+ def __init__(self, polls: AsyncPollsResource) -> None:
+ self._polls = polls
+
+ self.create = async_to_raw_response_wrapper(
+ polls.create,
+ )
+
+
+class PollsResourceWithStreamingResponse:
+ def __init__(self, polls: PollsResource) -> None:
+ self._polls = polls
+
+ self.create = to_streamed_response_wrapper(
+ polls.create,
+ )
+
+
+class AsyncPollsResourceWithStreamingResponse:
+ def __init__(self, polls: AsyncPollsResource) -> None:
+ self._polls = polls
+
+ self.create = async_to_streamed_response_wrapper(
+ polls.create,
+ )
diff --git a/src/linq/resources/messages/__init__.py b/src/linq/resources/messages/__init__.py
new file mode 100644
index 0000000..1fc2978
--- /dev/null
+++ b/src/linq/resources/messages/__init__.py
@@ -0,0 +1,33 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from .poll import (
+ PollResource,
+ AsyncPollResource,
+ PollResourceWithRawResponse,
+ AsyncPollResourceWithRawResponse,
+ PollResourceWithStreamingResponse,
+ AsyncPollResourceWithStreamingResponse,
+)
+from .messages import (
+ MessagesResource,
+ AsyncMessagesResource,
+ MessagesResourceWithRawResponse,
+ AsyncMessagesResourceWithRawResponse,
+ MessagesResourceWithStreamingResponse,
+ AsyncMessagesResourceWithStreamingResponse,
+)
+
+__all__ = [
+ "PollResource",
+ "AsyncPollResource",
+ "PollResourceWithRawResponse",
+ "AsyncPollResourceWithRawResponse",
+ "PollResourceWithStreamingResponse",
+ "AsyncPollResourceWithStreamingResponse",
+ "MessagesResource",
+ "AsyncMessagesResource",
+ "MessagesResourceWithRawResponse",
+ "AsyncMessagesResourceWithRawResponse",
+ "MessagesResourceWithStreamingResponse",
+ "AsyncMessagesResourceWithStreamingResponse",
+]
diff --git a/src/linq/resources/messages.py b/src/linq/resources/messages/messages.py
similarity index 67%
rename from src/linq/resources/messages.py
rename to src/linq/resources/messages/messages.py
index 9defd74..601bd85 100644
--- a/src/linq/resources/messages.py
+++ b/src/linq/resources/messages/messages.py
@@ -6,31 +6,39 @@
import httpx
-from ..types import (
+from .poll import (
+ PollResource,
+ AsyncPollResource,
+ PollResourceWithRawResponse,
+ AsyncPollResourceWithRawResponse,
+ PollResourceWithStreamingResponse,
+ AsyncPollResourceWithStreamingResponse,
+)
+from ...types import (
message_create_params,
message_update_params,
message_add_reaction_params,
message_update_app_card_params,
message_list_messages_thread_params,
)
-from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, omit, not_given
-from .._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform
-from .._compat import cached_property
-from .._resource import SyncAPIResource, AsyncAPIResource
-from .._response import (
+from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, omit, not_given
+from ..._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
to_raw_response_wrapper,
to_streamed_response_wrapper,
async_to_raw_response_wrapper,
async_to_streamed_response_wrapper,
)
-from ..pagination import SyncListMessagesPagination, AsyncListMessagesPagination
-from .._base_client import AsyncPaginator, make_request_options
-from ..types.message import Message
-from ..types.shared.reaction_type import ReactionType
-from ..types.message_content_param import MessageContentParam
-from ..types.message_create_response import MessageCreateResponse
-from ..types.message_add_reaction_response import MessageAddReactionResponse
-from ..types.message_update_app_card_response import MessageUpdateAppCardResponse
+from ...pagination import SyncListMessagesPagination, AsyncListMessagesPagination
+from ..._base_client import AsyncPaginator, make_request_options
+from ...types.message import Message
+from ...types.shared.reaction_type import ReactionType
+from ...types.message_content_param import MessageContentParam
+from ...types.message_create_response import MessageCreateResponse
+from ...types.message_add_reaction_response import MessageAddReactionResponse
+from ...types.message_update_app_card_response import MessageUpdateAppCardResponse
__all__ = ["MessagesResource", "AsyncMessagesResource"]
@@ -96,6 +104,69 @@ class MessagesResource(SyncAPIResource):
**Important:** ephemeral applies in *both directions* — messages you send **and** messages received by the phone numbers in that scope. Because Linq can no longer return the message after 24 hours, persist anything you need to keep from the webhook payload at the time it is delivered.
"""
+ @cached_property
+ def poll(self) -> PollResource:
+ """Messages are individual communications within a chat thread.
+
+ Messages can include text, media attachments, rich link previews, special effects
+ (like confetti or fireworks), and reactions. All messages are associated with a
+ specific chat and sent from a phone number you own.
+
+ Messages support delivery status tracking, read receipts, and editing capabilities.
+
+ ## Rich Link Previews
+
+ Send a URL as a `link` part to deliver it with a rich preview card showing the
+ page's title, description, and image (when available). A `link` part must be the
+ **only** part in the message — it cannot be combined with text or media parts.
+ To send a URL without a preview card, include it in a `text` part instead.
+
+ **Limitations:**
+ - A `link` part cannot be combined with other parts in the same message.
+ - Maximum URL length: 2,048 characters.
+
+ ## Ephemeral Messages (Privacy Tier)
+
+ For regulated or sensitive conversations, opt in to the **ephemeral messages** tier by contacting your Linq support contact. When enabled, every message on the covered phone numbers is automatically given a fixed **24-hour retention window** — after that window the platform permanently deletes the message from Linq storage. There is no per-message flag; ephemerality is applied automatically based on your configuration.
+
+ You can request it at two scopes:
+
+ | Scope | Effect |
+ |---|---|
+ | **Partner-wide** | Every outbound and inbound message on every phone number under your account is retained for 24 hours, then deleted. |
+ | **Per phone number** | Only the specified phone numbers have their messages auto-deleted. The rest follow the standard message-retention policy. |
+
+ **Behavioral differences vs the standard default:**
+
+ | Aspect | Standard | Ephemeral |
+ |---|---|---|
+ | Retention | Retained per the standard message-retention policy | **Hard backstop: 24 hours** from when the message is created |
+ | After expiry | Message stays retrievable | Message is permanently deleted — `GET /v3/messages/{messageId}` returns `404` and it no longer appears in `GET /v3/chats/{chatId}/messages` |
+ | Content on expiry | N/A | Text, formatting, and attachment references are scrubbed; the message is gone, not blanked out |
+ | Cross-partner isolation | Enforced | Enforced |
+
+ **How the 24-hour window works:**
+
+ - The window is fixed at **24 hours from message creation** (`created_at`) and cannot be configured per message.
+ - It mirrors the ephemeral *attachments* 1-day backstop, so a message and any media it carries expire together.
+ - Expiry is delivery-independent — the clock starts when the message is created, not when it is delivered or read.
+
+ **What you observe:**
+
+ - **No expiry timestamp is exposed.** API responses and webhook payloads do not include the deletion time. If you need it, compute `created_at + 24h` yourself.
+ - **No deletion webhook is sent.** There is no `message.deleted` event — a message simply stops being retrievable once its window passes.
+ - **Delivery is unaffected.** Ephemeral messages send, deliver, and fire the usual `message.sent` / `message.received` and status webhooks exactly like standard messages. Only retention changes.
+
+ **When to choose ephemeral:**
+
+ - You have a compliance requirement that the platform must not retain message content beyond a short window.
+ - The conversation is high-sensitivity (PHI, financial, identity verification) and you do not want it sitting in storage long-term.
+ - Your application is the system of record — you capture what you need from the delivery webhook in real time and do not rely on reading message history back from Linq later.
+
+ **Important:** ephemeral applies in *both directions* — messages you send **and** messages received by the phone numbers in that scope. Because Linq can no longer return the message after 24 hours, persist anything you need to keep from the webhook payload at the time it is delivered.
+ """
+ return PollResource(self._client)
+
@cached_property
def with_raw_response(self) -> MessagesResourceWithRawResponse:
"""
@@ -122,6 +193,7 @@ def create(
to: SequenceNotStr[str],
continuation_message: message_create_params.ContinuationMessage | Omit = omit,
exclude_from: SequenceNotStr[str] | Omit = omit,
+ override_optout: bool | Omit = omit,
idempotency_key: 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.
@@ -219,6 +291,11 @@ def create(
like `4155551234` is rejected rather than silently skipped. Excluding every one
of your available lines returns 400 when a line has to be picked.
+ override_optout: Send even though the recipient asked you to stop (`403`, error code `2024`).
+ Applies to this request only: the opt-out stays in place, so the next send
+ without this flag is rejected again. Every override is recorded against your API
+ key.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -236,6 +313,7 @@ def create(
"to": to,
"continuation_message": continuation_message,
"exclude_from": exclude_from,
+ "override_optout": override_optout,
},
message_create_params.MessageCreateParams,
),
@@ -656,6 +734,69 @@ class AsyncMessagesResource(AsyncAPIResource):
**Important:** ephemeral applies in *both directions* — messages you send **and** messages received by the phone numbers in that scope. Because Linq can no longer return the message after 24 hours, persist anything you need to keep from the webhook payload at the time it is delivered.
"""
+ @cached_property
+ def poll(self) -> AsyncPollResource:
+ """Messages are individual communications within a chat thread.
+
+ Messages can include text, media attachments, rich link previews, special effects
+ (like confetti or fireworks), and reactions. All messages are associated with a
+ specific chat and sent from a phone number you own.
+
+ Messages support delivery status tracking, read receipts, and editing capabilities.
+
+ ## Rich Link Previews
+
+ Send a URL as a `link` part to deliver it with a rich preview card showing the
+ page's title, description, and image (when available). A `link` part must be the
+ **only** part in the message — it cannot be combined with text or media parts.
+ To send a URL without a preview card, include it in a `text` part instead.
+
+ **Limitations:**
+ - A `link` part cannot be combined with other parts in the same message.
+ - Maximum URL length: 2,048 characters.
+
+ ## Ephemeral Messages (Privacy Tier)
+
+ For regulated or sensitive conversations, opt in to the **ephemeral messages** tier by contacting your Linq support contact. When enabled, every message on the covered phone numbers is automatically given a fixed **24-hour retention window** — after that window the platform permanently deletes the message from Linq storage. There is no per-message flag; ephemerality is applied automatically based on your configuration.
+
+ You can request it at two scopes:
+
+ | Scope | Effect |
+ |---|---|
+ | **Partner-wide** | Every outbound and inbound message on every phone number under your account is retained for 24 hours, then deleted. |
+ | **Per phone number** | Only the specified phone numbers have their messages auto-deleted. The rest follow the standard message-retention policy. |
+
+ **Behavioral differences vs the standard default:**
+
+ | Aspect | Standard | Ephemeral |
+ |---|---|---|
+ | Retention | Retained per the standard message-retention policy | **Hard backstop: 24 hours** from when the message is created |
+ | After expiry | Message stays retrievable | Message is permanently deleted — `GET /v3/messages/{messageId}` returns `404` and it no longer appears in `GET /v3/chats/{chatId}/messages` |
+ | Content on expiry | N/A | Text, formatting, and attachment references are scrubbed; the message is gone, not blanked out |
+ | Cross-partner isolation | Enforced | Enforced |
+
+ **How the 24-hour window works:**
+
+ - The window is fixed at **24 hours from message creation** (`created_at`) and cannot be configured per message.
+ - It mirrors the ephemeral *attachments* 1-day backstop, so a message and any media it carries expire together.
+ - Expiry is delivery-independent — the clock starts when the message is created, not when it is delivered or read.
+
+ **What you observe:**
+
+ - **No expiry timestamp is exposed.** API responses and webhook payloads do not include the deletion time. If you need it, compute `created_at + 24h` yourself.
+ - **No deletion webhook is sent.** There is no `message.deleted` event — a message simply stops being retrievable once its window passes.
+ - **Delivery is unaffected.** Ephemeral messages send, deliver, and fire the usual `message.sent` / `message.received` and status webhooks exactly like standard messages. Only retention changes.
+
+ **When to choose ephemeral:**
+
+ - You have a compliance requirement that the platform must not retain message content beyond a short window.
+ - The conversation is high-sensitivity (PHI, financial, identity verification) and you do not want it sitting in storage long-term.
+ - Your application is the system of record — you capture what you need from the delivery webhook in real time and do not rely on reading message history back from Linq later.
+
+ **Important:** ephemeral applies in *both directions* — messages you send **and** messages received by the phone numbers in that scope. Because Linq can no longer return the message after 24 hours, persist anything you need to keep from the webhook payload at the time it is delivered.
+ """
+ return AsyncPollResource(self._client)
+
@cached_property
def with_raw_response(self) -> AsyncMessagesResourceWithRawResponse:
"""
@@ -682,6 +823,7 @@ async def create(
to: SequenceNotStr[str],
continuation_message: message_create_params.ContinuationMessage | Omit = omit,
exclude_from: SequenceNotStr[str] | Omit = omit,
+ override_optout: bool | Omit = omit,
idempotency_key: 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.
@@ -779,6 +921,11 @@ async def create(
like `4155551234` is rejected rather than silently skipped. Excluding every one
of your available lines returns 400 when a line has to be picked.
+ override_optout: Send even though the recipient asked you to stop (`403`, error code `2024`).
+ Applies to this request only: the opt-out stays in place, so the next send
+ without this flag is rejected again. Every override is recorded against your API
+ key.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -796,6 +943,7 @@ async def create(
"to": to,
"continuation_message": continuation_message,
"exclude_from": exclude_from,
+ "override_optout": override_optout,
},
message_create_params.MessageCreateParams,
),
@@ -1181,6 +1329,69 @@ def __init__(self, messages: MessagesResource) -> None:
messages.update_app_card,
)
+ @cached_property
+ def poll(self) -> PollResourceWithRawResponse:
+ """Messages are individual communications within a chat thread.
+
+ Messages can include text, media attachments, rich link previews, special effects
+ (like confetti or fireworks), and reactions. All messages are associated with a
+ specific chat and sent from a phone number you own.
+
+ Messages support delivery status tracking, read receipts, and editing capabilities.
+
+ ## Rich Link Previews
+
+ Send a URL as a `link` part to deliver it with a rich preview card showing the
+ page's title, description, and image (when available). A `link` part must be the
+ **only** part in the message — it cannot be combined with text or media parts.
+ To send a URL without a preview card, include it in a `text` part instead.
+
+ **Limitations:**
+ - A `link` part cannot be combined with other parts in the same message.
+ - Maximum URL length: 2,048 characters.
+
+ ## Ephemeral Messages (Privacy Tier)
+
+ For regulated or sensitive conversations, opt in to the **ephemeral messages** tier by contacting your Linq support contact. When enabled, every message on the covered phone numbers is automatically given a fixed **24-hour retention window** — after that window the platform permanently deletes the message from Linq storage. There is no per-message flag; ephemerality is applied automatically based on your configuration.
+
+ You can request it at two scopes:
+
+ | Scope | Effect |
+ |---|---|
+ | **Partner-wide** | Every outbound and inbound message on every phone number under your account is retained for 24 hours, then deleted. |
+ | **Per phone number** | Only the specified phone numbers have their messages auto-deleted. The rest follow the standard message-retention policy. |
+
+ **Behavioral differences vs the standard default:**
+
+ | Aspect | Standard | Ephemeral |
+ |---|---|---|
+ | Retention | Retained per the standard message-retention policy | **Hard backstop: 24 hours** from when the message is created |
+ | After expiry | Message stays retrievable | Message is permanently deleted — `GET /v3/messages/{messageId}` returns `404` and it no longer appears in `GET /v3/chats/{chatId}/messages` |
+ | Content on expiry | N/A | Text, formatting, and attachment references are scrubbed; the message is gone, not blanked out |
+ | Cross-partner isolation | Enforced | Enforced |
+
+ **How the 24-hour window works:**
+
+ - The window is fixed at **24 hours from message creation** (`created_at`) and cannot be configured per message.
+ - It mirrors the ephemeral *attachments* 1-day backstop, so a message and any media it carries expire together.
+ - Expiry is delivery-independent — the clock starts when the message is created, not when it is delivered or read.
+
+ **What you observe:**
+
+ - **No expiry timestamp is exposed.** API responses and webhook payloads do not include the deletion time. If you need it, compute `created_at + 24h` yourself.
+ - **No deletion webhook is sent.** There is no `message.deleted` event — a message simply stops being retrievable once its window passes.
+ - **Delivery is unaffected.** Ephemeral messages send, deliver, and fire the usual `message.sent` / `message.received` and status webhooks exactly like standard messages. Only retention changes.
+
+ **When to choose ephemeral:**
+
+ - You have a compliance requirement that the platform must not retain message content beyond a short window.
+ - The conversation is high-sensitivity (PHI, financial, identity verification) and you do not want it sitting in storage long-term.
+ - Your application is the system of record — you capture what you need from the delivery webhook in real time and do not rely on reading message history back from Linq later.
+
+ **Important:** ephemeral applies in *both directions* — messages you send **and** messages received by the phone numbers in that scope. Because Linq can no longer return the message after 24 hours, persist anything you need to keep from the webhook payload at the time it is delivered.
+ """
+ return PollResourceWithRawResponse(self._messages.poll)
+
class AsyncMessagesResourceWithRawResponse:
def __init__(self, messages: AsyncMessagesResource) -> None:
@@ -1208,6 +1419,69 @@ def __init__(self, messages: AsyncMessagesResource) -> None:
messages.update_app_card,
)
+ @cached_property
+ def poll(self) -> AsyncPollResourceWithRawResponse:
+ """Messages are individual communications within a chat thread.
+
+ Messages can include text, media attachments, rich link previews, special effects
+ (like confetti or fireworks), and reactions. All messages are associated with a
+ specific chat and sent from a phone number you own.
+
+ Messages support delivery status tracking, read receipts, and editing capabilities.
+
+ ## Rich Link Previews
+
+ Send a URL as a `link` part to deliver it with a rich preview card showing the
+ page's title, description, and image (when available). A `link` part must be the
+ **only** part in the message — it cannot be combined with text or media parts.
+ To send a URL without a preview card, include it in a `text` part instead.
+
+ **Limitations:**
+ - A `link` part cannot be combined with other parts in the same message.
+ - Maximum URL length: 2,048 characters.
+
+ ## Ephemeral Messages (Privacy Tier)
+
+ For regulated or sensitive conversations, opt in to the **ephemeral messages** tier by contacting your Linq support contact. When enabled, every message on the covered phone numbers is automatically given a fixed **24-hour retention window** — after that window the platform permanently deletes the message from Linq storage. There is no per-message flag; ephemerality is applied automatically based on your configuration.
+
+ You can request it at two scopes:
+
+ | Scope | Effect |
+ |---|---|
+ | **Partner-wide** | Every outbound and inbound message on every phone number under your account is retained for 24 hours, then deleted. |
+ | **Per phone number** | Only the specified phone numbers have their messages auto-deleted. The rest follow the standard message-retention policy. |
+
+ **Behavioral differences vs the standard default:**
+
+ | Aspect | Standard | Ephemeral |
+ |---|---|---|
+ | Retention | Retained per the standard message-retention policy | **Hard backstop: 24 hours** from when the message is created |
+ | After expiry | Message stays retrievable | Message is permanently deleted — `GET /v3/messages/{messageId}` returns `404` and it no longer appears in `GET /v3/chats/{chatId}/messages` |
+ | Content on expiry | N/A | Text, formatting, and attachment references are scrubbed; the message is gone, not blanked out |
+ | Cross-partner isolation | Enforced | Enforced |
+
+ **How the 24-hour window works:**
+
+ - The window is fixed at **24 hours from message creation** (`created_at`) and cannot be configured per message.
+ - It mirrors the ephemeral *attachments* 1-day backstop, so a message and any media it carries expire together.
+ - Expiry is delivery-independent — the clock starts when the message is created, not when it is delivered or read.
+
+ **What you observe:**
+
+ - **No expiry timestamp is exposed.** API responses and webhook payloads do not include the deletion time. If you need it, compute `created_at + 24h` yourself.
+ - **No deletion webhook is sent.** There is no `message.deleted` event — a message simply stops being retrievable once its window passes.
+ - **Delivery is unaffected.** Ephemeral messages send, deliver, and fire the usual `message.sent` / `message.received` and status webhooks exactly like standard messages. Only retention changes.
+
+ **When to choose ephemeral:**
+
+ - You have a compliance requirement that the platform must not retain message content beyond a short window.
+ - The conversation is high-sensitivity (PHI, financial, identity verification) and you do not want it sitting in storage long-term.
+ - Your application is the system of record — you capture what you need from the delivery webhook in real time and do not rely on reading message history back from Linq later.
+
+ **Important:** ephemeral applies in *both directions* — messages you send **and** messages received by the phone numbers in that scope. Because Linq can no longer return the message after 24 hours, persist anything you need to keep from the webhook payload at the time it is delivered.
+ """
+ return AsyncPollResourceWithRawResponse(self._messages.poll)
+
class MessagesResourceWithStreamingResponse:
def __init__(self, messages: MessagesResource) -> None:
@@ -1235,6 +1509,69 @@ def __init__(self, messages: MessagesResource) -> None:
messages.update_app_card,
)
+ @cached_property
+ def poll(self) -> PollResourceWithStreamingResponse:
+ """Messages are individual communications within a chat thread.
+
+ Messages can include text, media attachments, rich link previews, special effects
+ (like confetti or fireworks), and reactions. All messages are associated with a
+ specific chat and sent from a phone number you own.
+
+ Messages support delivery status tracking, read receipts, and editing capabilities.
+
+ ## Rich Link Previews
+
+ Send a URL as a `link` part to deliver it with a rich preview card showing the
+ page's title, description, and image (when available). A `link` part must be the
+ **only** part in the message — it cannot be combined with text or media parts.
+ To send a URL without a preview card, include it in a `text` part instead.
+
+ **Limitations:**
+ - A `link` part cannot be combined with other parts in the same message.
+ - Maximum URL length: 2,048 characters.
+
+ ## Ephemeral Messages (Privacy Tier)
+
+ For regulated or sensitive conversations, opt in to the **ephemeral messages** tier by contacting your Linq support contact. When enabled, every message on the covered phone numbers is automatically given a fixed **24-hour retention window** — after that window the platform permanently deletes the message from Linq storage. There is no per-message flag; ephemerality is applied automatically based on your configuration.
+
+ You can request it at two scopes:
+
+ | Scope | Effect |
+ |---|---|
+ | **Partner-wide** | Every outbound and inbound message on every phone number under your account is retained for 24 hours, then deleted. |
+ | **Per phone number** | Only the specified phone numbers have their messages auto-deleted. The rest follow the standard message-retention policy. |
+
+ **Behavioral differences vs the standard default:**
+
+ | Aspect | Standard | Ephemeral |
+ |---|---|---|
+ | Retention | Retained per the standard message-retention policy | **Hard backstop: 24 hours** from when the message is created |
+ | After expiry | Message stays retrievable | Message is permanently deleted — `GET /v3/messages/{messageId}` returns `404` and it no longer appears in `GET /v3/chats/{chatId}/messages` |
+ | Content on expiry | N/A | Text, formatting, and attachment references are scrubbed; the message is gone, not blanked out |
+ | Cross-partner isolation | Enforced | Enforced |
+
+ **How the 24-hour window works:**
+
+ - The window is fixed at **24 hours from message creation** (`created_at`) and cannot be configured per message.
+ - It mirrors the ephemeral *attachments* 1-day backstop, so a message and any media it carries expire together.
+ - Expiry is delivery-independent — the clock starts when the message is created, not when it is delivered or read.
+
+ **What you observe:**
+
+ - **No expiry timestamp is exposed.** API responses and webhook payloads do not include the deletion time. If you need it, compute `created_at + 24h` yourself.
+ - **No deletion webhook is sent.** There is no `message.deleted` event — a message simply stops being retrievable once its window passes.
+ - **Delivery is unaffected.** Ephemeral messages send, deliver, and fire the usual `message.sent` / `message.received` and status webhooks exactly like standard messages. Only retention changes.
+
+ **When to choose ephemeral:**
+
+ - You have a compliance requirement that the platform must not retain message content beyond a short window.
+ - The conversation is high-sensitivity (PHI, financial, identity verification) and you do not want it sitting in storage long-term.
+ - Your application is the system of record — you capture what you need from the delivery webhook in real time and do not rely on reading message history back from Linq later.
+
+ **Important:** ephemeral applies in *both directions* — messages you send **and** messages received by the phone numbers in that scope. Because Linq can no longer return the message after 24 hours, persist anything you need to keep from the webhook payload at the time it is delivered.
+ """
+ return PollResourceWithStreamingResponse(self._messages.poll)
+
class AsyncMessagesResourceWithStreamingResponse:
def __init__(self, messages: AsyncMessagesResource) -> None:
@@ -1261,3 +1598,66 @@ def __init__(self, messages: AsyncMessagesResource) -> None:
self.update_app_card = async_to_streamed_response_wrapper(
messages.update_app_card,
)
+
+ @cached_property
+ def poll(self) -> AsyncPollResourceWithStreamingResponse:
+ """Messages are individual communications within a chat thread.
+
+ Messages can include text, media attachments, rich link previews, special effects
+ (like confetti or fireworks), and reactions. All messages are associated with a
+ specific chat and sent from a phone number you own.
+
+ Messages support delivery status tracking, read receipts, and editing capabilities.
+
+ ## Rich Link Previews
+
+ Send a URL as a `link` part to deliver it with a rich preview card showing the
+ page's title, description, and image (when available). A `link` part must be the
+ **only** part in the message — it cannot be combined with text or media parts.
+ To send a URL without a preview card, include it in a `text` part instead.
+
+ **Limitations:**
+ - A `link` part cannot be combined with other parts in the same message.
+ - Maximum URL length: 2,048 characters.
+
+ ## Ephemeral Messages (Privacy Tier)
+
+ For regulated or sensitive conversations, opt in to the **ephemeral messages** tier by contacting your Linq support contact. When enabled, every message on the covered phone numbers is automatically given a fixed **24-hour retention window** — after that window the platform permanently deletes the message from Linq storage. There is no per-message flag; ephemerality is applied automatically based on your configuration.
+
+ You can request it at two scopes:
+
+ | Scope | Effect |
+ |---|---|
+ | **Partner-wide** | Every outbound and inbound message on every phone number under your account is retained for 24 hours, then deleted. |
+ | **Per phone number** | Only the specified phone numbers have their messages auto-deleted. The rest follow the standard message-retention policy. |
+
+ **Behavioral differences vs the standard default:**
+
+ | Aspect | Standard | Ephemeral |
+ |---|---|---|
+ | Retention | Retained per the standard message-retention policy | **Hard backstop: 24 hours** from when the message is created |
+ | After expiry | Message stays retrievable | Message is permanently deleted — `GET /v3/messages/{messageId}` returns `404` and it no longer appears in `GET /v3/chats/{chatId}/messages` |
+ | Content on expiry | N/A | Text, formatting, and attachment references are scrubbed; the message is gone, not blanked out |
+ | Cross-partner isolation | Enforced | Enforced |
+
+ **How the 24-hour window works:**
+
+ - The window is fixed at **24 hours from message creation** (`created_at`) and cannot be configured per message.
+ - It mirrors the ephemeral *attachments* 1-day backstop, so a message and any media it carries expire together.
+ - Expiry is delivery-independent — the clock starts when the message is created, not when it is delivered or read.
+
+ **What you observe:**
+
+ - **No expiry timestamp is exposed.** API responses and webhook payloads do not include the deletion time. If you need it, compute `created_at + 24h` yourself.
+ - **No deletion webhook is sent.** There is no `message.deleted` event — a message simply stops being retrievable once its window passes.
+ - **Delivery is unaffected.** Ephemeral messages send, deliver, and fire the usual `message.sent` / `message.received` and status webhooks exactly like standard messages. Only retention changes.
+
+ **When to choose ephemeral:**
+
+ - You have a compliance requirement that the platform must not retain message content beyond a short window.
+ - The conversation is high-sensitivity (PHI, financial, identity verification) and you do not want it sitting in storage long-term.
+ - Your application is the system of record — you capture what you need from the delivery webhook in real time and do not rely on reading message history back from Linq later.
+
+ **Important:** ephemeral applies in *both directions* — messages you send **and** messages received by the phone numbers in that scope. Because Linq can no longer return the message after 24 hours, persist anything you need to keep from the webhook payload at the time it is delivered.
+ """
+ return AsyncPollResourceWithStreamingResponse(self._messages.poll)
diff --git a/src/linq/resources/messages/poll.py b/src/linq/resources/messages/poll.py
new file mode 100644
index 0000000..078d77c
--- /dev/null
+++ b/src/linq/resources/messages/poll.py
@@ -0,0 +1,486 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable
+from typing_extensions import Literal
+
+import httpx
+
+from ..._types import Body, Query, Headers, NotGiven, not_given
+from ..._utils import path_template, maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.messages import poll_vote_params, poll_add_options_params
+from ...types.chats.poll_envelope import PollEnvelope
+
+__all__ = ["PollResource", "AsyncPollResource"]
+
+
+class PollResource(SyncAPIResource):
+ """Messages are individual communications within a chat thread.
+
+ Messages can include text, media attachments, rich link previews, special effects
+ (like confetti or fireworks), and reactions. All messages are associated with a
+ specific chat and sent from a phone number you own.
+
+ Messages support delivery status tracking, read receipts, and editing capabilities.
+
+ ## Rich Link Previews
+
+ Send a URL as a `link` part to deliver it with a rich preview card showing the
+ page's title, description, and image (when available). A `link` part must be the
+ **only** part in the message — it cannot be combined with text or media parts.
+ To send a URL without a preview card, include it in a `text` part instead.
+
+ **Limitations:**
+ - A `link` part cannot be combined with other parts in the same message.
+ - Maximum URL length: 2,048 characters.
+
+ ## Ephemeral Messages (Privacy Tier)
+
+ For regulated or sensitive conversations, opt in to the **ephemeral messages** tier by contacting your Linq support contact. When enabled, every message on the covered phone numbers is automatically given a fixed **24-hour retention window** — after that window the platform permanently deletes the message from Linq storage. There is no per-message flag; ephemerality is applied automatically based on your configuration.
+
+ You can request it at two scopes:
+
+ | Scope | Effect |
+ |---|---|
+ | **Partner-wide** | Every outbound and inbound message on every phone number under your account is retained for 24 hours, then deleted. |
+ | **Per phone number** | Only the specified phone numbers have their messages auto-deleted. The rest follow the standard message-retention policy. |
+
+ **Behavioral differences vs the standard default:**
+
+ | Aspect | Standard | Ephemeral |
+ |---|---|---|
+ | Retention | Retained per the standard message-retention policy | **Hard backstop: 24 hours** from when the message is created |
+ | After expiry | Message stays retrievable | Message is permanently deleted — `GET /v3/messages/{messageId}` returns `404` and it no longer appears in `GET /v3/chats/{chatId}/messages` |
+ | Content on expiry | N/A | Text, formatting, and attachment references are scrubbed; the message is gone, not blanked out |
+ | Cross-partner isolation | Enforced | Enforced |
+
+ **How the 24-hour window works:**
+
+ - The window is fixed at **24 hours from message creation** (`created_at`) and cannot be configured per message.
+ - It mirrors the ephemeral *attachments* 1-day backstop, so a message and any media it carries expire together.
+ - Expiry is delivery-independent — the clock starts when the message is created, not when it is delivered or read.
+
+ **What you observe:**
+
+ - **No expiry timestamp is exposed.** API responses and webhook payloads do not include the deletion time. If you need it, compute `created_at + 24h` yourself.
+ - **No deletion webhook is sent.** There is no `message.deleted` event — a message simply stops being retrievable once its window passes.
+ - **Delivery is unaffected.** Ephemeral messages send, deliver, and fire the usual `message.sent` / `message.received` and status webhooks exactly like standard messages. Only retention changes.
+
+ **When to choose ephemeral:**
+
+ - You have a compliance requirement that the platform must not retain message content beyond a short window.
+ - The conversation is high-sensitivity (PHI, financial, identity verification) and you do not want it sitting in storage long-term.
+ - Your application is the system of record — you capture what you need from the delivery webhook in real time and do not rely on reading message history back from Linq later.
+
+ **Important:** ephemeral applies in *both directions* — messages you send **and** messages received by the phone numbers in that scope. Because Linq can no longer return the message after 24 hours, persist anything you need to keep from the webhook payload at the time it is delivered.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> PollResourceWithRawResponse:
+ """
+ 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 PollResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> PollResourceWithStreamingResponse:
+ """
+ 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 PollResourceWithStreamingResponse(self)
+
+ def retrieve(
+ self,
+ message_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PollEnvelope:
+ """
+ Return a poll's current results — its options, each option's voters, and the
+ distinct total number of voters — by the poll-definition message's ID.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not message_id:
+ raise ValueError(f"Expected a non-empty value for `message_id` but received {message_id!r}")
+ return self._get(
+ path_template("/v3/messages/{message_id}/poll", message_id=message_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PollEnvelope,
+ )
+
+ def add_options(
+ self,
+ message_id: str,
+ *,
+ options: Iterable[poll_add_options_params.Option],
+ # 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,
+ ) -> PollEnvelope:
+ """Add one or more options to an existing poll.
+
+ Options are **add-only and
+ immutable** — you can append options but never edit or remove them (Apple
+ constraint). Returns the full poll.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not message_id:
+ raise ValueError(f"Expected a non-empty value for `message_id` but received {message_id!r}")
+ return self._post(
+ path_template("/v3/messages/{message_id}/poll/options", message_id=message_id),
+ body=maybe_transform({"options": options}, poll_add_options_params.PollAddOptionsParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PollEnvelope,
+ )
+
+ def vote(
+ self,
+ message_id: str,
+ *,
+ operation: Literal["add", "remove"],
+ option_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PollEnvelope:
+ """
+ Add or remove your line's vote on **one** poll option (per-option toggle —
+ iMessage polls are toggled one option at a time). Returns the poll reflecting
+ the toggle.
+
+ Args:
+ operation: Add or remove your line's vote on the option.
+
+ option_id: The option to toggle a vote on.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not message_id:
+ raise ValueError(f"Expected a non-empty value for `message_id` but received {message_id!r}")
+ return self._post(
+ path_template("/v3/messages/{message_id}/poll/votes", message_id=message_id),
+ body=maybe_transform(
+ {
+ "operation": operation,
+ "option_id": option_id,
+ },
+ poll_vote_params.PollVoteParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PollEnvelope,
+ )
+
+
+class AsyncPollResource(AsyncAPIResource):
+ """Messages are individual communications within a chat thread.
+
+ Messages can include text, media attachments, rich link previews, special effects
+ (like confetti or fireworks), and reactions. All messages are associated with a
+ specific chat and sent from a phone number you own.
+
+ Messages support delivery status tracking, read receipts, and editing capabilities.
+
+ ## Rich Link Previews
+
+ Send a URL as a `link` part to deliver it with a rich preview card showing the
+ page's title, description, and image (when available). A `link` part must be the
+ **only** part in the message — it cannot be combined with text or media parts.
+ To send a URL without a preview card, include it in a `text` part instead.
+
+ **Limitations:**
+ - A `link` part cannot be combined with other parts in the same message.
+ - Maximum URL length: 2,048 characters.
+
+ ## Ephemeral Messages (Privacy Tier)
+
+ For regulated or sensitive conversations, opt in to the **ephemeral messages** tier by contacting your Linq support contact. When enabled, every message on the covered phone numbers is automatically given a fixed **24-hour retention window** — after that window the platform permanently deletes the message from Linq storage. There is no per-message flag; ephemerality is applied automatically based on your configuration.
+
+ You can request it at two scopes:
+
+ | Scope | Effect |
+ |---|---|
+ | **Partner-wide** | Every outbound and inbound message on every phone number under your account is retained for 24 hours, then deleted. |
+ | **Per phone number** | Only the specified phone numbers have their messages auto-deleted. The rest follow the standard message-retention policy. |
+
+ **Behavioral differences vs the standard default:**
+
+ | Aspect | Standard | Ephemeral |
+ |---|---|---|
+ | Retention | Retained per the standard message-retention policy | **Hard backstop: 24 hours** from when the message is created |
+ | After expiry | Message stays retrievable | Message is permanently deleted — `GET /v3/messages/{messageId}` returns `404` and it no longer appears in `GET /v3/chats/{chatId}/messages` |
+ | Content on expiry | N/A | Text, formatting, and attachment references are scrubbed; the message is gone, not blanked out |
+ | Cross-partner isolation | Enforced | Enforced |
+
+ **How the 24-hour window works:**
+
+ - The window is fixed at **24 hours from message creation** (`created_at`) and cannot be configured per message.
+ - It mirrors the ephemeral *attachments* 1-day backstop, so a message and any media it carries expire together.
+ - Expiry is delivery-independent — the clock starts when the message is created, not when it is delivered or read.
+
+ **What you observe:**
+
+ - **No expiry timestamp is exposed.** API responses and webhook payloads do not include the deletion time. If you need it, compute `created_at + 24h` yourself.
+ - **No deletion webhook is sent.** There is no `message.deleted` event — a message simply stops being retrievable once its window passes.
+ - **Delivery is unaffected.** Ephemeral messages send, deliver, and fire the usual `message.sent` / `message.received` and status webhooks exactly like standard messages. Only retention changes.
+
+ **When to choose ephemeral:**
+
+ - You have a compliance requirement that the platform must not retain message content beyond a short window.
+ - The conversation is high-sensitivity (PHI, financial, identity verification) and you do not want it sitting in storage long-term.
+ - Your application is the system of record — you capture what you need from the delivery webhook in real time and do not rely on reading message history back from Linq later.
+
+ **Important:** ephemeral applies in *both directions* — messages you send **and** messages received by the phone numbers in that scope. Because Linq can no longer return the message after 24 hours, persist anything you need to keep from the webhook payload at the time it is delivered.
+ """
+
+ @cached_property
+ def with_raw_response(self) -> AsyncPollResourceWithRawResponse:
+ """
+ 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 AsyncPollResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncPollResourceWithStreamingResponse:
+ """
+ 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 AsyncPollResourceWithStreamingResponse(self)
+
+ async def retrieve(
+ self,
+ message_id: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PollEnvelope:
+ """
+ Return a poll's current results — its options, each option's voters, and the
+ distinct total number of voters — by the poll-definition message's ID.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not message_id:
+ raise ValueError(f"Expected a non-empty value for `message_id` but received {message_id!r}")
+ return await self._get(
+ path_template("/v3/messages/{message_id}/poll", message_id=message_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PollEnvelope,
+ )
+
+ async def add_options(
+ self,
+ message_id: str,
+ *,
+ options: Iterable[poll_add_options_params.Option],
+ # 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,
+ ) -> PollEnvelope:
+ """Add one or more options to an existing poll.
+
+ Options are **add-only and
+ immutable** — you can append options but never edit or remove them (Apple
+ constraint). Returns the full poll.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not message_id:
+ raise ValueError(f"Expected a non-empty value for `message_id` but received {message_id!r}")
+ return await self._post(
+ path_template("/v3/messages/{message_id}/poll/options", message_id=message_id),
+ body=await async_maybe_transform({"options": options}, poll_add_options_params.PollAddOptionsParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PollEnvelope,
+ )
+
+ async def vote(
+ self,
+ message_id: str,
+ *,
+ operation: Literal["add", "remove"],
+ option_id: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PollEnvelope:
+ """
+ Add or remove your line's vote on **one** poll option (per-option toggle —
+ iMessage polls are toggled one option at a time). Returns the poll reflecting
+ the toggle.
+
+ Args:
+ operation: Add or remove your line's vote on the option.
+
+ option_id: The option to toggle a vote on.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not message_id:
+ raise ValueError(f"Expected a non-empty value for `message_id` but received {message_id!r}")
+ return await self._post(
+ path_template("/v3/messages/{message_id}/poll/votes", message_id=message_id),
+ body=await async_maybe_transform(
+ {
+ "operation": operation,
+ "option_id": option_id,
+ },
+ poll_vote_params.PollVoteParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PollEnvelope,
+ )
+
+
+class PollResourceWithRawResponse:
+ def __init__(self, poll: PollResource) -> None:
+ self._poll = poll
+
+ self.retrieve = to_raw_response_wrapper(
+ poll.retrieve,
+ )
+ self.add_options = to_raw_response_wrapper(
+ poll.add_options,
+ )
+ self.vote = to_raw_response_wrapper(
+ poll.vote,
+ )
+
+
+class AsyncPollResourceWithRawResponse:
+ def __init__(self, poll: AsyncPollResource) -> None:
+ self._poll = poll
+
+ self.retrieve = async_to_raw_response_wrapper(
+ poll.retrieve,
+ )
+ self.add_options = async_to_raw_response_wrapper(
+ poll.add_options,
+ )
+ self.vote = async_to_raw_response_wrapper(
+ poll.vote,
+ )
+
+
+class PollResourceWithStreamingResponse:
+ def __init__(self, poll: PollResource) -> None:
+ self._poll = poll
+
+ self.retrieve = to_streamed_response_wrapper(
+ poll.retrieve,
+ )
+ self.add_options = to_streamed_response_wrapper(
+ poll.add_options,
+ )
+ self.vote = to_streamed_response_wrapper(
+ poll.vote,
+ )
+
+
+class AsyncPollResourceWithStreamingResponse:
+ def __init__(self, poll: AsyncPollResource) -> None:
+ self._poll = poll
+
+ self.retrieve = async_to_streamed_response_wrapper(
+ poll.retrieve,
+ )
+ self.add_options = async_to_streamed_response_wrapper(
+ poll.add_options,
+ )
+ self.vote = async_to_streamed_response_wrapper(
+ poll.vote,
+ )
diff --git a/src/linq/types/chat.py b/src/linq/types/chat.py
index 2d5fa35..f8f5d1f 100644
--- a/src/linq/types/chat.py
+++ b/src/linq/types/chat.py
@@ -31,15 +31,19 @@ class HealthStatus(BaseModel):
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`, and you should send nothing further on this chat.
- 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. Suppressing sends to opted-out recipients is
- your responsibility — Linq surfaces the status but does not block the send.
+ `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.
+
+ 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
+ every chat and every line on your account. Nothing is delivered, including a
+ final courtesy message — to send one, set `override_optout: true` on that single
+ request.
"""
updated_at: datetime
diff --git a/src/linq/types/chat_create_params.py b/src/linq/types/chat_create_params.py
index a549e03..30ce575 100644
--- a/src/linq/types/chat_create_params.py
+++ b/src/linq/types/chat_create_params.py
@@ -36,3 +36,11 @@ class ChatCreateParams(TypedDict, total=False):
Array of recipient handles (phone numbers in E.164 format or email addresses).
For individual chats, provide one recipient. For group chats, provide multiple.
"""
+
+ override_optout: bool
+ """Send even though the recipient asked you to stop (`403`, error code `2024`).
+
+ Applies to this request only: the opt-out stays in place, so the next send
+ without this flag is rejected again. Every override is recorded against your API
+ key.
+ """
diff --git a/src/linq/types/chat_create_response.py b/src/linq/types/chat_create_response.py
index 35410e3..e1c34ed 100644
--- a/src/linq/types/chat_create_response.py
+++ b/src/linq/types/chat_create_response.py
@@ -32,15 +32,19 @@ class ChatHealthStatus(BaseModel):
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`, and you should send nothing further on this chat.
- 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. Suppressing sends to opted-out recipients is
- your responsibility — Linq surfaces the status but does not block the send.
+ `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.
+
+ 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
+ every chat and every line on your account. Nothing is delivered, including a
+ final courtesy message — to send one, set `override_optout: true` on that single
+ request.
"""
updated_at: datetime
diff --git a/src/linq/types/chat_created_webhook_event.py b/src/linq/types/chat_created_webhook_event.py
index 2aa622a..0522edf 100644
--- a/src/linq/types/chat_created_webhook_event.py
+++ b/src/linq/types/chat_created_webhook_event.py
@@ -32,15 +32,19 @@ class DataHealthStatus(BaseModel):
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`, and you should send nothing further on this chat.
- 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. Suppressing sends to opted-out recipients is
- your responsibility — Linq surfaces the status but does not block the send.
+ `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.
+
+ 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
+ every chat and every line on your account. Nothing is delivered, including a
+ final courtesy message — to send one, set `override_optout: true` on that single
+ request.
"""
updated_at: datetime
diff --git a/src/linq/types/chat_send_voicememo_params.py b/src/linq/types/chat_send_voicememo_params.py
index 0a0d15b..7d8ed4a 100644
--- a/src/linq/types/chat_send_voicememo_params.py
+++ b/src/linq/types/chat_send_voicememo_params.py
@@ -16,6 +16,14 @@ class ChatSendVoicememoParams(TypedDict, total=False):
Either `voice_memo_url` or `attachment_id` must be provided, but not both.
"""
+ override_optout: bool
+ """Send even though the recipient asked you to stop (`403`, error code `2024`).
+
+ Applies to this request only: the opt-out stays in place, so the next send
+ without this flag is rejected again. Every override is recorded against your API
+ key.
+ """
+
voice_memo_url: str
"""URL of the voice memo audio file. Must be a publicly accessible HTTPS URL.
diff --git a/src/linq/types/chats/__init__.py b/src/linq/types/chats/__init__.py
index b91a16e..693bdc4 100644
--- a/src/linq/types/chats/__init__.py
+++ b/src/linq/types/chats/__init__.py
@@ -2,7 +2,10 @@
from __future__ import annotations
+from .poll import Poll as Poll
from .sent_message import SentMessage as SentMessage
+from .poll_envelope import PollEnvelope as PollEnvelope
+from .poll_create_params import PollCreateParams as PollCreateParams
from .message_list_params import MessageListParams as MessageListParams
from .message_send_params import MessageSendParams as MessageSendParams
from .message_send_response import MessageSendResponse as MessageSendResponse
diff --git a/src/linq/types/chats/message_send_params.py b/src/linq/types/chats/message_send_params.py
index 25f3fac..0c64fed 100644
--- a/src/linq/types/chats/message_send_params.py
+++ b/src/linq/types/chats/message_send_params.py
@@ -21,3 +21,11 @@ class MessageSendParams(TypedDict, total=False):
iMessage app. Never both: an app card is the whole message (Apple's `MSMessage`
cannot coexist with text), so copy and a card are two sends, not one.
"""
+
+ override_optout: bool
+ """Send even though the recipient asked you to stop (`403`, error code `2024`).
+
+ Applies to this request only: the opt-out stays in place, so the next send
+ without this flag is rejected again. Every override is recorded against your API
+ key.
+ """
diff --git a/src/linq/types/chats/poll.py b/src/linq/types/chats/poll.py
new file mode 100644
index 0000000..3670edc
--- /dev/null
+++ b/src/linq/types/chats/poll.py
@@ -0,0 +1,44 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+from datetime import datetime
+
+from ..._models import BaseModel
+from ..shared.chat_handle import ChatHandle
+
+__all__ = ["Poll", "Option", "OptionVoter"]
+
+
+class OptionVoter(BaseModel):
+ handle: str
+
+ voted_at: datetime
+
+
+class Option(BaseModel):
+ can_be_edited: bool
+
+ creator_handle: ChatHandle
+ """
+ The participant who added this option (poll creator for the initial options;
+ whoever added later ones).
+ """
+
+ option_id: str
+
+ text: str
+
+ voters: List[OptionVoter]
+ """Participants who voted for this option (vote_count = voters.length)."""
+
+
+class Poll(BaseModel):
+ """Poll content — options and the aggregate voter count."""
+
+ options: List[Option]
+
+ total_voters: int
+ """
+ Distinct participants across the whole poll (a voter picking two options counts
+ once).
+ """
diff --git a/src/linq/types/chats/poll_create_params.py b/src/linq/types/chats/poll_create_params.py
new file mode 100644
index 0000000..6108516
--- /dev/null
+++ b/src/linq/types/chats/poll_create_params.py
@@ -0,0 +1,34 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable
+from typing_extensions import Required, TypedDict
+
+__all__ = ["PollCreateParams", "Poll", "PollOption"]
+
+
+class PollCreateParams(TypedDict, total=False):
+ poll: Required[Poll]
+ """Poll content to create.
+
+ A poll needs at least two options. Options are add-only and immutable — there is
+ no title/question (send that as a normal text message).
+ """
+
+
+class PollOption(TypedDict, total=False):
+ text: Required[str]
+
+
+class Poll(TypedDict, total=False):
+ """Poll content to create.
+
+ A poll needs at least two options. Options are add-only and
+ immutable — there is no title/question (send that as a normal text message).
+ """
+
+ options: Required[Iterable[PollOption]]
+
+ idempotency_key: str
+ """Optional key to deduplicate the poll creation."""
diff --git a/src/linq/types/chats/poll_envelope.py b/src/linq/types/chats/poll_envelope.py
new file mode 100644
index 0000000..9d9bed6
--- /dev/null
+++ b/src/linq/types/chats/poll_envelope.py
@@ -0,0 +1,29 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+from datetime import datetime
+
+from .poll import Poll
+from ..._models import BaseModel
+from ..shared.reaction import Reaction
+
+__all__ = ["PollEnvelope"]
+
+
+class PollEnvelope(BaseModel):
+ """Message-level envelope returned by every poll endpoint."""
+
+ chat_id: str
+
+ created_at: datetime
+
+ message_id: str
+ """The poll-definition message's ID — reference this poll by it."""
+
+ poll: Poll
+ """Poll content — options and the aggregate voter count."""
+
+ reactions: List[Reaction]
+ """Tapbacks/stickers on the whole poll (message part 0)."""
+
+ updated_at: datetime
diff --git a/src/linq/types/message_create_params.py b/src/linq/types/message_create_params.py
index 939d9c4..a835ed1 100644
--- a/src/linq/types/message_create_params.py
+++ b/src/linq/types/message_create_params.py
@@ -59,6 +59,14 @@ class MessageCreateParams(TypedDict, total=False):
of your available lines returns 400 when a line has to be picked.
"""
+ override_optout: bool
+ """Send even though the recipient asked you to stop (`403`, error code `2024`).
+
+ Applies to this request only: the opt-out stays in place, so the next send
+ without this flag is rejected again. Every override is recorded against your API
+ key.
+ """
+
idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
diff --git a/src/linq/types/message_edited_webhook_event.py b/src/linq/types/message_edited_webhook_event.py
index b660613..4b52d49 100644
--- a/src/linq/types/message_edited_webhook_event.py
+++ b/src/linq/types/message_edited_webhook_event.py
@@ -30,15 +30,19 @@ class DataChatHealthStatus(BaseModel):
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`, and you should send nothing further on this chat.
- 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. Suppressing sends to opted-out recipients is
- your responsibility — Linq surfaces the status but does not block the send.
+ `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.
+
+ 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
+ every chat and every line on your account. Nothing is delivered, including a
+ final courtesy message — to send one, set `override_optout: true` on that single
+ request.
"""
updated_at: datetime
diff --git a/src/linq/types/message_event_v2.py b/src/linq/types/message_event_v2.py
index 070d2ad..fa20f95 100644
--- a/src/linq/types/message_event_v2.py
+++ b/src/linq/types/message_event_v2.py
@@ -45,15 +45,19 @@ class ChatHealthStatus(BaseModel):
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`, and you should send nothing further on this chat.
- 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. Suppressing sends to opted-out recipients is
- your responsibility — Linq surfaces the status but does not block the send.
+ `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.
+
+ 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
+ every chat and every line on your account. Nothing is delivered, including a
+ final courtesy message — to send one, set `override_optout: true` on that single
+ request.
"""
updated_at: datetime
diff --git a/src/linq/types/messages/__init__.py b/src/linq/types/messages/__init__.py
new file mode 100644
index 0000000..07efd43
--- /dev/null
+++ b/src/linq/types/messages/__init__.py
@@ -0,0 +1,6 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from .poll_vote_params import PollVoteParams as PollVoteParams
+from .poll_add_options_params import PollAddOptionsParams as PollAddOptionsParams
diff --git a/src/linq/types/messages/poll_add_options_params.py b/src/linq/types/messages/poll_add_options_params.py
new file mode 100644
index 0000000..37b4c2d
--- /dev/null
+++ b/src/linq/types/messages/poll_add_options_params.py
@@ -0,0 +1,16 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Iterable
+from typing_extensions import Required, TypedDict
+
+__all__ = ["PollAddOptionsParams", "Option"]
+
+
+class PollAddOptionsParams(TypedDict, total=False):
+ options: Required[Iterable[Option]]
+
+
+class Option(TypedDict, total=False):
+ text: Required[str]
diff --git a/src/linq/types/messages/poll_vote_params.py b/src/linq/types/messages/poll_vote_params.py
new file mode 100644
index 0000000..6cdc9ce
--- /dev/null
+++ b/src/linq/types/messages/poll_vote_params.py
@@ -0,0 +1,15 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["PollVoteParams"]
+
+
+class PollVoteParams(TypedDict, total=False):
+ operation: Required[Literal["add", "remove"]]
+ """Add or remove your line's vote on the option."""
+
+ option_id: Required[str]
+ """The option to toggle a vote on."""
diff --git a/src/linq/types/phone_number_status_updated_webhook_event.py b/src/linq/types/phone_number_status_updated_webhook_event.py
index b552c9c..ef1d958 100644
--- a/src/linq/types/phone_number_status_updated_webhook_event.py
+++ b/src/linq/types/phone_number_status_updated_webhook_event.py
@@ -54,6 +54,15 @@ class PhoneNumberStatusUpdatedWebhookEvent(BaseModel):
"message.edited",
"reaction.added",
"reaction.removed",
+ "poll.received",
+ "poll.failed",
+ "poll.sent",
+ "poll.delivered",
+ "poll.read",
+ "poll.updated",
+ "poll.vote.added",
+ "poll.vote.removed",
+ "poll.reaction.added",
"participant.added",
"participant.removed",
"chat.created",
diff --git a/src/linq/types/text_part_param.py b/src/linq/types/text_part_param.py
index a46fb93..ee86b08 100644
--- a/src/linq/types/text_part_param.py
+++ b/src/linq/types/text_part_param.py
@@ -22,6 +22,27 @@ class TextPartParam(TypedDict, total=False):
formatting and animations (iMessage only).
"""
+ mention: str
+ """@mention a chat participant (iMessage group chats only).
+
+ Set to their handle — E.164 phone number or Apple ID email. `value` is the
+ display text; use the bare name (`"Juan"`, not `"@Juan"`). The mentioned
+ participant is notified even if the chat is muted. Falls back to plain text over
+ SMS/RCS.
+
+ By default the entire `value` renders as the mention; use `mention_range` to
+ highlight only part of it.
+ """
+
+ mention_range: Iterable[int]
+ """
+ Optional character range `[start, end)` in `value` that renders as the `mention`
+ highlight (e.g. just the name in `"Hey Kevin, can you look at this?"`). Requires
+ `mention`. Without it, the entire `value` is highlighted. `start` is inclusive,
+ `end` is exclusive. _Characters are measured as UTF-16 code units. Most
+ characters count as 1; some emoji count as 2._
+ """
+
text_decorations: Iterable[TextDecoration]
"""
Optional array of text decorations applied to character ranges in the `value`
diff --git a/src/linq/types/webhook_event_type.py b/src/linq/types/webhook_event_type.py
index ade2e5d..6210535 100644
--- a/src/linq/types/webhook_event_type.py
+++ b/src/linq/types/webhook_event_type.py
@@ -13,6 +13,15 @@
"message.edited",
"reaction.added",
"reaction.removed",
+ "poll.received",
+ "poll.failed",
+ "poll.sent",
+ "poll.delivered",
+ "poll.read",
+ "poll.updated",
+ "poll.vote.added",
+ "poll.vote.removed",
+ "poll.reaction.added",
"participant.added",
"participant.removed",
"chat.created",
diff --git a/tests/api_resources/chats/test_messages.py b/tests/api_resources/chats/test_messages.py
index 8dea3b6..ec67e16 100644
--- a/tests/api_resources/chats/test_messages.py
+++ b/tests/api_resources/chats/test_messages.py
@@ -100,6 +100,8 @@ def test_method_send_with_all_params(self, client: LinqAPIV3) -> None:
{
"type": "text",
"value": "Hello, world!",
+ "mention": "+14155551234",
+ "mention_range": [4, 9],
"text_decorations": [
{
"range": [0, 5],
@@ -120,6 +122,7 @@ def test_method_send_with_all_params(self, client: LinqAPIV3) -> None:
"part_index": 0,
},
},
+ override_optout=False,
)
assert_matches_type(MessageSendResponse, message, path=["response"])
@@ -247,6 +250,8 @@ async def test_method_send_with_all_params(self, async_client: AsyncLinqAPIV3) -
{
"type": "text",
"value": "Hello, world!",
+ "mention": "+14155551234",
+ "mention_range": [4, 9],
"text_decorations": [
{
"range": [0, 5],
@@ -267,6 +272,7 @@ async def test_method_send_with_all_params(self, async_client: AsyncLinqAPIV3) -
"part_index": 0,
},
},
+ override_optout=False,
)
assert_matches_type(MessageSendResponse, message, path=["response"])
diff --git a/tests/api_resources/chats/test_polls.py b/tests/api_resources/chats/test_polls.py
new file mode 100644
index 0000000..a29e405
--- /dev/null
+++ b/tests/api_resources/chats/test_polls.py
@@ -0,0 +1,140 @@
+# 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 tests.utils import assert_matches_type
+from linq.types.chats import PollEnvelope
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestPolls:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_create(self, client: LinqAPIV3) -> None:
+ poll = client.chats.polls.create(
+ chat_id="550e8400-e29b-41d4-a716-446655440000",
+ poll={"options": [{"text": "Tacos"}, {"text": "Sushi"}]},
+ )
+ assert_matches_type(PollEnvelope, poll, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_create_with_all_params(self, client: LinqAPIV3) -> None:
+ poll = client.chats.polls.create(
+ chat_id="550e8400-e29b-41d4-a716-446655440000",
+ poll={
+ "options": [{"text": "Tacos"}, {"text": "Sushi"}],
+ "idempotency_key": "poll-abc123",
+ },
+ )
+ assert_matches_type(PollEnvelope, poll, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_create(self, client: LinqAPIV3) -> None:
+ response = client.chats.polls.with_raw_response.create(
+ chat_id="550e8400-e29b-41d4-a716-446655440000",
+ poll={"options": [{"text": "Tacos"}, {"text": "Sushi"}]},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ poll = response.parse()
+ assert_matches_type(PollEnvelope, poll, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_create(self, client: LinqAPIV3) -> None:
+ with client.chats.polls.with_streaming_response.create(
+ chat_id="550e8400-e29b-41d4-a716-446655440000",
+ poll={"options": [{"text": "Tacos"}, {"text": "Sushi"}]},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ poll = response.parse()
+ assert_matches_type(PollEnvelope, poll, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_create(self, client: LinqAPIV3) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `chat_id` but received ''"):
+ client.chats.polls.with_raw_response.create(
+ chat_id="",
+ poll={"options": [{"text": "Tacos"}, {"text": "Sushi"}]},
+ )
+
+
+class TestAsyncPolls:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_create(self, async_client: AsyncLinqAPIV3) -> None:
+ poll = await async_client.chats.polls.create(
+ chat_id="550e8400-e29b-41d4-a716-446655440000",
+ poll={"options": [{"text": "Tacos"}, {"text": "Sushi"}]},
+ )
+ assert_matches_type(PollEnvelope, poll, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncLinqAPIV3) -> None:
+ poll = await async_client.chats.polls.create(
+ chat_id="550e8400-e29b-41d4-a716-446655440000",
+ poll={
+ "options": [{"text": "Tacos"}, {"text": "Sushi"}],
+ "idempotency_key": "poll-abc123",
+ },
+ )
+ assert_matches_type(PollEnvelope, poll, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncLinqAPIV3) -> None:
+ response = await async_client.chats.polls.with_raw_response.create(
+ chat_id="550e8400-e29b-41d4-a716-446655440000",
+ poll={"options": [{"text": "Tacos"}, {"text": "Sushi"}]},
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ poll = await response.parse()
+ assert_matches_type(PollEnvelope, poll, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncLinqAPIV3) -> None:
+ async with async_client.chats.polls.with_streaming_response.create(
+ chat_id="550e8400-e29b-41d4-a716-446655440000",
+ poll={"options": [{"text": "Tacos"}, {"text": "Sushi"}]},
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ poll = await response.parse()
+ assert_matches_type(PollEnvelope, poll, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_create(self, async_client: AsyncLinqAPIV3) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `chat_id` but received ''"):
+ await async_client.chats.polls.with_raw_response.create(
+ chat_id="",
+ poll={"options": [{"text": "Tacos"}, {"text": "Sushi"}]},
+ )
diff --git a/tests/api_resources/messages/__init__.py b/tests/api_resources/messages/__init__.py
new file mode 100644
index 0000000..fd8019a
--- /dev/null
+++ b/tests/api_resources/messages/__init__.py
@@ -0,0 +1 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
diff --git a/tests/api_resources/messages/test_poll.py b/tests/api_resources/messages/test_poll.py
new file mode 100644
index 0000000..be037b2
--- /dev/null
+++ b/tests/api_resources/messages/test_poll.py
@@ -0,0 +1,300 @@
+# 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 tests.utils import assert_matches_type
+from linq.types.chats import PollEnvelope
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestPoll:
+ 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_retrieve(self, client: LinqAPIV3) -> None:
+ poll = client.messages.poll.retrieve(
+ "69a37c7d-af4f-4b5e-af42-e28e98ce873a",
+ )
+ assert_matches_type(PollEnvelope, poll, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve(self, client: LinqAPIV3) -> None:
+ response = client.messages.poll.with_raw_response.retrieve(
+ "69a37c7d-af4f-4b5e-af42-e28e98ce873a",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ poll = response.parse()
+ assert_matches_type(PollEnvelope, poll, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve(self, client: LinqAPIV3) -> None:
+ with client.messages.poll.with_streaming_response.retrieve(
+ "69a37c7d-af4f-4b5e-af42-e28e98ce873a",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ poll = response.parse()
+ assert_matches_type(PollEnvelope, poll, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_retrieve(self, client: LinqAPIV3) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"):
+ client.messages.poll.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_add_options(self, client: LinqAPIV3) -> None:
+ poll = client.messages.poll.add_options(
+ message_id="69a37c7d-af4f-4b5e-af42-e28e98ce873a",
+ options=[{"text": "Pizza"}],
+ )
+ assert_matches_type(PollEnvelope, poll, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_add_options(self, client: LinqAPIV3) -> None:
+ response = client.messages.poll.with_raw_response.add_options(
+ message_id="69a37c7d-af4f-4b5e-af42-e28e98ce873a",
+ options=[{"text": "Pizza"}],
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ poll = response.parse()
+ assert_matches_type(PollEnvelope, poll, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_add_options(self, client: LinqAPIV3) -> None:
+ with client.messages.poll.with_streaming_response.add_options(
+ message_id="69a37c7d-af4f-4b5e-af42-e28e98ce873a",
+ options=[{"text": "Pizza"}],
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ poll = response.parse()
+ assert_matches_type(PollEnvelope, poll, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_add_options(self, client: LinqAPIV3) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"):
+ client.messages.poll.with_raw_response.add_options(
+ message_id="",
+ options=[{"text": "Pizza"}],
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_vote(self, client: LinqAPIV3) -> None:
+ poll = client.messages.poll.vote(
+ message_id="69a37c7d-af4f-4b5e-af42-e28e98ce873a",
+ operation="add",
+ option_id="97ce8c17-7ef6-4bbc-a89a-6b93d189712f",
+ )
+ assert_matches_type(PollEnvelope, poll, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_vote(self, client: LinqAPIV3) -> None:
+ response = client.messages.poll.with_raw_response.vote(
+ message_id="69a37c7d-af4f-4b5e-af42-e28e98ce873a",
+ operation="add",
+ option_id="97ce8c17-7ef6-4bbc-a89a-6b93d189712f",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ poll = response.parse()
+ assert_matches_type(PollEnvelope, poll, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_vote(self, client: LinqAPIV3) -> None:
+ with client.messages.poll.with_streaming_response.vote(
+ message_id="69a37c7d-af4f-4b5e-af42-e28e98ce873a",
+ operation="add",
+ option_id="97ce8c17-7ef6-4bbc-a89a-6b93d189712f",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ poll = response.parse()
+ assert_matches_type(PollEnvelope, poll, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_vote(self, client: LinqAPIV3) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"):
+ client.messages.poll.with_raw_response.vote(
+ message_id="",
+ operation="add",
+ option_id="97ce8c17-7ef6-4bbc-a89a-6b93d189712f",
+ )
+
+
+class TestAsyncPoll:
+ 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_retrieve(self, async_client: AsyncLinqAPIV3) -> None:
+ poll = await async_client.messages.poll.retrieve(
+ "69a37c7d-af4f-4b5e-af42-e28e98ce873a",
+ )
+ assert_matches_type(PollEnvelope, poll, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_retrieve(self, async_client: AsyncLinqAPIV3) -> None:
+ response = await async_client.messages.poll.with_raw_response.retrieve(
+ "69a37c7d-af4f-4b5e-af42-e28e98ce873a",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ poll = await response.parse()
+ assert_matches_type(PollEnvelope, poll, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_retrieve(self, async_client: AsyncLinqAPIV3) -> None:
+ async with async_client.messages.poll.with_streaming_response.retrieve(
+ "69a37c7d-af4f-4b5e-af42-e28e98ce873a",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ poll = await response.parse()
+ assert_matches_type(PollEnvelope, poll, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_retrieve(self, async_client: AsyncLinqAPIV3) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"):
+ await async_client.messages.poll.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_add_options(self, async_client: AsyncLinqAPIV3) -> None:
+ poll = await async_client.messages.poll.add_options(
+ message_id="69a37c7d-af4f-4b5e-af42-e28e98ce873a",
+ options=[{"text": "Pizza"}],
+ )
+ assert_matches_type(PollEnvelope, poll, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_add_options(self, async_client: AsyncLinqAPIV3) -> None:
+ response = await async_client.messages.poll.with_raw_response.add_options(
+ message_id="69a37c7d-af4f-4b5e-af42-e28e98ce873a",
+ options=[{"text": "Pizza"}],
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ poll = await response.parse()
+ assert_matches_type(PollEnvelope, poll, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_add_options(self, async_client: AsyncLinqAPIV3) -> None:
+ async with async_client.messages.poll.with_streaming_response.add_options(
+ message_id="69a37c7d-af4f-4b5e-af42-e28e98ce873a",
+ options=[{"text": "Pizza"}],
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ poll = await response.parse()
+ assert_matches_type(PollEnvelope, poll, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_add_options(self, async_client: AsyncLinqAPIV3) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"):
+ await async_client.messages.poll.with_raw_response.add_options(
+ message_id="",
+ options=[{"text": "Pizza"}],
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_vote(self, async_client: AsyncLinqAPIV3) -> None:
+ poll = await async_client.messages.poll.vote(
+ message_id="69a37c7d-af4f-4b5e-af42-e28e98ce873a",
+ operation="add",
+ option_id="97ce8c17-7ef6-4bbc-a89a-6b93d189712f",
+ )
+ assert_matches_type(PollEnvelope, poll, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_vote(self, async_client: AsyncLinqAPIV3) -> None:
+ response = await async_client.messages.poll.with_raw_response.vote(
+ message_id="69a37c7d-af4f-4b5e-af42-e28e98ce873a",
+ operation="add",
+ option_id="97ce8c17-7ef6-4bbc-a89a-6b93d189712f",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ poll = await response.parse()
+ assert_matches_type(PollEnvelope, poll, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_vote(self, async_client: AsyncLinqAPIV3) -> None:
+ async with async_client.messages.poll.with_streaming_response.vote(
+ message_id="69a37c7d-af4f-4b5e-af42-e28e98ce873a",
+ operation="add",
+ option_id="97ce8c17-7ef6-4bbc-a89a-6b93d189712f",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ poll = await response.parse()
+ assert_matches_type(PollEnvelope, poll, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_path_params_vote(self, async_client: AsyncLinqAPIV3) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `message_id` but received ''"):
+ await async_client.messages.poll.with_raw_response.vote(
+ message_id="",
+ operation="add",
+ option_id="97ce8c17-7ef6-4bbc-a89a-6b93d189712f",
+ )
diff --git a/tests/api_resources/test_chats.py b/tests/api_resources/test_chats.py
index 2b84869..4aab890 100644
--- a/tests/api_resources/test_chats.py
+++ b/tests/api_resources/test_chats.py
@@ -54,6 +54,8 @@ def test_method_create_with_all_params(self, client: LinqAPIV3) -> None:
{
"type": "text",
"value": "Hello! How can I help you today?",
+ "mention": "+14155551234",
+ "mention_range": [4, 9],
"text_decorations": [
{
"range": [0, 5],
@@ -75,6 +77,7 @@ def test_method_create_with_all_params(self, client: LinqAPIV3) -> None:
},
},
to=["+12052532136"],
+ override_optout=False,
)
assert_matches_type(ChatCreateResponse, chat, path=["response"])
@@ -339,6 +342,7 @@ def test_method_send_voicememo_with_all_params(self, client: LinqAPIV3) -> None:
chat = client.chats.send_voicememo(
chat_id="f19ee7b8-8533-4c5c-83ec-4ef8d6d1ddbd",
attachment_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ override_optout=False,
voice_memo_url="https://example.com/voice-memo.m4a",
)
assert_matches_type(ChatSendVoicememoResponse, chat, path=["response"])
@@ -455,6 +459,8 @@ async def test_method_create_with_all_params(self, async_client: AsyncLinqAPIV3)
{
"type": "text",
"value": "Hello! How can I help you today?",
+ "mention": "+14155551234",
+ "mention_range": [4, 9],
"text_decorations": [
{
"range": [0, 5],
@@ -476,6 +482,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncLinqAPIV3)
},
},
to=["+12052532136"],
+ override_optout=False,
)
assert_matches_type(ChatCreateResponse, chat, path=["response"])
@@ -740,6 +747,7 @@ async def test_method_send_voicememo_with_all_params(self, async_client: AsyncLi
chat = await async_client.chats.send_voicememo(
chat_id="f19ee7b8-8533-4c5c-83ec-4ef8d6d1ddbd",
attachment_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ override_optout=False,
voice_memo_url="https://example.com/voice-memo.m4a",
)
assert_matches_type(ChatSendVoicememoResponse, chat, path=["response"])
diff --git a/tests/api_resources/test_messages.py b/tests/api_resources/test_messages.py
index dae476c..51beb7e 100644
--- a/tests/api_resources/test_messages.py
+++ b/tests/api_resources/test_messages.py
@@ -51,6 +51,8 @@ def test_method_create_with_all_params(self, client: LinqAPIV3) -> None:
{
"type": "text",
"value": "Hi! Thanks for reaching out — how can we help?",
+ "mention": "+14155551234",
+ "mention_range": [4, 9],
"text_decorations": [
{
"range": [0, 5],
@@ -74,6 +76,7 @@ def test_method_create_with_all_params(self, client: LinqAPIV3) -> None:
to=["+14155559876"],
continuation_message={"text": "Hi, it's Acme Support reaching you from a new number."},
exclude_from=["+12052535597"],
+ override_optout=False,
idempotency_key="send-abc123xyz",
)
assert_matches_type(MessageCreateResponse, message, path=["response"])
@@ -461,6 +464,8 @@ async def test_method_create_with_all_params(self, async_client: AsyncLinqAPIV3)
{
"type": "text",
"value": "Hi! Thanks for reaching out — how can we help?",
+ "mention": "+14155551234",
+ "mention_range": [4, 9],
"text_decorations": [
{
"range": [0, 5],
@@ -484,6 +489,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncLinqAPIV3)
to=["+14155559876"],
continuation_message={"text": "Hi, it's Acme Support reaching you from a new number."},
exclude_from=["+12052535597"],
+ override_optout=False,
idempotency_key="send-abc123xyz",
)
assert_matches_type(MessageCreateResponse, message, path=["response"])
diff --git a/uv.lock b/uv.lock
index a662c66..c25f4b1 100644
--- a/uv.lock
+++ b/uv.lock
@@ -530,7 +530,7 @@ wheels = [
[[package]]
name = "linq-python"
-version = "0.18.0"
+version = "0.19.0"
source = { editable = "." }
dependencies = [
{ name = "anyio" },