diff --git a/.stats.yml b/.stats.yml
index 1fd2991..c7c071b 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1 +1 @@
-configured_endpoints: 41
+configured_endpoints: 45
diff --git a/api.md b/api.md
index 31bb7ea..d408acf 100644
--- a/api.md
+++ b/api.md
@@ -164,6 +164,21 @@ Methods:
- client.available_number.retrieve(\*\*params) -> AvailableNumberRetrieveResponse
+# PaymentRequests
+
+Types:
+
+```python
+from linq.types import PaymentRequest, PaymentRequestListResponse
+```
+
+Methods:
+
+- client.payment_requests.create(\*\*params) -> PaymentRequest
+- client.payment_requests.retrieve(payment_request_id) -> PaymentRequest
+- client.payment_requests.list(\*\*params) -> PaymentRequestListResponse
+- client.payment_requests.cancel(payment_request_id) -> PaymentRequest
+
# WebhookEvents
Types:
diff --git a/scripts/lint b/scripts/lint
index 3232aa9..b0df0fd 100755
--- a/scripts/lint
+++ b/scripts/lint
@@ -13,7 +13,7 @@ else
fi
echo "==> Running pyright"
-uv run pyright
+uv run pyright -p .
echo "==> Running mypy"
uv run mypy .
diff --git a/src/linq/_client.py b/src/linq/_client.py
index 45286ad..05ed097 100644
--- a/src/linq/_client.py
+++ b/src/linq/_client.py
@@ -46,6 +46,7 @@
phone_numbers,
webhook_events,
available_number,
+ payment_requests,
webhook_subscriptions,
)
from .resources.messages import MessagesResource, AsyncMessagesResource
@@ -58,6 +59,7 @@
from .resources.phone_numbers import PhoneNumbersResource, AsyncPhoneNumbersResource
from .resources.webhook_events import WebhookEventsResource, AsyncWebhookEventsResource
from .resources.available_number import AvailableNumberResource, AsyncAvailableNumberResource
+ from .resources.payment_requests import PaymentRequestsResource, AsyncPaymentRequestsResource
from .resources.webhook_subscriptions import WebhookSubscriptionsResource, AsyncWebhookSubscriptionsResource
__all__ = [
@@ -443,6 +445,126 @@ def available_number(self) -> AvailableNumberResource:
return AvailableNumberResource(self)
+ @cached_property
+ def payment_requests(self) -> PaymentRequestsResource:
+ """Request a payment from a recipient over iMessage.
+
+ You create a payment
+ request, send its `checkout_url` to the recipient, and they pay with Apple
+ Pay or card. Funds settle **directly to your own Stripe account** — Linq
+ never holds the money.
+
+ ## How it works
+
+ 1. **Create** a payment request with an amount and currency. You get back a
+ `checkout_url` and a `status` of `requested`.
+ 2. **Send** the `checkout_url` to the recipient as a `link` message part so
+ it arrives as a tappable card (see *Sending the link* below).
+ 3. The recipient **pays** on the hosted checkout (Apple Pay App Clip on a
+ supported iPhone, web checkout everywhere else).
+ 4. You receive a **`payment.succeeded`** webhook and the request's `status`
+ becomes `succeeded`. Requests you don't collect eventually `expire`.
+
+ ## Connected accounts (Stripe Standard, direct charges)
+
+ Agent Pay runs on **Stripe Connect Standard accounts** using **direct
+ charges**: the charge is created on *your* connected account and **you are
+ the merchant of record**. That means the money, the payout schedule, the
+ customer relationship, and the compliance surface are all yours — Linq
+ orchestrates the request and the checkout but is never in the funds flow.
+
+ **Refunds, disputes, and chargebacks are handled by you, in your own Stripe
+ Dashboard.** Because charges settle directly to your account, Linq has no
+ custody of the funds and cannot issue refunds or contest disputes on your
+ behalf — and there is no refund/dispute endpoint in this API by design. Use
+ the Stripe Dashboard (or the Stripe API on your own account) for the money
+ lifecycle after a payment succeeds.
+
+ ## Getting set up
+
+ Open **Agent Pay** in your Linq dashboard
+ (`https://zero.linqapp.com/organization/payments`), click **Connect Stripe**,
+ and complete Stripe's onboarding (business details + a bank account). When
+ your account reaches `charges_enabled`, request creation unlocks; until you
+ connect Stripe, `POST /v3/payment_requests` returns `403`. You can keep
+ collecting even while Stripe finishes background verification.
+
+ ## Subscriptions
+
+ Set `mode: subscription` on `POST /v3/payment_requests` to start an
+ **auto-renewing subscription** instead of a one-time charge. Instead of an
+ amount, you pass a `price_id` — an active **recurring Price** on your
+ connected Stripe account (create one in your Stripe Dashboard under
+ Product catalog; if you sell through Stripe Payment Links today, reuse the
+ price your link is built from). The recipient pays the first invoice at
+ the same checkout, and their payment method is saved to the subscription
+ for automatic renewals.
+
+ The division of labor is deliberate: **Linq handles the first payment,
+ your Stripe account handles the rest.** The request reaches `succeeded`
+ when the first invoice is paid; from then on the subscription lives
+ entirely on your connected account. The response's `stripe` object gives
+ you the join keys — `customer_id` and `subscription_id` — so renewals,
+ plan changes, dunning, and cancellation are managed with your own Stripe
+ Dashboard/API and your own Stripe webhooks. Your `metadata` is stamped on
+ the Customer and Subscription, so correlating in either direction is
+ trivial. There are no renewal webhooks from Linq by design.
+
+ ### Free trials
+
+ Add `trial_period_days` (or a fixed `trial_end` timestamp) to start the
+ subscription with a free trial. The checkout still collects the
+ recipient's payment method — the pay sheet shows "$0 due today" with the
+ first charge date — and saves it to the subscription; Stripe bills it
+ automatically when the trial ends. The request reaches `succeeded` when
+ the card is collected, and the response carries `trial_end`. If the trial
+ would end without a payment method on file, the subscription cancels
+ rather than generating unpayable invoices. Trial lifecycle after checkout
+ (extending, ending early) is managed in your own Stripe account via
+ `stripe.subscription_id`.
+
+ A subscription request you cancel (or that expires unpaid) cancels the
+ incomplete Stripe subscription — nothing lingers on your account.
+
+ ## Pre-created customers
+
+ By default each request stands alone: payment mode attaches no Customer,
+ and subscription mode creates a fresh one. If you already manage
+ Customers on your connected account, pass their id as `customer_id`
+ (`cus_...`) on create — in payment mode the charge lands on that
+ customer's payment history, and in subscription mode the subscription is
+ created on them instead of on a new Customer. The id must reference an
+ existing, non-deleted customer on your connected account or the request
+ fails with `400`. We never modify a customer you pass — no metadata is
+ stamped on it.
+
+ ## Sending the link
+
+ Deliver the `checkout_url` as a **`link` message part** via
+ `POST /v3/chats/{chatId}/messages` — it renders as a rich card with your
+ branding (title, amount, image) instead of a bare URL, which converts far
+ better. A `link` part must be the only part in the message. See
+ [Rich Link Previews](/guides/messaging/sending-messages).
+
+ On a supported iPhone the link opens an **Apple Pay App Clip** — a native,
+ no-install checkout sheet. Everywhere else (Android, desktop, iPhones
+ without the App Clip yet) the same URL opens the web checkout, so the link
+ always works. The App Clip experience for your payment links is registered
+ automatically by Linq and refreshed whenever you update your Agent Pay
+ branding; a newly registered experience can take up to ~24 hours to
+ activate on Apple's side, during which links open the web checkout.
+
+ ## Webhooks
+
+ Subscribe to payment lifecycle events to reconcile server-side rather than
+ polling: `payment.succeeded`, `payment.canceled`, and `payment.expired`.
+ Each event carries the payment request id, amount, currency, and your
+ `metadata`. See [Webhooks](/guides/webhooks).
+ """
+ from .resources.payment_requests import PaymentRequestsResource
+
+ return PaymentRequestsResource(self)
+
@cached_property
def webhook_events(self) -> WebhookEventsResource:
"""
@@ -1232,6 +1354,126 @@ def available_number(self) -> AsyncAvailableNumberResource:
return AsyncAvailableNumberResource(self)
+ @cached_property
+ def payment_requests(self) -> AsyncPaymentRequestsResource:
+ """Request a payment from a recipient over iMessage.
+
+ You create a payment
+ request, send its `checkout_url` to the recipient, and they pay with Apple
+ Pay or card. Funds settle **directly to your own Stripe account** — Linq
+ never holds the money.
+
+ ## How it works
+
+ 1. **Create** a payment request with an amount and currency. You get back a
+ `checkout_url` and a `status` of `requested`.
+ 2. **Send** the `checkout_url` to the recipient as a `link` message part so
+ it arrives as a tappable card (see *Sending the link* below).
+ 3. The recipient **pays** on the hosted checkout (Apple Pay App Clip on a
+ supported iPhone, web checkout everywhere else).
+ 4. You receive a **`payment.succeeded`** webhook and the request's `status`
+ becomes `succeeded`. Requests you don't collect eventually `expire`.
+
+ ## Connected accounts (Stripe Standard, direct charges)
+
+ Agent Pay runs on **Stripe Connect Standard accounts** using **direct
+ charges**: the charge is created on *your* connected account and **you are
+ the merchant of record**. That means the money, the payout schedule, the
+ customer relationship, and the compliance surface are all yours — Linq
+ orchestrates the request and the checkout but is never in the funds flow.
+
+ **Refunds, disputes, and chargebacks are handled by you, in your own Stripe
+ Dashboard.** Because charges settle directly to your account, Linq has no
+ custody of the funds and cannot issue refunds or contest disputes on your
+ behalf — and there is no refund/dispute endpoint in this API by design. Use
+ the Stripe Dashboard (or the Stripe API on your own account) for the money
+ lifecycle after a payment succeeds.
+
+ ## Getting set up
+
+ Open **Agent Pay** in your Linq dashboard
+ (`https://zero.linqapp.com/organization/payments`), click **Connect Stripe**,
+ and complete Stripe's onboarding (business details + a bank account). When
+ your account reaches `charges_enabled`, request creation unlocks; until you
+ connect Stripe, `POST /v3/payment_requests` returns `403`. You can keep
+ collecting even while Stripe finishes background verification.
+
+ ## Subscriptions
+
+ Set `mode: subscription` on `POST /v3/payment_requests` to start an
+ **auto-renewing subscription** instead of a one-time charge. Instead of an
+ amount, you pass a `price_id` — an active **recurring Price** on your
+ connected Stripe account (create one in your Stripe Dashboard under
+ Product catalog; if you sell through Stripe Payment Links today, reuse the
+ price your link is built from). The recipient pays the first invoice at
+ the same checkout, and their payment method is saved to the subscription
+ for automatic renewals.
+
+ The division of labor is deliberate: **Linq handles the first payment,
+ your Stripe account handles the rest.** The request reaches `succeeded`
+ when the first invoice is paid; from then on the subscription lives
+ entirely on your connected account. The response's `stripe` object gives
+ you the join keys — `customer_id` and `subscription_id` — so renewals,
+ plan changes, dunning, and cancellation are managed with your own Stripe
+ Dashboard/API and your own Stripe webhooks. Your `metadata` is stamped on
+ the Customer and Subscription, so correlating in either direction is
+ trivial. There are no renewal webhooks from Linq by design.
+
+ ### Free trials
+
+ Add `trial_period_days` (or a fixed `trial_end` timestamp) to start the
+ subscription with a free trial. The checkout still collects the
+ recipient's payment method — the pay sheet shows "$0 due today" with the
+ first charge date — and saves it to the subscription; Stripe bills it
+ automatically when the trial ends. The request reaches `succeeded` when
+ the card is collected, and the response carries `trial_end`. If the trial
+ would end without a payment method on file, the subscription cancels
+ rather than generating unpayable invoices. Trial lifecycle after checkout
+ (extending, ending early) is managed in your own Stripe account via
+ `stripe.subscription_id`.
+
+ A subscription request you cancel (or that expires unpaid) cancels the
+ incomplete Stripe subscription — nothing lingers on your account.
+
+ ## Pre-created customers
+
+ By default each request stands alone: payment mode attaches no Customer,
+ and subscription mode creates a fresh one. If you already manage
+ Customers on your connected account, pass their id as `customer_id`
+ (`cus_...`) on create — in payment mode the charge lands on that
+ customer's payment history, and in subscription mode the subscription is
+ created on them instead of on a new Customer. The id must reference an
+ existing, non-deleted customer on your connected account or the request
+ fails with `400`. We never modify a customer you pass — no metadata is
+ stamped on it.
+
+ ## Sending the link
+
+ Deliver the `checkout_url` as a **`link` message part** via
+ `POST /v3/chats/{chatId}/messages` — it renders as a rich card with your
+ branding (title, amount, image) instead of a bare URL, which converts far
+ better. A `link` part must be the only part in the message. See
+ [Rich Link Previews](/guides/messaging/sending-messages).
+
+ On a supported iPhone the link opens an **Apple Pay App Clip** — a native,
+ no-install checkout sheet. Everywhere else (Android, desktop, iPhones
+ without the App Clip yet) the same URL opens the web checkout, so the link
+ always works. The App Clip experience for your payment links is registered
+ automatically by Linq and refreshed whenever you update your Agent Pay
+ branding; a newly registered experience can take up to ~24 hours to
+ activate on Apple's side, during which links open the web checkout.
+
+ ## Webhooks
+
+ Subscribe to payment lifecycle events to reconcile server-side rather than
+ polling: `payment.succeeded`, `payment.canceled`, and `payment.expired`.
+ Each event carries the payment request id, amount, currency, and your
+ `metadata`. See [Webhooks](/guides/webhooks).
+ """
+ from .resources.payment_requests import AsyncPaymentRequestsResource
+
+ return AsyncPaymentRequestsResource(self)
+
@cached_property
def webhook_events(self) -> AsyncWebhookEventsResource:
"""
@@ -1955,6 +2197,126 @@ def available_number(self) -> available_number.AvailableNumberResourceWithRawRes
return AvailableNumberResourceWithRawResponse(self._client.available_number)
+ @cached_property
+ def payment_requests(self) -> payment_requests.PaymentRequestsResourceWithRawResponse:
+ """Request a payment from a recipient over iMessage.
+
+ You create a payment
+ request, send its `checkout_url` to the recipient, and they pay with Apple
+ Pay or card. Funds settle **directly to your own Stripe account** — Linq
+ never holds the money.
+
+ ## How it works
+
+ 1. **Create** a payment request with an amount and currency. You get back a
+ `checkout_url` and a `status` of `requested`.
+ 2. **Send** the `checkout_url` to the recipient as a `link` message part so
+ it arrives as a tappable card (see *Sending the link* below).
+ 3. The recipient **pays** on the hosted checkout (Apple Pay App Clip on a
+ supported iPhone, web checkout everywhere else).
+ 4. You receive a **`payment.succeeded`** webhook and the request's `status`
+ becomes `succeeded`. Requests you don't collect eventually `expire`.
+
+ ## Connected accounts (Stripe Standard, direct charges)
+
+ Agent Pay runs on **Stripe Connect Standard accounts** using **direct
+ charges**: the charge is created on *your* connected account and **you are
+ the merchant of record**. That means the money, the payout schedule, the
+ customer relationship, and the compliance surface are all yours — Linq
+ orchestrates the request and the checkout but is never in the funds flow.
+
+ **Refunds, disputes, and chargebacks are handled by you, in your own Stripe
+ Dashboard.** Because charges settle directly to your account, Linq has no
+ custody of the funds and cannot issue refunds or contest disputes on your
+ behalf — and there is no refund/dispute endpoint in this API by design. Use
+ the Stripe Dashboard (or the Stripe API on your own account) for the money
+ lifecycle after a payment succeeds.
+
+ ## Getting set up
+
+ Open **Agent Pay** in your Linq dashboard
+ (`https://zero.linqapp.com/organization/payments`), click **Connect Stripe**,
+ and complete Stripe's onboarding (business details + a bank account). When
+ your account reaches `charges_enabled`, request creation unlocks; until you
+ connect Stripe, `POST /v3/payment_requests` returns `403`. You can keep
+ collecting even while Stripe finishes background verification.
+
+ ## Subscriptions
+
+ Set `mode: subscription` on `POST /v3/payment_requests` to start an
+ **auto-renewing subscription** instead of a one-time charge. Instead of an
+ amount, you pass a `price_id` — an active **recurring Price** on your
+ connected Stripe account (create one in your Stripe Dashboard under
+ Product catalog; if you sell through Stripe Payment Links today, reuse the
+ price your link is built from). The recipient pays the first invoice at
+ the same checkout, and their payment method is saved to the subscription
+ for automatic renewals.
+
+ The division of labor is deliberate: **Linq handles the first payment,
+ your Stripe account handles the rest.** The request reaches `succeeded`
+ when the first invoice is paid; from then on the subscription lives
+ entirely on your connected account. The response's `stripe` object gives
+ you the join keys — `customer_id` and `subscription_id` — so renewals,
+ plan changes, dunning, and cancellation are managed with your own Stripe
+ Dashboard/API and your own Stripe webhooks. Your `metadata` is stamped on
+ the Customer and Subscription, so correlating in either direction is
+ trivial. There are no renewal webhooks from Linq by design.
+
+ ### Free trials
+
+ Add `trial_period_days` (or a fixed `trial_end` timestamp) to start the
+ subscription with a free trial. The checkout still collects the
+ recipient's payment method — the pay sheet shows "$0 due today" with the
+ first charge date — and saves it to the subscription; Stripe bills it
+ automatically when the trial ends. The request reaches `succeeded` when
+ the card is collected, and the response carries `trial_end`. If the trial
+ would end without a payment method on file, the subscription cancels
+ rather than generating unpayable invoices. Trial lifecycle after checkout
+ (extending, ending early) is managed in your own Stripe account via
+ `stripe.subscription_id`.
+
+ A subscription request you cancel (or that expires unpaid) cancels the
+ incomplete Stripe subscription — nothing lingers on your account.
+
+ ## Pre-created customers
+
+ By default each request stands alone: payment mode attaches no Customer,
+ and subscription mode creates a fresh one. If you already manage
+ Customers on your connected account, pass their id as `customer_id`
+ (`cus_...`) on create — in payment mode the charge lands on that
+ customer's payment history, and in subscription mode the subscription is
+ created on them instead of on a new Customer. The id must reference an
+ existing, non-deleted customer on your connected account or the request
+ fails with `400`. We never modify a customer you pass — no metadata is
+ stamped on it.
+
+ ## Sending the link
+
+ Deliver the `checkout_url` as a **`link` message part** via
+ `POST /v3/chats/{chatId}/messages` — it renders as a rich card with your
+ branding (title, amount, image) instead of a bare URL, which converts far
+ better. A `link` part must be the only part in the message. See
+ [Rich Link Previews](/guides/messaging/sending-messages).
+
+ On a supported iPhone the link opens an **Apple Pay App Clip** — a native,
+ no-install checkout sheet. Everywhere else (Android, desktop, iPhones
+ without the App Clip yet) the same URL opens the web checkout, so the link
+ always works. The App Clip experience for your payment links is registered
+ automatically by Linq and refreshed whenever you update your Agent Pay
+ branding; a newly registered experience can take up to ~24 hours to
+ activate on Apple's side, during which links open the web checkout.
+
+ ## Webhooks
+
+ Subscribe to payment lifecycle events to reconcile server-side rather than
+ polling: `payment.succeeded`, `payment.canceled`, and `payment.expired`.
+ Each event carries the payment request id, amount, currency, and your
+ `metadata`. See [Webhooks](/guides/webhooks).
+ """
+ from .resources.payment_requests import PaymentRequestsResourceWithRawResponse
+
+ return PaymentRequestsResourceWithRawResponse(self._client.payment_requests)
+
@cached_property
def webhook_events(self) -> webhook_events.WebhookEventsResourceWithRawResponse:
"""
@@ -2551,6 +2913,126 @@ def available_number(self) -> available_number.AsyncAvailableNumberResourceWithR
return AsyncAvailableNumberResourceWithRawResponse(self._client.available_number)
+ @cached_property
+ def payment_requests(self) -> payment_requests.AsyncPaymentRequestsResourceWithRawResponse:
+ """Request a payment from a recipient over iMessage.
+
+ You create a payment
+ request, send its `checkout_url` to the recipient, and they pay with Apple
+ Pay or card. Funds settle **directly to your own Stripe account** — Linq
+ never holds the money.
+
+ ## How it works
+
+ 1. **Create** a payment request with an amount and currency. You get back a
+ `checkout_url` and a `status` of `requested`.
+ 2. **Send** the `checkout_url` to the recipient as a `link` message part so
+ it arrives as a tappable card (see *Sending the link* below).
+ 3. The recipient **pays** on the hosted checkout (Apple Pay App Clip on a
+ supported iPhone, web checkout everywhere else).
+ 4. You receive a **`payment.succeeded`** webhook and the request's `status`
+ becomes `succeeded`. Requests you don't collect eventually `expire`.
+
+ ## Connected accounts (Stripe Standard, direct charges)
+
+ Agent Pay runs on **Stripe Connect Standard accounts** using **direct
+ charges**: the charge is created on *your* connected account and **you are
+ the merchant of record**. That means the money, the payout schedule, the
+ customer relationship, and the compliance surface are all yours — Linq
+ orchestrates the request and the checkout but is never in the funds flow.
+
+ **Refunds, disputes, and chargebacks are handled by you, in your own Stripe
+ Dashboard.** Because charges settle directly to your account, Linq has no
+ custody of the funds and cannot issue refunds or contest disputes on your
+ behalf — and there is no refund/dispute endpoint in this API by design. Use
+ the Stripe Dashboard (or the Stripe API on your own account) for the money
+ lifecycle after a payment succeeds.
+
+ ## Getting set up
+
+ Open **Agent Pay** in your Linq dashboard
+ (`https://zero.linqapp.com/organization/payments`), click **Connect Stripe**,
+ and complete Stripe's onboarding (business details + a bank account). When
+ your account reaches `charges_enabled`, request creation unlocks; until you
+ connect Stripe, `POST /v3/payment_requests` returns `403`. You can keep
+ collecting even while Stripe finishes background verification.
+
+ ## Subscriptions
+
+ Set `mode: subscription` on `POST /v3/payment_requests` to start an
+ **auto-renewing subscription** instead of a one-time charge. Instead of an
+ amount, you pass a `price_id` — an active **recurring Price** on your
+ connected Stripe account (create one in your Stripe Dashboard under
+ Product catalog; if you sell through Stripe Payment Links today, reuse the
+ price your link is built from). The recipient pays the first invoice at
+ the same checkout, and their payment method is saved to the subscription
+ for automatic renewals.
+
+ The division of labor is deliberate: **Linq handles the first payment,
+ your Stripe account handles the rest.** The request reaches `succeeded`
+ when the first invoice is paid; from then on the subscription lives
+ entirely on your connected account. The response's `stripe` object gives
+ you the join keys — `customer_id` and `subscription_id` — so renewals,
+ plan changes, dunning, and cancellation are managed with your own Stripe
+ Dashboard/API and your own Stripe webhooks. Your `metadata` is stamped on
+ the Customer and Subscription, so correlating in either direction is
+ trivial. There are no renewal webhooks from Linq by design.
+
+ ### Free trials
+
+ Add `trial_period_days` (or a fixed `trial_end` timestamp) to start the
+ subscription with a free trial. The checkout still collects the
+ recipient's payment method — the pay sheet shows "$0 due today" with the
+ first charge date — and saves it to the subscription; Stripe bills it
+ automatically when the trial ends. The request reaches `succeeded` when
+ the card is collected, and the response carries `trial_end`. If the trial
+ would end without a payment method on file, the subscription cancels
+ rather than generating unpayable invoices. Trial lifecycle after checkout
+ (extending, ending early) is managed in your own Stripe account via
+ `stripe.subscription_id`.
+
+ A subscription request you cancel (or that expires unpaid) cancels the
+ incomplete Stripe subscription — nothing lingers on your account.
+
+ ## Pre-created customers
+
+ By default each request stands alone: payment mode attaches no Customer,
+ and subscription mode creates a fresh one. If you already manage
+ Customers on your connected account, pass their id as `customer_id`
+ (`cus_...`) on create — in payment mode the charge lands on that
+ customer's payment history, and in subscription mode the subscription is
+ created on them instead of on a new Customer. The id must reference an
+ existing, non-deleted customer on your connected account or the request
+ fails with `400`. We never modify a customer you pass — no metadata is
+ stamped on it.
+
+ ## Sending the link
+
+ Deliver the `checkout_url` as a **`link` message part** via
+ `POST /v3/chats/{chatId}/messages` — it renders as a rich card with your
+ branding (title, amount, image) instead of a bare URL, which converts far
+ better. A `link` part must be the only part in the message. See
+ [Rich Link Previews](/guides/messaging/sending-messages).
+
+ On a supported iPhone the link opens an **Apple Pay App Clip** — a native,
+ no-install checkout sheet. Everywhere else (Android, desktop, iPhones
+ without the App Clip yet) the same URL opens the web checkout, so the link
+ always works. The App Clip experience for your payment links is registered
+ automatically by Linq and refreshed whenever you update your Agent Pay
+ branding; a newly registered experience can take up to ~24 hours to
+ activate on Apple's side, during which links open the web checkout.
+
+ ## Webhooks
+
+ Subscribe to payment lifecycle events to reconcile server-side rather than
+ polling: `payment.succeeded`, `payment.canceled`, and `payment.expired`.
+ Each event carries the payment request id, amount, currency, and your
+ `metadata`. See [Webhooks](/guides/webhooks).
+ """
+ from .resources.payment_requests import AsyncPaymentRequestsResourceWithRawResponse
+
+ return AsyncPaymentRequestsResourceWithRawResponse(self._client.payment_requests)
+
@cached_property
def webhook_events(self) -> webhook_events.AsyncWebhookEventsResourceWithRawResponse:
"""
@@ -3147,6 +3629,126 @@ def available_number(self) -> available_number.AvailableNumberResourceWithStream
return AvailableNumberResourceWithStreamingResponse(self._client.available_number)
+ @cached_property
+ def payment_requests(self) -> payment_requests.PaymentRequestsResourceWithStreamingResponse:
+ """Request a payment from a recipient over iMessage.
+
+ You create a payment
+ request, send its `checkout_url` to the recipient, and they pay with Apple
+ Pay or card. Funds settle **directly to your own Stripe account** — Linq
+ never holds the money.
+
+ ## How it works
+
+ 1. **Create** a payment request with an amount and currency. You get back a
+ `checkout_url` and a `status` of `requested`.
+ 2. **Send** the `checkout_url` to the recipient as a `link` message part so
+ it arrives as a tappable card (see *Sending the link* below).
+ 3. The recipient **pays** on the hosted checkout (Apple Pay App Clip on a
+ supported iPhone, web checkout everywhere else).
+ 4. You receive a **`payment.succeeded`** webhook and the request's `status`
+ becomes `succeeded`. Requests you don't collect eventually `expire`.
+
+ ## Connected accounts (Stripe Standard, direct charges)
+
+ Agent Pay runs on **Stripe Connect Standard accounts** using **direct
+ charges**: the charge is created on *your* connected account and **you are
+ the merchant of record**. That means the money, the payout schedule, the
+ customer relationship, and the compliance surface are all yours — Linq
+ orchestrates the request and the checkout but is never in the funds flow.
+
+ **Refunds, disputes, and chargebacks are handled by you, in your own Stripe
+ Dashboard.** Because charges settle directly to your account, Linq has no
+ custody of the funds and cannot issue refunds or contest disputes on your
+ behalf — and there is no refund/dispute endpoint in this API by design. Use
+ the Stripe Dashboard (or the Stripe API on your own account) for the money
+ lifecycle after a payment succeeds.
+
+ ## Getting set up
+
+ Open **Agent Pay** in your Linq dashboard
+ (`https://zero.linqapp.com/organization/payments`), click **Connect Stripe**,
+ and complete Stripe's onboarding (business details + a bank account). When
+ your account reaches `charges_enabled`, request creation unlocks; until you
+ connect Stripe, `POST /v3/payment_requests` returns `403`. You can keep
+ collecting even while Stripe finishes background verification.
+
+ ## Subscriptions
+
+ Set `mode: subscription` on `POST /v3/payment_requests` to start an
+ **auto-renewing subscription** instead of a one-time charge. Instead of an
+ amount, you pass a `price_id` — an active **recurring Price** on your
+ connected Stripe account (create one in your Stripe Dashboard under
+ Product catalog; if you sell through Stripe Payment Links today, reuse the
+ price your link is built from). The recipient pays the first invoice at
+ the same checkout, and their payment method is saved to the subscription
+ for automatic renewals.
+
+ The division of labor is deliberate: **Linq handles the first payment,
+ your Stripe account handles the rest.** The request reaches `succeeded`
+ when the first invoice is paid; from then on the subscription lives
+ entirely on your connected account. The response's `stripe` object gives
+ you the join keys — `customer_id` and `subscription_id` — so renewals,
+ plan changes, dunning, and cancellation are managed with your own Stripe
+ Dashboard/API and your own Stripe webhooks. Your `metadata` is stamped on
+ the Customer and Subscription, so correlating in either direction is
+ trivial. There are no renewal webhooks from Linq by design.
+
+ ### Free trials
+
+ Add `trial_period_days` (or a fixed `trial_end` timestamp) to start the
+ subscription with a free trial. The checkout still collects the
+ recipient's payment method — the pay sheet shows "$0 due today" with the
+ first charge date — and saves it to the subscription; Stripe bills it
+ automatically when the trial ends. The request reaches `succeeded` when
+ the card is collected, and the response carries `trial_end`. If the trial
+ would end without a payment method on file, the subscription cancels
+ rather than generating unpayable invoices. Trial lifecycle after checkout
+ (extending, ending early) is managed in your own Stripe account via
+ `stripe.subscription_id`.
+
+ A subscription request you cancel (or that expires unpaid) cancels the
+ incomplete Stripe subscription — nothing lingers on your account.
+
+ ## Pre-created customers
+
+ By default each request stands alone: payment mode attaches no Customer,
+ and subscription mode creates a fresh one. If you already manage
+ Customers on your connected account, pass their id as `customer_id`
+ (`cus_...`) on create — in payment mode the charge lands on that
+ customer's payment history, and in subscription mode the subscription is
+ created on them instead of on a new Customer. The id must reference an
+ existing, non-deleted customer on your connected account or the request
+ fails with `400`. We never modify a customer you pass — no metadata is
+ stamped on it.
+
+ ## Sending the link
+
+ Deliver the `checkout_url` as a **`link` message part** via
+ `POST /v3/chats/{chatId}/messages` — it renders as a rich card with your
+ branding (title, amount, image) instead of a bare URL, which converts far
+ better. A `link` part must be the only part in the message. See
+ [Rich Link Previews](/guides/messaging/sending-messages).
+
+ On a supported iPhone the link opens an **Apple Pay App Clip** — a native,
+ no-install checkout sheet. Everywhere else (Android, desktop, iPhones
+ without the App Clip yet) the same URL opens the web checkout, so the link
+ always works. The App Clip experience for your payment links is registered
+ automatically by Linq and refreshed whenever you update your Agent Pay
+ branding; a newly registered experience can take up to ~24 hours to
+ activate on Apple's side, during which links open the web checkout.
+
+ ## Webhooks
+
+ Subscribe to payment lifecycle events to reconcile server-side rather than
+ polling: `payment.succeeded`, `payment.canceled`, and `payment.expired`.
+ Each event carries the payment request id, amount, currency, and your
+ `metadata`. See [Webhooks](/guides/webhooks).
+ """
+ from .resources.payment_requests import PaymentRequestsResourceWithStreamingResponse
+
+ return PaymentRequestsResourceWithStreamingResponse(self._client.payment_requests)
+
@cached_property
def webhook_events(self) -> webhook_events.WebhookEventsResourceWithStreamingResponse:
"""
@@ -3743,6 +4345,126 @@ def available_number(self) -> available_number.AsyncAvailableNumberResourceWithS
return AsyncAvailableNumberResourceWithStreamingResponse(self._client.available_number)
+ @cached_property
+ def payment_requests(self) -> payment_requests.AsyncPaymentRequestsResourceWithStreamingResponse:
+ """Request a payment from a recipient over iMessage.
+
+ You create a payment
+ request, send its `checkout_url` to the recipient, and they pay with Apple
+ Pay or card. Funds settle **directly to your own Stripe account** — Linq
+ never holds the money.
+
+ ## How it works
+
+ 1. **Create** a payment request with an amount and currency. You get back a
+ `checkout_url` and a `status` of `requested`.
+ 2. **Send** the `checkout_url` to the recipient as a `link` message part so
+ it arrives as a tappable card (see *Sending the link* below).
+ 3. The recipient **pays** on the hosted checkout (Apple Pay App Clip on a
+ supported iPhone, web checkout everywhere else).
+ 4. You receive a **`payment.succeeded`** webhook and the request's `status`
+ becomes `succeeded`. Requests you don't collect eventually `expire`.
+
+ ## Connected accounts (Stripe Standard, direct charges)
+
+ Agent Pay runs on **Stripe Connect Standard accounts** using **direct
+ charges**: the charge is created on *your* connected account and **you are
+ the merchant of record**. That means the money, the payout schedule, the
+ customer relationship, and the compliance surface are all yours — Linq
+ orchestrates the request and the checkout but is never in the funds flow.
+
+ **Refunds, disputes, and chargebacks are handled by you, in your own Stripe
+ Dashboard.** Because charges settle directly to your account, Linq has no
+ custody of the funds and cannot issue refunds or contest disputes on your
+ behalf — and there is no refund/dispute endpoint in this API by design. Use
+ the Stripe Dashboard (or the Stripe API on your own account) for the money
+ lifecycle after a payment succeeds.
+
+ ## Getting set up
+
+ Open **Agent Pay** in your Linq dashboard
+ (`https://zero.linqapp.com/organization/payments`), click **Connect Stripe**,
+ and complete Stripe's onboarding (business details + a bank account). When
+ your account reaches `charges_enabled`, request creation unlocks; until you
+ connect Stripe, `POST /v3/payment_requests` returns `403`. You can keep
+ collecting even while Stripe finishes background verification.
+
+ ## Subscriptions
+
+ Set `mode: subscription` on `POST /v3/payment_requests` to start an
+ **auto-renewing subscription** instead of a one-time charge. Instead of an
+ amount, you pass a `price_id` — an active **recurring Price** on your
+ connected Stripe account (create one in your Stripe Dashboard under
+ Product catalog; if you sell through Stripe Payment Links today, reuse the
+ price your link is built from). The recipient pays the first invoice at
+ the same checkout, and their payment method is saved to the subscription
+ for automatic renewals.
+
+ The division of labor is deliberate: **Linq handles the first payment,
+ your Stripe account handles the rest.** The request reaches `succeeded`
+ when the first invoice is paid; from then on the subscription lives
+ entirely on your connected account. The response's `stripe` object gives
+ you the join keys — `customer_id` and `subscription_id` — so renewals,
+ plan changes, dunning, and cancellation are managed with your own Stripe
+ Dashboard/API and your own Stripe webhooks. Your `metadata` is stamped on
+ the Customer and Subscription, so correlating in either direction is
+ trivial. There are no renewal webhooks from Linq by design.
+
+ ### Free trials
+
+ Add `trial_period_days` (or a fixed `trial_end` timestamp) to start the
+ subscription with a free trial. The checkout still collects the
+ recipient's payment method — the pay sheet shows "$0 due today" with the
+ first charge date — and saves it to the subscription; Stripe bills it
+ automatically when the trial ends. The request reaches `succeeded` when
+ the card is collected, and the response carries `trial_end`. If the trial
+ would end without a payment method on file, the subscription cancels
+ rather than generating unpayable invoices. Trial lifecycle after checkout
+ (extending, ending early) is managed in your own Stripe account via
+ `stripe.subscription_id`.
+
+ A subscription request you cancel (or that expires unpaid) cancels the
+ incomplete Stripe subscription — nothing lingers on your account.
+
+ ## Pre-created customers
+
+ By default each request stands alone: payment mode attaches no Customer,
+ and subscription mode creates a fresh one. If you already manage
+ Customers on your connected account, pass their id as `customer_id`
+ (`cus_...`) on create — in payment mode the charge lands on that
+ customer's payment history, and in subscription mode the subscription is
+ created on them instead of on a new Customer. The id must reference an
+ existing, non-deleted customer on your connected account or the request
+ fails with `400`. We never modify a customer you pass — no metadata is
+ stamped on it.
+
+ ## Sending the link
+
+ Deliver the `checkout_url` as a **`link` message part** via
+ `POST /v3/chats/{chatId}/messages` — it renders as a rich card with your
+ branding (title, amount, image) instead of a bare URL, which converts far
+ better. A `link` part must be the only part in the message. See
+ [Rich Link Previews](/guides/messaging/sending-messages).
+
+ On a supported iPhone the link opens an **Apple Pay App Clip** — a native,
+ no-install checkout sheet. Everywhere else (Android, desktop, iPhones
+ without the App Clip yet) the same URL opens the web checkout, so the link
+ always works. The App Clip experience for your payment links is registered
+ automatically by Linq and refreshed whenever you update your Agent Pay
+ branding; a newly registered experience can take up to ~24 hours to
+ activate on Apple's side, during which links open the web checkout.
+
+ ## Webhooks
+
+ Subscribe to payment lifecycle events to reconcile server-side rather than
+ polling: `payment.succeeded`, `payment.canceled`, and `payment.expired`.
+ Each event carries the payment request id, amount, currency, and your
+ `metadata`. See [Webhooks](/guides/webhooks).
+ """
+ from .resources.payment_requests import AsyncPaymentRequestsResourceWithStreamingResponse
+
+ return AsyncPaymentRequestsResourceWithStreamingResponse(self._client.payment_requests)
+
@cached_property
def webhook_events(self) -> webhook_events.AsyncWebhookEventsResourceWithStreamingResponse:
"""
diff --git a/src/linq/resources/__init__.py b/src/linq/resources/__init__.py
index 00e6eb8..b0ae102 100644
--- a/src/linq/resources/__init__.py
+++ b/src/linq/resources/__init__.py
@@ -73,6 +73,14 @@
AvailableNumberResourceWithStreamingResponse,
AsyncAvailableNumberResourceWithStreamingResponse,
)
+from .payment_requests import (
+ PaymentRequestsResource,
+ AsyncPaymentRequestsResource,
+ PaymentRequestsResourceWithRawResponse,
+ AsyncPaymentRequestsResourceWithRawResponse,
+ PaymentRequestsResourceWithStreamingResponse,
+ AsyncPaymentRequestsResourceWithStreamingResponse,
+)
from .webhook_subscriptions import (
WebhookSubscriptionsResource,
AsyncWebhookSubscriptionsResource,
@@ -119,6 +127,12 @@
"AsyncAvailableNumberResourceWithRawResponse",
"AvailableNumberResourceWithStreamingResponse",
"AsyncAvailableNumberResourceWithStreamingResponse",
+ "PaymentRequestsResource",
+ "AsyncPaymentRequestsResource",
+ "PaymentRequestsResourceWithRawResponse",
+ "AsyncPaymentRequestsResourceWithRawResponse",
+ "PaymentRequestsResourceWithStreamingResponse",
+ "AsyncPaymentRequestsResourceWithStreamingResponse",
"WebhookEventsResource",
"AsyncWebhookEventsResource",
"WebhookEventsResourceWithRawResponse",
diff --git a/src/linq/resources/available_number.py b/src/linq/resources/available_number.py
index 0fb879a..fe2ff48 100644
--- a/src/linq/resources/available_number.py
+++ b/src/linq/resources/available_number.py
@@ -67,9 +67,11 @@ def retrieve(
reusing the line an existing chat with those recipients is already on. Without
`to`, the best healthy line is chosen.
- This is advisory: it does not reserve the line or change selection state. Pass
- the returned `phone_number` as `from` when you create the chat to guarantee the
- same line.
+ This does not reserve the line. Without `to`, the least-recently-used healthy
+ line is returned — suggestions and your own sends (including an explicit `from`
+ on chat creation) both count as use, so successive calls cycle through your
+ healthy lines and traffic spreads evenly. Pass the returned `phone_number` as
+ `from` when you create the chat to guarantee the same line.
Also returns `vcf_url`: a time-limited link to a vCard (`.vcf`) for the chosen
line, carrying its contact card (name/photo) with the chosen number as the
@@ -148,9 +150,11 @@ async def retrieve(
reusing the line an existing chat with those recipients is already on. Without
`to`, the best healthy line is chosen.
- This is advisory: it does not reserve the line or change selection state. Pass
- the returned `phone_number` as `from` when you create the chat to guarantee the
- same line.
+ This does not reserve the line. Without `to`, the least-recently-used healthy
+ line is returned — suggestions and your own sends (including an explicit `from`
+ on chat creation) both count as use, so successive calls cycle through your
+ healthy lines and traffic spreads evenly. Pass the returned `phone_number` as
+ `from` when you create the chat to guarantee the same line.
Also returns `vcf_url`: a time-limited link to a vCard (`.vcf`) for the chosen
line, carrying its contact card (name/photo) with the chosen number as the
diff --git a/src/linq/resources/chats/chats.py b/src/linq/resources/chats/chats.py
index 20022f4..81edb5e 100644
--- a/src/linq/resources/chats/chats.py
+++ b/src/linq/resources/chats/chats.py
@@ -172,14 +172,43 @@ def messages(self) -> MessagesResource:
@cached_property
def location(self) -> LocationResource:
- """Request and retrieve real-time location data via iMessage.
-
- Use these endpoints to request a contact's location, retrieve location data
- for contacts who are sharing with you, and subscribe to webhooks when someone
- starts or stops sharing their location.
+ """
+ Request a contact's location, retrieve location for contacts sharing with you,
+ and subscribe to webhooks when someone starts or stops sharing.
**Coordinates** are returned in [GeoJSON](https://datatracker.ietf.org/doc/html/rfc7946) format:
`[longitude, latitude]` or `[longitude, latitude, altitude]` if altitude is available.
+
+ ### Reading location is poll-based
+
+ Poll `GET /v3/chats/{chatId}/location` whenever you need the latest position.
+ **There is no webhook that pushes updated coordinates** — the
+ `location.sharing.started` / `location.sharing.stopped` webhooks fire only when a
+ contact begins or ends sharing, not on each position update. To track a moving
+ contact, poll the `GET` endpoint.
+
+ ### Freshness
+
+ Each feature's `properties.updated_at` tells you when that participant's
+ location was last updated — use it to judge freshness.
+
+ ### Polling guidance
+
+ Locations refresh on Apple's cadence, not per request — polling faster than a
+ participant's location actually updates just returns the same position. Poll at a
+ modest interval (for example, once every few minutes per chat) rather than
+ continuously.
+
+ ### Why is location empty after `location.sharing.started` fired?
+
+ If the contact started sharing from the **standalone Find My app** instead of the
+ Messages conversation, the share may be tied to their **Apple ID email** rather
+ than their phone number — the webhook's `shared_by` field shows the email in that
+ case. Location is readable only through a chat with the handle that shared, so
+ `GET /v3/chats/{chatId}/location` on the phone-number chat stays empty.
+
+ The fix: have the contact stop sharing and re-share from **Find My inside the
+ Messages conversation** with your number.
"""
return LocationResource(self._client)
@@ -764,14 +793,43 @@ def messages(self) -> AsyncMessagesResource:
@cached_property
def location(self) -> AsyncLocationResource:
- """Request and retrieve real-time location data via iMessage.
-
- Use these endpoints to request a contact's location, retrieve location data
- for contacts who are sharing with you, and subscribe to webhooks when someone
- starts or stops sharing their location.
+ """
+ Request a contact's location, retrieve location for contacts sharing with you,
+ and subscribe to webhooks when someone starts or stops sharing.
**Coordinates** are returned in [GeoJSON](https://datatracker.ietf.org/doc/html/rfc7946) format:
`[longitude, latitude]` or `[longitude, latitude, altitude]` if altitude is available.
+
+ ### Reading location is poll-based
+
+ Poll `GET /v3/chats/{chatId}/location` whenever you need the latest position.
+ **There is no webhook that pushes updated coordinates** — the
+ `location.sharing.started` / `location.sharing.stopped` webhooks fire only when a
+ contact begins or ends sharing, not on each position update. To track a moving
+ contact, poll the `GET` endpoint.
+
+ ### Freshness
+
+ Each feature's `properties.updated_at` tells you when that participant's
+ location was last updated — use it to judge freshness.
+
+ ### Polling guidance
+
+ Locations refresh on Apple's cadence, not per request — polling faster than a
+ participant's location actually updates just returns the same position. Poll at a
+ modest interval (for example, once every few minutes per chat) rather than
+ continuously.
+
+ ### Why is location empty after `location.sharing.started` fired?
+
+ If the contact started sharing from the **standalone Find My app** instead of the
+ Messages conversation, the share may be tied to their **Apple ID email** rather
+ than their phone number — the webhook's `shared_by` field shows the email in that
+ case. Location is readable only through a chat with the handle that shared, so
+ `GET /v3/chats/{chatId}/location` on the phone-number chat stays empty.
+
+ The fix: have the contact stop sharing and re-share from **Find My inside the
+ Messages conversation** with your number.
"""
return AsyncLocationResource(self._client)
@@ -1384,14 +1442,43 @@ def messages(self) -> MessagesResourceWithRawResponse:
@cached_property
def location(self) -> LocationResourceWithRawResponse:
- """Request and retrieve real-time location data via iMessage.
-
- Use these endpoints to request a contact's location, retrieve location data
- for contacts who are sharing with you, and subscribe to webhooks when someone
- starts or stops sharing their location.
+ """
+ Request a contact's location, retrieve location for contacts sharing with you,
+ and subscribe to webhooks when someone starts or stops sharing.
**Coordinates** are returned in [GeoJSON](https://datatracker.ietf.org/doc/html/rfc7946) format:
`[longitude, latitude]` or `[longitude, latitude, altitude]` if altitude is available.
+
+ ### Reading location is poll-based
+
+ Poll `GET /v3/chats/{chatId}/location` whenever you need the latest position.
+ **There is no webhook that pushes updated coordinates** — the
+ `location.sharing.started` / `location.sharing.stopped` webhooks fire only when a
+ contact begins or ends sharing, not on each position update. To track a moving
+ contact, poll the `GET` endpoint.
+
+ ### Freshness
+
+ Each feature's `properties.updated_at` tells you when that participant's
+ location was last updated — use it to judge freshness.
+
+ ### Polling guidance
+
+ Locations refresh on Apple's cadence, not per request — polling faster than a
+ participant's location actually updates just returns the same position. Poll at a
+ modest interval (for example, once every few minutes per chat) rather than
+ continuously.
+
+ ### Why is location empty after `location.sharing.started` fired?
+
+ If the contact started sharing from the **standalone Find My app** instead of the
+ Messages conversation, the share may be tied to their **Apple ID email** rather
+ than their phone number — the webhook's `shared_by` field shows the email in that
+ case. Location is readable only through a chat with the handle that shared, so
+ `GET /v3/chats/{chatId}/location` on the phone-number chat stays empty.
+
+ The fix: have the contact stop sharing and re-share from **Find My inside the
+ Messages conversation** with your number.
"""
return LocationResourceWithRawResponse(self._chats.location)
@@ -1532,14 +1619,43 @@ def messages(self) -> AsyncMessagesResourceWithRawResponse:
@cached_property
def location(self) -> AsyncLocationResourceWithRawResponse:
- """Request and retrieve real-time location data via iMessage.
-
- Use these endpoints to request a contact's location, retrieve location data
- for contacts who are sharing with you, and subscribe to webhooks when someone
- starts or stops sharing their location.
+ """
+ Request a contact's location, retrieve location for contacts sharing with you,
+ and subscribe to webhooks when someone starts or stops sharing.
**Coordinates** are returned in [GeoJSON](https://datatracker.ietf.org/doc/html/rfc7946) format:
`[longitude, latitude]` or `[longitude, latitude, altitude]` if altitude is available.
+
+ ### Reading location is poll-based
+
+ Poll `GET /v3/chats/{chatId}/location` whenever you need the latest position.
+ **There is no webhook that pushes updated coordinates** — the
+ `location.sharing.started` / `location.sharing.stopped` webhooks fire only when a
+ contact begins or ends sharing, not on each position update. To track a moving
+ contact, poll the `GET` endpoint.
+
+ ### Freshness
+
+ Each feature's `properties.updated_at` tells you when that participant's
+ location was last updated — use it to judge freshness.
+
+ ### Polling guidance
+
+ Locations refresh on Apple's cadence, not per request — polling faster than a
+ participant's location actually updates just returns the same position. Poll at a
+ modest interval (for example, once every few minutes per chat) rather than
+ continuously.
+
+ ### Why is location empty after `location.sharing.started` fired?
+
+ If the contact started sharing from the **standalone Find My app** instead of the
+ Messages conversation, the share may be tied to their **Apple ID email** rather
+ than their phone number — the webhook's `shared_by` field shows the email in that
+ case. Location is readable only through a chat with the handle that shared, so
+ `GET /v3/chats/{chatId}/location` on the phone-number chat stays empty.
+
+ The fix: have the contact stop sharing and re-share from **Find My inside the
+ Messages conversation** with your number.
"""
return AsyncLocationResourceWithRawResponse(self._chats.location)
@@ -1680,14 +1796,43 @@ def messages(self) -> MessagesResourceWithStreamingResponse:
@cached_property
def location(self) -> LocationResourceWithStreamingResponse:
- """Request and retrieve real-time location data via iMessage.
-
- Use these endpoints to request a contact's location, retrieve location data
- for contacts who are sharing with you, and subscribe to webhooks when someone
- starts or stops sharing their location.
+ """
+ Request a contact's location, retrieve location for contacts sharing with you,
+ and subscribe to webhooks when someone starts or stops sharing.
**Coordinates** are returned in [GeoJSON](https://datatracker.ietf.org/doc/html/rfc7946) format:
`[longitude, latitude]` or `[longitude, latitude, altitude]` if altitude is available.
+
+ ### Reading location is poll-based
+
+ Poll `GET /v3/chats/{chatId}/location` whenever you need the latest position.
+ **There is no webhook that pushes updated coordinates** — the
+ `location.sharing.started` / `location.sharing.stopped` webhooks fire only when a
+ contact begins or ends sharing, not on each position update. To track a moving
+ contact, poll the `GET` endpoint.
+
+ ### Freshness
+
+ Each feature's `properties.updated_at` tells you when that participant's
+ location was last updated — use it to judge freshness.
+
+ ### Polling guidance
+
+ Locations refresh on Apple's cadence, not per request — polling faster than a
+ participant's location actually updates just returns the same position. Poll at a
+ modest interval (for example, once every few minutes per chat) rather than
+ continuously.
+
+ ### Why is location empty after `location.sharing.started` fired?
+
+ If the contact started sharing from the **standalone Find My app** instead of the
+ Messages conversation, the share may be tied to their **Apple ID email** rather
+ than their phone number — the webhook's `shared_by` field shows the email in that
+ case. Location is readable only through a chat with the handle that shared, so
+ `GET /v3/chats/{chatId}/location` on the phone-number chat stays empty.
+
+ The fix: have the contact stop sharing and re-share from **Find My inside the
+ Messages conversation** with your number.
"""
return LocationResourceWithStreamingResponse(self._chats.location)
@@ -1828,13 +1973,42 @@ def messages(self) -> AsyncMessagesResourceWithStreamingResponse:
@cached_property
def location(self) -> AsyncLocationResourceWithStreamingResponse:
- """Request and retrieve real-time location data via iMessage.
-
- Use these endpoints to request a contact's location, retrieve location data
- for contacts who are sharing with you, and subscribe to webhooks when someone
- starts or stops sharing their location.
+ """
+ Request a contact's location, retrieve location for contacts sharing with you,
+ and subscribe to webhooks when someone starts or stops sharing.
**Coordinates** are returned in [GeoJSON](https://datatracker.ietf.org/doc/html/rfc7946) format:
`[longitude, latitude]` or `[longitude, latitude, altitude]` if altitude is available.
+
+ ### Reading location is poll-based
+
+ Poll `GET /v3/chats/{chatId}/location` whenever you need the latest position.
+ **There is no webhook that pushes updated coordinates** — the
+ `location.sharing.started` / `location.sharing.stopped` webhooks fire only when a
+ contact begins or ends sharing, not on each position update. To track a moving
+ contact, poll the `GET` endpoint.
+
+ ### Freshness
+
+ Each feature's `properties.updated_at` tells you when that participant's
+ location was last updated — use it to judge freshness.
+
+ ### Polling guidance
+
+ Locations refresh on Apple's cadence, not per request — polling faster than a
+ participant's location actually updates just returns the same position. Poll at a
+ modest interval (for example, once every few minutes per chat) rather than
+ continuously.
+
+ ### Why is location empty after `location.sharing.started` fired?
+
+ If the contact started sharing from the **standalone Find My app** instead of the
+ Messages conversation, the share may be tied to their **Apple ID email** rather
+ than their phone number — the webhook's `shared_by` field shows the email in that
+ case. Location is readable only through a chat with the handle that shared, so
+ `GET /v3/chats/{chatId}/location` on the phone-number chat stays empty.
+
+ The fix: have the contact stop sharing and re-share from **Find My inside the
+ Messages conversation** with your number.
"""
return AsyncLocationResourceWithStreamingResponse(self._chats.location)
diff --git a/src/linq/resources/chats/location.py b/src/linq/resources/chats/location.py
index d2dc409..9c9dd4b 100644
--- a/src/linq/resources/chats/location.py
+++ b/src/linq/resources/chats/location.py
@@ -22,14 +22,43 @@
class LocationResource(SyncAPIResource):
- """Request and retrieve real-time location data via iMessage.
-
- Use these endpoints to request a contact's location, retrieve location data
- for contacts who are sharing with you, and subscribe to webhooks when someone
- starts or stops sharing their location.
+ """
+ Request a contact's location, retrieve location for contacts sharing with you,
+ and subscribe to webhooks when someone starts or stops sharing.
**Coordinates** are returned in [GeoJSON](https://datatracker.ietf.org/doc/html/rfc7946) format:
`[longitude, latitude]` or `[longitude, latitude, altitude]` if altitude is available.
+
+ ### Reading location is poll-based
+
+ Poll `GET /v3/chats/{chatId}/location` whenever you need the latest position.
+ **There is no webhook that pushes updated coordinates** — the
+ `location.sharing.started` / `location.sharing.stopped` webhooks fire only when a
+ contact begins or ends sharing, not on each position update. To track a moving
+ contact, poll the `GET` endpoint.
+
+ ### Freshness
+
+ Each feature's `properties.updated_at` tells you when that participant's
+ location was last updated — use it to judge freshness.
+
+ ### Polling guidance
+
+ Locations refresh on Apple's cadence, not per request — polling faster than a
+ participant's location actually updates just returns the same position. Poll at a
+ modest interval (for example, once every few minutes per chat) rather than
+ continuously.
+
+ ### Why is location empty after `location.sharing.started` fired?
+
+ If the contact started sharing from the **standalone Find My app** instead of the
+ Messages conversation, the share may be tied to their **Apple ID email** rather
+ than their phone number — the webhook's `shared_by` field shows the email in that
+ case. Location is readable only through a chat with the handle that shared, so
+ `GET /v3/chats/{chatId}/location` on the phone-number chat stays empty.
+
+ The fix: have the contact stop sharing and re-share from **Find My inside the
+ Messages conversation** with your number.
"""
@cached_property
@@ -75,7 +104,12 @@ def retrieve(
`properties.handle` identifies the user.
Returns an empty `data.features` array if no one is sharing or no location data
- is available yet.
+ is available yet. If sharing started but this stays empty, see the **Location
+ Sharing** overview.
+
+ Poll this endpoint to track a moving contact. `properties.updated_at` reflects
+ when each participant's location was last updated. There is no coordinate-update
+ webhook. See the **Location Sharing** overview for polling guidance.
Args:
extra_headers: Send extra headers
@@ -108,14 +142,22 @@ def request(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> LocationRequestResponse:
- """Send a location sharing request to a contact.
+ """Request a contact in a chat to share their location.
+
+ They receive an iMessage
+ prompt and must accept before any location is available; once they do, read
+ their location coordinates with `GET /v3/chats/{chatId}/location`.
+
+ The request is delivered asynchronously. The endpoint returns immediately with
+ `{ "success": true, "message": "Location request sent" }` and does not return
+ coordinates.
- They will receive an iMessage
- prompt asking them to share their location.
+ Location requests only work in **1:1 iMessage chats** (Apple limitation):
- Location requests only work in **1:1 iMessage chats** (Apple limitation).
- Attempting to request location in a group chat, or in an SMS or RCS chat,
- returns `409` (Operation not supported on this chat's service type).
+ - Group chats (any service) return `409` with code `2016`
+ (`GroupChatNotSupported`).
+ - 1:1 SMS and RCS chats return `409` with code `2017`
+ (`ChatServiceNotSupported`).
Args:
extra_headers: Send extra headers
@@ -138,14 +180,43 @@ def request(
class AsyncLocationResource(AsyncAPIResource):
- """Request and retrieve real-time location data via iMessage.
-
- Use these endpoints to request a contact's location, retrieve location data
- for contacts who are sharing with you, and subscribe to webhooks when someone
- starts or stops sharing their location.
+ """
+ Request a contact's location, retrieve location for contacts sharing with you,
+ and subscribe to webhooks when someone starts or stops sharing.
**Coordinates** are returned in [GeoJSON](https://datatracker.ietf.org/doc/html/rfc7946) format:
`[longitude, latitude]` or `[longitude, latitude, altitude]` if altitude is available.
+
+ ### Reading location is poll-based
+
+ Poll `GET /v3/chats/{chatId}/location` whenever you need the latest position.
+ **There is no webhook that pushes updated coordinates** — the
+ `location.sharing.started` / `location.sharing.stopped` webhooks fire only when a
+ contact begins or ends sharing, not on each position update. To track a moving
+ contact, poll the `GET` endpoint.
+
+ ### Freshness
+
+ Each feature's `properties.updated_at` tells you when that participant's
+ location was last updated — use it to judge freshness.
+
+ ### Polling guidance
+
+ Locations refresh on Apple's cadence, not per request — polling faster than a
+ participant's location actually updates just returns the same position. Poll at a
+ modest interval (for example, once every few minutes per chat) rather than
+ continuously.
+
+ ### Why is location empty after `location.sharing.started` fired?
+
+ If the contact started sharing from the **standalone Find My app** instead of the
+ Messages conversation, the share may be tied to their **Apple ID email** rather
+ than their phone number — the webhook's `shared_by` field shows the email in that
+ case. Location is readable only through a chat with the handle that shared, so
+ `GET /v3/chats/{chatId}/location` on the phone-number chat stays empty.
+
+ The fix: have the contact stop sharing and re-share from **Find My inside the
+ Messages conversation** with your number.
"""
@cached_property
@@ -191,7 +262,12 @@ async def retrieve(
`properties.handle` identifies the user.
Returns an empty `data.features` array if no one is sharing or no location data
- is available yet.
+ is available yet. If sharing started but this stays empty, see the **Location
+ Sharing** overview.
+
+ Poll this endpoint to track a moving contact. `properties.updated_at` reflects
+ when each participant's location was last updated. There is no coordinate-update
+ webhook. See the **Location Sharing** overview for polling guidance.
Args:
extra_headers: Send extra headers
@@ -224,14 +300,22 @@ async def request(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> LocationRequestResponse:
- """Send a location sharing request to a contact.
+ """Request a contact in a chat to share their location.
+
+ They receive an iMessage
+ prompt and must accept before any location is available; once they do, read
+ their location coordinates with `GET /v3/chats/{chatId}/location`.
+
+ The request is delivered asynchronously. The endpoint returns immediately with
+ `{ "success": true, "message": "Location request sent" }` and does not return
+ coordinates.
- They will receive an iMessage
- prompt asking them to share their location.
+ Location requests only work in **1:1 iMessage chats** (Apple limitation):
- Location requests only work in **1:1 iMessage chats** (Apple limitation).
- Attempting to request location in a group chat, or in an SMS or RCS chat,
- returns `409` (Operation not supported on this chat's service type).
+ - Group chats (any service) return `409` with code `2016`
+ (`GroupChatNotSupported`).
+ - 1:1 SMS and RCS chats return `409` with code `2017`
+ (`ChatServiceNotSupported`).
Args:
extra_headers: Send extra headers
diff --git a/src/linq/resources/messages.py b/src/linq/resources/messages.py
index f2490ab..5078412 100644
--- a/src/linq/resources/messages.py
+++ b/src/linq/resources/messages.py
@@ -141,12 +141,14 @@ def create(
## How the from-number and chat are chosen
- - **Reuse** — if a chat with exactly these recipients already exists and the
- line it lives on is healthy, the message is sent into that chat on its
- existing line (`from_selection.reason = reused_active_chat`).
+ - **Reuse** — if a chat with exactly these recipients already exists on a line
+ that can still send, the message is sent into that chat on its existing line
+ (`from_selection.reason = reused_active_chat`). The most-recently-active such
+ chat wins; chats stranded on flagged lines (e.g. by an earlier failover) are
+ skipped.
- **New** — if no such chat exists, a new chat is created on the best available
line (`from_selection.reason = new_best_number`).
- - **Failover** — if a matching chat exists but its line has been flagged, a
+ - **Failover** — if matching chats exist but none is on a line that can send, a
**new** chat is created on a fresh best line and the flagged chat is abandoned
(`from_selection.reason = failover_flagged`, `previous_chat_id` set). If you
supply `continuation_message`, that text is sent as the single message INSTEAD
@@ -667,12 +669,14 @@ async def create(
## How the from-number and chat are chosen
- - **Reuse** — if a chat with exactly these recipients already exists and the
- line it lives on is healthy, the message is sent into that chat on its
- existing line (`from_selection.reason = reused_active_chat`).
+ - **Reuse** — if a chat with exactly these recipients already exists on a line
+ that can still send, the message is sent into that chat on its existing line
+ (`from_selection.reason = reused_active_chat`). The most-recently-active such
+ chat wins; chats stranded on flagged lines (e.g. by an earlier failover) are
+ skipped.
- **New** — if no such chat exists, a new chat is created on the best available
line (`from_selection.reason = new_best_number`).
- - **Failover** — if a matching chat exists but its line has been flagged, a
+ - **Failover** — if matching chats exist but none is on a line that can send, a
**new** chat is created on a fresh best line and the flagged chat is abandoned
(`from_selection.reason = failover_flagged`, `previous_chat_id` set). If you
supply `continuation_message`, that text is sent as the single message INSTEAD
diff --git a/src/linq/resources/payment_requests.py b/src/linq/resources/payment_requests.py
new file mode 100644
index 0000000..537a5af
--- /dev/null
+++ b/src/linq/resources/payment_requests.py
@@ -0,0 +1,838 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union
+from datetime import datetime
+from typing_extensions import Literal
+
+import httpx
+
+from ..types import payment_request_list_params, payment_request_create_params
+from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from .._utils import path_template, maybe_transform, strip_not_given, async_maybe_transform
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+ 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.payment_request import PaymentRequest
+from ..types.payment_request_list_response import PaymentRequestListResponse
+
+__all__ = ["PaymentRequestsResource", "AsyncPaymentRequestsResource"]
+
+
+class PaymentRequestsResource(SyncAPIResource):
+ """Request a payment from a recipient over iMessage.
+
+ You create a payment
+ request, send its `checkout_url` to the recipient, and they pay with Apple
+ Pay or card. Funds settle **directly to your own Stripe account** — Linq
+ never holds the money.
+
+ ## How it works
+
+ 1. **Create** a payment request with an amount and currency. You get back a
+ `checkout_url` and a `status` of `requested`.
+ 2. **Send** the `checkout_url` to the recipient as a `link` message part so
+ it arrives as a tappable card (see *Sending the link* below).
+ 3. The recipient **pays** on the hosted checkout (Apple Pay App Clip on a
+ supported iPhone, web checkout everywhere else).
+ 4. You receive a **`payment.succeeded`** webhook and the request's `status`
+ becomes `succeeded`. Requests you don't collect eventually `expire`.
+
+ ## Connected accounts (Stripe Standard, direct charges)
+
+ Agent Pay runs on **Stripe Connect Standard accounts** using **direct
+ charges**: the charge is created on *your* connected account and **you are
+ the merchant of record**. That means the money, the payout schedule, the
+ customer relationship, and the compliance surface are all yours — Linq
+ orchestrates the request and the checkout but is never in the funds flow.
+
+ **Refunds, disputes, and chargebacks are handled by you, in your own Stripe
+ Dashboard.** Because charges settle directly to your account, Linq has no
+ custody of the funds and cannot issue refunds or contest disputes on your
+ behalf — and there is no refund/dispute endpoint in this API by design. Use
+ the Stripe Dashboard (or the Stripe API on your own account) for the money
+ lifecycle after a payment succeeds.
+
+ ## Getting set up
+
+ Open **Agent Pay** in your Linq dashboard
+ (`https://zero.linqapp.com/organization/payments`), click **Connect Stripe**,
+ and complete Stripe's onboarding (business details + a bank account). When
+ your account reaches `charges_enabled`, request creation unlocks; until you
+ connect Stripe, `POST /v3/payment_requests` returns `403`. You can keep
+ collecting even while Stripe finishes background verification.
+
+ ## Subscriptions
+
+ Set `mode: subscription` on `POST /v3/payment_requests` to start an
+ **auto-renewing subscription** instead of a one-time charge. Instead of an
+ amount, you pass a `price_id` — an active **recurring Price** on your
+ connected Stripe account (create one in your Stripe Dashboard under
+ Product catalog; if you sell through Stripe Payment Links today, reuse the
+ price your link is built from). The recipient pays the first invoice at
+ the same checkout, and their payment method is saved to the subscription
+ for automatic renewals.
+
+ The division of labor is deliberate: **Linq handles the first payment,
+ your Stripe account handles the rest.** The request reaches `succeeded`
+ when the first invoice is paid; from then on the subscription lives
+ entirely on your connected account. The response's `stripe` object gives
+ you the join keys — `customer_id` and `subscription_id` — so renewals,
+ plan changes, dunning, and cancellation are managed with your own Stripe
+ Dashboard/API and your own Stripe webhooks. Your `metadata` is stamped on
+ the Customer and Subscription, so correlating in either direction is
+ trivial. There are no renewal webhooks from Linq by design.
+
+ ### Free trials
+
+ Add `trial_period_days` (or a fixed `trial_end` timestamp) to start the
+ subscription with a free trial. The checkout still collects the
+ recipient's payment method — the pay sheet shows "$0 due today" with the
+ first charge date — and saves it to the subscription; Stripe bills it
+ automatically when the trial ends. The request reaches `succeeded` when
+ the card is collected, and the response carries `trial_end`. If the trial
+ would end without a payment method on file, the subscription cancels
+ rather than generating unpayable invoices. Trial lifecycle after checkout
+ (extending, ending early) is managed in your own Stripe account via
+ `stripe.subscription_id`.
+
+ A subscription request you cancel (or that expires unpaid) cancels the
+ incomplete Stripe subscription — nothing lingers on your account.
+
+ ## Pre-created customers
+
+ By default each request stands alone: payment mode attaches no Customer,
+ and subscription mode creates a fresh one. If you already manage
+ Customers on your connected account, pass their id as `customer_id`
+ (`cus_...`) on create — in payment mode the charge lands on that
+ customer's payment history, and in subscription mode the subscription is
+ created on them instead of on a new Customer. The id must reference an
+ existing, non-deleted customer on your connected account or the request
+ fails with `400`. We never modify a customer you pass — no metadata is
+ stamped on it.
+
+ ## Sending the link
+
+ Deliver the `checkout_url` as a **`link` message part** via
+ `POST /v3/chats/{chatId}/messages` — it renders as a rich card with your
+ branding (title, amount, image) instead of a bare URL, which converts far
+ better. A `link` part must be the only part in the message. See
+ [Rich Link Previews](/guides/messaging/sending-messages).
+
+ On a supported iPhone the link opens an **Apple Pay App Clip** — a native,
+ no-install checkout sheet. Everywhere else (Android, desktop, iPhones
+ without the App Clip yet) the same URL opens the web checkout, so the link
+ always works. The App Clip experience for your payment links is registered
+ automatically by Linq and refreshed whenever you update your Agent Pay
+ branding; a newly registered experience can take up to ~24 hours to
+ activate on Apple's side, during which links open the web checkout.
+
+ ## Webhooks
+
+ Subscribe to payment lifecycle events to reconcile server-side rather than
+ polling: `payment.succeeded`, `payment.canceled`, and `payment.expired`.
+ Each event carries the payment request id, amount, currency, and your
+ `metadata`. See [Webhooks](/guides/webhooks).
+ """
+
+ @cached_property
+ def with_raw_response(self) -> PaymentRequestsResourceWithRawResponse:
+ """
+ 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 PaymentRequestsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> PaymentRequestsResourceWithStreamingResponse:
+ """
+ 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 PaymentRequestsResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ *,
+ amount: int | Omit = omit,
+ currency: str | Omit = omit,
+ customer_id: str | Omit = omit,
+ description: str | Omit = omit,
+ metadata: Dict[str, str] | Omit = omit,
+ mode: Literal["payment", "subscription"] | Omit = omit,
+ price_id: str | Omit = omit,
+ quantity: int | Omit = omit,
+ trial_end: Union[str, datetime] | Omit = omit,
+ trial_period_days: int | 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PaymentRequest:
+ """
+ Creates a payment request and returns a `checkout_url` the recipient opens to
+ pay with Apple Pay or card. Funds settle directly to your connected Stripe
+ account. A payment request is independent of any chat; to associate one with a
+ chat for your records, store the chat id in `metadata`. Requires your connected
+ account to be `charges_enabled` (returns `403` otherwise).
+
+ Set `mode: subscription` with a recurring `price_id` from your connected Stripe
+ account to start an **auto-renewing subscription** instead of a one-time charge
+ — the recipient pays the first invoice at checkout and the response's `stripe`
+ object carries the customer and subscription ids for the ongoing lifecycle in
+ your own Stripe account. See the _Subscriptions_ section of the tag overview.
+
+ In either mode, pass `customer_id` to attach the request to an **existing
+ Customer** on your connected account instead of creating a new one — see
+ _Pre-created customers_ in the tag overview.
+
+ Args:
+ amount: Amount to charge, in the currency's minor units (e.g. cents). Must be at least
+ the payment provider's minimum (50 for `usd`). Required in `payment` mode; must
+ be omitted in `subscription` mode (the amount comes from the price).
+
+ currency: Three-letter ISO 4217 currency code. Only `usd` is currently supported. Required
+ in `payment` mode; must be omitted in `subscription` mode (the currency comes
+ from the price).
+
+ customer_id: Optional id of an **existing Customer** on your connected Stripe account
+ (`cus_...`) to attach this request to, instead of a new Customer being created.
+ In `payment` mode the charge lands on that customer's payment history; in
+ `subscription` mode the subscription is created on them. The customer must exist
+ (and not be deleted) on your connected account.
+
+ description: Optional description shown to the recipient at checkout.
+
+ metadata: Optional key/value metadata (up to 49 keys) echoed back on retrieval and on
+ `payment.*` webhooks, and stamped on the Stripe objects we create on your
+ connected account (the PaymentIntent, and in subscription mode the Subscription
+ and any Customer created for you — a customer you pass via `customer_id` is
+ never modified) — use it to correlate a request with your own records (e.g. a
+ chat id). Keys starting with `linq_` are reserved.
+
+ mode: `payment` (default) collects a one-time charge for `amount` + `currency`.
+ `subscription` starts an auto-renewing subscription from a recurring `price_id`
+ on your connected Stripe account: the recipient pays the first invoice at
+ checkout and Stripe renews it automatically from then on.
+
+ price_id: Subscription mode only (required there): id of an **active recurring Price** on
+ your connected Stripe account (`price_...`). If you sell through Stripe Payment
+ Links today, pass the same price the link was built from to get the native
+ iMessage checkout for it.
+
+ quantity: Subscription mode only — units of the price to subscribe to.
+
+ trial_end: Subscription mode only — end the free trial at a fixed timestamp (must be in the
+ future) instead of a day count. Mutually exclusive with `trial_period_days`.
+
+ trial_period_days: Subscription mode only — start with a free trial of this many days. The
+ recipient's card is still collected at checkout (Apple Pay or card), saved to
+ the subscription, and first charged when the trial ends. Mutually exclusive with
+ `trial_end`.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ extra_headers = {**strip_not_given({"Idempotency-Key": idempotency_key}), **(extra_headers or {})}
+ return self._post(
+ "/v3/payment_requests",
+ body=maybe_transform(
+ {
+ "amount": amount,
+ "currency": currency,
+ "customer_id": customer_id,
+ "description": description,
+ "metadata": metadata,
+ "mode": mode,
+ "price_id": price_id,
+ "quantity": quantity,
+ "trial_end": trial_end,
+ "trial_period_days": trial_period_days,
+ },
+ payment_request_create_params.PaymentRequestCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PaymentRequest,
+ )
+
+ def retrieve(
+ self,
+ payment_request_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,
+ ) -> PaymentRequest:
+ """
+ Returns a payment request's status and details.
+
+ 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 payment_request_id:
+ raise ValueError(f"Expected a non-empty value for `payment_request_id` but received {payment_request_id!r}")
+ return self._get(
+ path_template("/v3/payment_requests/{payment_request_id}", payment_request_id=payment_request_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PaymentRequest,
+ )
+
+ def list(
+ self,
+ *,
+ limit: int | Omit = omit,
+ offset: int | Omit = omit,
+ status: Literal["requested", "succeeded", "canceled", "expired"] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PaymentRequestListResponse:
+ """Lists your payment requests, newest first, for reconciliation.
+
+ Paginate with
+ `limit` + `offset`; `has_more` indicates whether another page exists.
+
+ Args:
+ limit: Max results to return (default 20, max 100).
+
+ offset: Number of results to skip.
+
+ status: Filter by lifecycle status.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._get(
+ "/v3/payment_requests",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "limit": limit,
+ "offset": offset,
+ "status": status,
+ },
+ payment_request_list_params.PaymentRequestListParams,
+ ),
+ ),
+ cast_to=PaymentRequestListResponse,
+ )
+
+ def cancel(
+ self,
+ payment_request_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,
+ ) -> PaymentRequest:
+ """
+ Cancels an unpaid payment request: the underlying payment intent is canceled and
+ the request moves to `canceled`. A request that is already paid, canceled, or
+ expired returns 409.
+
+ 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 payment_request_id:
+ raise ValueError(f"Expected a non-empty value for `payment_request_id` but received {payment_request_id!r}")
+ return self._post(
+ path_template("/v3/payment_requests/{payment_request_id}/cancel", payment_request_id=payment_request_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PaymentRequest,
+ )
+
+
+class AsyncPaymentRequestsResource(AsyncAPIResource):
+ """Request a payment from a recipient over iMessage.
+
+ You create a payment
+ request, send its `checkout_url` to the recipient, and they pay with Apple
+ Pay or card. Funds settle **directly to your own Stripe account** — Linq
+ never holds the money.
+
+ ## How it works
+
+ 1. **Create** a payment request with an amount and currency. You get back a
+ `checkout_url` and a `status` of `requested`.
+ 2. **Send** the `checkout_url` to the recipient as a `link` message part so
+ it arrives as a tappable card (see *Sending the link* below).
+ 3. The recipient **pays** on the hosted checkout (Apple Pay App Clip on a
+ supported iPhone, web checkout everywhere else).
+ 4. You receive a **`payment.succeeded`** webhook and the request's `status`
+ becomes `succeeded`. Requests you don't collect eventually `expire`.
+
+ ## Connected accounts (Stripe Standard, direct charges)
+
+ Agent Pay runs on **Stripe Connect Standard accounts** using **direct
+ charges**: the charge is created on *your* connected account and **you are
+ the merchant of record**. That means the money, the payout schedule, the
+ customer relationship, and the compliance surface are all yours — Linq
+ orchestrates the request and the checkout but is never in the funds flow.
+
+ **Refunds, disputes, and chargebacks are handled by you, in your own Stripe
+ Dashboard.** Because charges settle directly to your account, Linq has no
+ custody of the funds and cannot issue refunds or contest disputes on your
+ behalf — and there is no refund/dispute endpoint in this API by design. Use
+ the Stripe Dashboard (or the Stripe API on your own account) for the money
+ lifecycle after a payment succeeds.
+
+ ## Getting set up
+
+ Open **Agent Pay** in your Linq dashboard
+ (`https://zero.linqapp.com/organization/payments`), click **Connect Stripe**,
+ and complete Stripe's onboarding (business details + a bank account). When
+ your account reaches `charges_enabled`, request creation unlocks; until you
+ connect Stripe, `POST /v3/payment_requests` returns `403`. You can keep
+ collecting even while Stripe finishes background verification.
+
+ ## Subscriptions
+
+ Set `mode: subscription` on `POST /v3/payment_requests` to start an
+ **auto-renewing subscription** instead of a one-time charge. Instead of an
+ amount, you pass a `price_id` — an active **recurring Price** on your
+ connected Stripe account (create one in your Stripe Dashboard under
+ Product catalog; if you sell through Stripe Payment Links today, reuse the
+ price your link is built from). The recipient pays the first invoice at
+ the same checkout, and their payment method is saved to the subscription
+ for automatic renewals.
+
+ The division of labor is deliberate: **Linq handles the first payment,
+ your Stripe account handles the rest.** The request reaches `succeeded`
+ when the first invoice is paid; from then on the subscription lives
+ entirely on your connected account. The response's `stripe` object gives
+ you the join keys — `customer_id` and `subscription_id` — so renewals,
+ plan changes, dunning, and cancellation are managed with your own Stripe
+ Dashboard/API and your own Stripe webhooks. Your `metadata` is stamped on
+ the Customer and Subscription, so correlating in either direction is
+ trivial. There are no renewal webhooks from Linq by design.
+
+ ### Free trials
+
+ Add `trial_period_days` (or a fixed `trial_end` timestamp) to start the
+ subscription with a free trial. The checkout still collects the
+ recipient's payment method — the pay sheet shows "$0 due today" with the
+ first charge date — and saves it to the subscription; Stripe bills it
+ automatically when the trial ends. The request reaches `succeeded` when
+ the card is collected, and the response carries `trial_end`. If the trial
+ would end without a payment method on file, the subscription cancels
+ rather than generating unpayable invoices. Trial lifecycle after checkout
+ (extending, ending early) is managed in your own Stripe account via
+ `stripe.subscription_id`.
+
+ A subscription request you cancel (or that expires unpaid) cancels the
+ incomplete Stripe subscription — nothing lingers on your account.
+
+ ## Pre-created customers
+
+ By default each request stands alone: payment mode attaches no Customer,
+ and subscription mode creates a fresh one. If you already manage
+ Customers on your connected account, pass their id as `customer_id`
+ (`cus_...`) on create — in payment mode the charge lands on that
+ customer's payment history, and in subscription mode the subscription is
+ created on them instead of on a new Customer. The id must reference an
+ existing, non-deleted customer on your connected account or the request
+ fails with `400`. We never modify a customer you pass — no metadata is
+ stamped on it.
+
+ ## Sending the link
+
+ Deliver the `checkout_url` as a **`link` message part** via
+ `POST /v3/chats/{chatId}/messages` — it renders as a rich card with your
+ branding (title, amount, image) instead of a bare URL, which converts far
+ better. A `link` part must be the only part in the message. See
+ [Rich Link Previews](/guides/messaging/sending-messages).
+
+ On a supported iPhone the link opens an **Apple Pay App Clip** — a native,
+ no-install checkout sheet. Everywhere else (Android, desktop, iPhones
+ without the App Clip yet) the same URL opens the web checkout, so the link
+ always works. The App Clip experience for your payment links is registered
+ automatically by Linq and refreshed whenever you update your Agent Pay
+ branding; a newly registered experience can take up to ~24 hours to
+ activate on Apple's side, during which links open the web checkout.
+
+ ## Webhooks
+
+ Subscribe to payment lifecycle events to reconcile server-side rather than
+ polling: `payment.succeeded`, `payment.canceled`, and `payment.expired`.
+ Each event carries the payment request id, amount, currency, and your
+ `metadata`. See [Webhooks](/guides/webhooks).
+ """
+
+ @cached_property
+ def with_raw_response(self) -> AsyncPaymentRequestsResourceWithRawResponse:
+ """
+ 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 AsyncPaymentRequestsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncPaymentRequestsResourceWithStreamingResponse:
+ """
+ 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 AsyncPaymentRequestsResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ *,
+ amount: int | Omit = omit,
+ currency: str | Omit = omit,
+ customer_id: str | Omit = omit,
+ description: str | Omit = omit,
+ metadata: Dict[str, str] | Omit = omit,
+ mode: Literal["payment", "subscription"] | Omit = omit,
+ price_id: str | Omit = omit,
+ quantity: int | Omit = omit,
+ trial_end: Union[str, datetime] | Omit = omit,
+ trial_period_days: int | 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.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PaymentRequest:
+ """
+ Creates a payment request and returns a `checkout_url` the recipient opens to
+ pay with Apple Pay or card. Funds settle directly to your connected Stripe
+ account. A payment request is independent of any chat; to associate one with a
+ chat for your records, store the chat id in `metadata`. Requires your connected
+ account to be `charges_enabled` (returns `403` otherwise).
+
+ Set `mode: subscription` with a recurring `price_id` from your connected Stripe
+ account to start an **auto-renewing subscription** instead of a one-time charge
+ — the recipient pays the first invoice at checkout and the response's `stripe`
+ object carries the customer and subscription ids for the ongoing lifecycle in
+ your own Stripe account. See the _Subscriptions_ section of the tag overview.
+
+ In either mode, pass `customer_id` to attach the request to an **existing
+ Customer** on your connected account instead of creating a new one — see
+ _Pre-created customers_ in the tag overview.
+
+ Args:
+ amount: Amount to charge, in the currency's minor units (e.g. cents). Must be at least
+ the payment provider's minimum (50 for `usd`). Required in `payment` mode; must
+ be omitted in `subscription` mode (the amount comes from the price).
+
+ currency: Three-letter ISO 4217 currency code. Only `usd` is currently supported. Required
+ in `payment` mode; must be omitted in `subscription` mode (the currency comes
+ from the price).
+
+ customer_id: Optional id of an **existing Customer** on your connected Stripe account
+ (`cus_...`) to attach this request to, instead of a new Customer being created.
+ In `payment` mode the charge lands on that customer's payment history; in
+ `subscription` mode the subscription is created on them. The customer must exist
+ (and not be deleted) on your connected account.
+
+ description: Optional description shown to the recipient at checkout.
+
+ metadata: Optional key/value metadata (up to 49 keys) echoed back on retrieval and on
+ `payment.*` webhooks, and stamped on the Stripe objects we create on your
+ connected account (the PaymentIntent, and in subscription mode the Subscription
+ and any Customer created for you — a customer you pass via `customer_id` is
+ never modified) — use it to correlate a request with your own records (e.g. a
+ chat id). Keys starting with `linq_` are reserved.
+
+ mode: `payment` (default) collects a one-time charge for `amount` + `currency`.
+ `subscription` starts an auto-renewing subscription from a recurring `price_id`
+ on your connected Stripe account: the recipient pays the first invoice at
+ checkout and Stripe renews it automatically from then on.
+
+ price_id: Subscription mode only (required there): id of an **active recurring Price** on
+ your connected Stripe account (`price_...`). If you sell through Stripe Payment
+ Links today, pass the same price the link was built from to get the native
+ iMessage checkout for it.
+
+ quantity: Subscription mode only — units of the price to subscribe to.
+
+ trial_end: Subscription mode only — end the free trial at a fixed timestamp (must be in the
+ future) instead of a day count. Mutually exclusive with `trial_period_days`.
+
+ trial_period_days: Subscription mode only — start with a free trial of this many days. The
+ recipient's card is still collected at checkout (Apple Pay or card), saved to
+ the subscription, and first charged when the trial ends. Mutually exclusive with
+ `trial_end`.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ extra_headers = {**strip_not_given({"Idempotency-Key": idempotency_key}), **(extra_headers or {})}
+ return await self._post(
+ "/v3/payment_requests",
+ body=await async_maybe_transform(
+ {
+ "amount": amount,
+ "currency": currency,
+ "customer_id": customer_id,
+ "description": description,
+ "metadata": metadata,
+ "mode": mode,
+ "price_id": price_id,
+ "quantity": quantity,
+ "trial_end": trial_end,
+ "trial_period_days": trial_period_days,
+ },
+ payment_request_create_params.PaymentRequestCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PaymentRequest,
+ )
+
+ async def retrieve(
+ self,
+ payment_request_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,
+ ) -> PaymentRequest:
+ """
+ Returns a payment request's status and details.
+
+ 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 payment_request_id:
+ raise ValueError(f"Expected a non-empty value for `payment_request_id` but received {payment_request_id!r}")
+ return await self._get(
+ path_template("/v3/payment_requests/{payment_request_id}", payment_request_id=payment_request_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PaymentRequest,
+ )
+
+ async def list(
+ self,
+ *,
+ limit: int | Omit = omit,
+ offset: int | Omit = omit,
+ status: Literal["requested", "succeeded", "canceled", "expired"] | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> PaymentRequestListResponse:
+ """Lists your payment requests, newest first, for reconciliation.
+
+ Paginate with
+ `limit` + `offset`; `has_more` indicates whether another page exists.
+
+ Args:
+ limit: Max results to return (default 20, max 100).
+
+ offset: Number of results to skip.
+
+ status: Filter by lifecycle status.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._get(
+ "/v3/payment_requests",
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "limit": limit,
+ "offset": offset,
+ "status": status,
+ },
+ payment_request_list_params.PaymentRequestListParams,
+ ),
+ ),
+ cast_to=PaymentRequestListResponse,
+ )
+
+ async def cancel(
+ self,
+ payment_request_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,
+ ) -> PaymentRequest:
+ """
+ Cancels an unpaid payment request: the underlying payment intent is canceled and
+ the request moves to `canceled`. A request that is already paid, canceled, or
+ expired returns 409.
+
+ 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 payment_request_id:
+ raise ValueError(f"Expected a non-empty value for `payment_request_id` but received {payment_request_id!r}")
+ return await self._post(
+ path_template("/v3/payment_requests/{payment_request_id}/cancel", payment_request_id=payment_request_id),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=PaymentRequest,
+ )
+
+
+class PaymentRequestsResourceWithRawResponse:
+ def __init__(self, payment_requests: PaymentRequestsResource) -> None:
+ self._payment_requests = payment_requests
+
+ self.create = to_raw_response_wrapper(
+ payment_requests.create,
+ )
+ self.retrieve = to_raw_response_wrapper(
+ payment_requests.retrieve,
+ )
+ self.list = to_raw_response_wrapper(
+ payment_requests.list,
+ )
+ self.cancel = to_raw_response_wrapper(
+ payment_requests.cancel,
+ )
+
+
+class AsyncPaymentRequestsResourceWithRawResponse:
+ def __init__(self, payment_requests: AsyncPaymentRequestsResource) -> None:
+ self._payment_requests = payment_requests
+
+ self.create = async_to_raw_response_wrapper(
+ payment_requests.create,
+ )
+ self.retrieve = async_to_raw_response_wrapper(
+ payment_requests.retrieve,
+ )
+ self.list = async_to_raw_response_wrapper(
+ payment_requests.list,
+ )
+ self.cancel = async_to_raw_response_wrapper(
+ payment_requests.cancel,
+ )
+
+
+class PaymentRequestsResourceWithStreamingResponse:
+ def __init__(self, payment_requests: PaymentRequestsResource) -> None:
+ self._payment_requests = payment_requests
+
+ self.create = to_streamed_response_wrapper(
+ payment_requests.create,
+ )
+ self.retrieve = to_streamed_response_wrapper(
+ payment_requests.retrieve,
+ )
+ self.list = to_streamed_response_wrapper(
+ payment_requests.list,
+ )
+ self.cancel = to_streamed_response_wrapper(
+ payment_requests.cancel,
+ )
+
+
+class AsyncPaymentRequestsResourceWithStreamingResponse:
+ def __init__(self, payment_requests: AsyncPaymentRequestsResource) -> None:
+ self._payment_requests = payment_requests
+
+ self.create = async_to_streamed_response_wrapper(
+ payment_requests.create,
+ )
+ self.retrieve = async_to_streamed_response_wrapper(
+ payment_requests.retrieve,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ payment_requests.list,
+ )
+ self.cancel = async_to_streamed_response_wrapper(
+ payment_requests.cancel,
+ )
diff --git a/src/linq/types/__init__.py b/src/linq/types/__init__.py
index f8e252f..ecfd46b 100644
--- a/src/linq/types/__init__.py
+++ b/src/linq/types/__init__.py
@@ -18,6 +18,7 @@
from .message_effect import MessageEffect as MessageEffect
from .reply_to_param import ReplyToParam as ReplyToParam
from .link_part_param import LinkPartParam as LinkPartParam
+from .payment_request import PaymentRequest as PaymentRequest
from .text_part_param import TextPartParam as TextPartParam
from .media_part_param import MediaPartParam as MediaPartParam
from .message_event_v2 import MessageEventV2 as MessageEventV2
@@ -54,6 +55,7 @@
from .schemas_text_part_response import SchemasTextPartResponse as SchemasTextPartResponse
from .capability_check_RCS_params import CapabilityCheckRCSParams as CapabilityCheckRCSParams
from .message_add_reaction_params import MessageAddReactionParams as MessageAddReactionParams
+from .payment_request_list_params import PaymentRequestListParams as PaymentRequestListParams
from .schemas_media_part_response import SchemasMediaPartResponse as SchemasMediaPartResponse
from .webhook_event_list_response import WebhookEventListResponse as WebhookEventListResponse
from .attachment_retrieve_response import AttachmentRetrieveResponse as AttachmentRetrieveResponse
@@ -64,6 +66,8 @@
from .phone_number_update_response import PhoneNumberUpdateResponse as PhoneNumberUpdateResponse
from .reaction_added_webhook_event import ReactionAddedWebhookEvent as ReactionAddedWebhookEvent
from .message_add_reaction_response import MessageAddReactionResponse as MessageAddReactionResponse
+from .payment_request_create_params import PaymentRequestCreateParams as PaymentRequestCreateParams
+from .payment_request_list_response import PaymentRequestListResponse as PaymentRequestListResponse
from .contact_card_retrieve_response import ContactCardRetrieveResponse as ContactCardRetrieveResponse
from .message_received_webhook_event import MessageReceivedWebhookEvent as MessageReceivedWebhookEvent
from .message_update_app_card_params import MessageUpdateAppCardParams as MessageUpdateAppCardParams
diff --git a/src/linq/types/message_create_response.py b/src/linq/types/message_create_response.py
index f4d8d2d..bbac97e 100644
--- a/src/linq/types/message_create_response.py
+++ b/src/linq/types/message_create_response.py
@@ -20,8 +20,8 @@ class FromSelection(BaseModel):
"""
- `reused_active_chat` — reused an existing chat on its healthy line
- `new_best_number` — created a new chat on the best available line
- - `failover_flagged` — prior chat's line was flagged; created a new chat on a
- fresh line
+ - `failover_flagged` — no existing chat for these recipients was on a line that
+ could send; created a new chat on a fresh line
"""
reused_existing_chat: bool
diff --git a/src/linq/types/payment_request.py b/src/linq/types/payment_request.py
new file mode 100644
index 0000000..0ffdeb4
--- /dev/null
+++ b/src/linq/types/payment_request.py
@@ -0,0 +1,102 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Dict, Optional
+from datetime import datetime
+from typing_extensions import Literal
+
+from .._models import BaseModel
+
+__all__ = ["PaymentRequest", "Stripe"]
+
+
+class Stripe(BaseModel):
+ """
+ Ids of the Stripe objects created **on your connected account** —
+ your join keys into your own Stripe Dashboard, webhooks, and API.
+ After a subscription's first payment succeeds, its ongoing lifecycle
+ (renewals, plan changes, cancellation) is managed in your Stripe
+ account using `subscription_id`.
+ """
+
+ customer_id: Optional[str] = None
+ """The Customer this request is attached to (`cus_...`).
+
+ Always set in subscription mode (created for you unless you passed
+ `customer_id`); set in payment mode only when you passed one.
+ """
+
+ payment_intent_id: Optional[str] = None
+ """The PaymentIntent collected at checkout (`pi_...`)."""
+
+ subscription_id: Optional[str] = None
+ """Subscription mode — the Subscription (`sub_...`)."""
+
+
+class PaymentRequest(BaseModel):
+ id: str
+ """Unique identifier of the payment request."""
+
+ amount: int
+ """Amount in the currency's minor units.
+
+ In `subscription` mode this is the recurring amount (price × quantity) the
+ recipient pays per interval, starting at checkout.
+ """
+
+ checkout_url: str
+ """
+ URL the recipient opens to pay:
+ `https://zero.linqapp.com/pay/{slug}?session=...`, where `{slug}` is your
+ partner checkout slug.
+ """
+
+ created_at: datetime
+
+ currency: str
+
+ mode: Literal["payment", "subscription"]
+ """Whether this request collects a one-time charge or starts a subscription."""
+
+ object: str
+
+ status: Literal["requested", "succeeded", "canceled", "expired"]
+ """Lifecycle status of the payment request."""
+
+ description: Optional[str] = None
+
+ expires_at: Optional[datetime] = None
+ """When an unpaid request auto-expires."""
+
+ interval: Optional[Literal["day", "week", "month", "year"]] = None
+ """Subscription mode — how often the subscription renews."""
+
+ interval_count: Optional[int] = None
+ """Subscription mode — intervals per renewal (e.g. `3` + `month` = quarterly)."""
+
+ metadata: Optional[Dict[str, str]] = None
+
+ paid_at: Optional[datetime] = None
+ """When the request was paid. Absent until it succeeds."""
+
+ price_id: Optional[str] = None
+ """Subscription mode — the recurring price this request subscribes to."""
+
+ quantity: Optional[int] = None
+ """Subscription mode — units of the price subscribed to."""
+
+ stripe: Optional[Stripe] = None
+ """
+ Ids of the Stripe objects created **on your connected account** — your join keys
+ into your own Stripe Dashboard, webhooks, and API. After a subscription's first
+ payment succeeds, its ongoing lifecycle (renewals, plan changes, cancellation)
+ is managed in your Stripe account using `subscription_id`.
+ """
+
+ trial_end: Optional[datetime] = None
+ """Subscription mode — when the free trial ends and the first charge happens.
+
+ Present only on trial requests; `paid_at`/`succeeded` mean the payment method
+ was collected (no funds move until this time).
+ """
+
+ updated_at: Optional[datetime] = None
diff --git a/src/linq/types/payment_request_create_params.py b/src/linq/types/payment_request_create_params.py
new file mode 100644
index 0000000..b5f4927
--- /dev/null
+++ b/src/linq/types/payment_request_create_params.py
@@ -0,0 +1,85 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Dict, Union
+from datetime import datetime
+from typing_extensions import Literal, Annotated, TypedDict
+
+from .._utils import PropertyInfo
+
+__all__ = ["PaymentRequestCreateParams"]
+
+
+class PaymentRequestCreateParams(TypedDict, total=False):
+ amount: int
+ """Amount to charge, in the currency's minor units (e.g.
+
+ cents). Must be at least the payment provider's minimum (50 for `usd`). Required
+ in `payment` mode; must be omitted in `subscription` mode (the amount comes from
+ the price).
+ """
+
+ currency: str
+ """Three-letter ISO 4217 currency code.
+
+ Only `usd` is currently supported. Required in `payment` mode; must be omitted
+ in `subscription` mode (the currency comes from the price).
+ """
+
+ customer_id: str
+ """
+ Optional id of an **existing Customer** on your connected Stripe account
+ (`cus_...`) to attach this request to, instead of a new Customer being created.
+ In `payment` mode the charge lands on that customer's payment history; in
+ `subscription` mode the subscription is created on them. The customer must exist
+ (and not be deleted) on your connected account.
+ """
+
+ description: str
+ """Optional description shown to the recipient at checkout."""
+
+ metadata: Dict[str, str]
+ """
+ Optional key/value metadata (up to 49 keys) echoed back on retrieval and on
+ `payment.*` webhooks, and stamped on the Stripe objects we create on your
+ connected account (the PaymentIntent, and in subscription mode the Subscription
+ and any Customer created for you — a customer you pass via `customer_id` is
+ never modified) — use it to correlate a request with your own records (e.g. a
+ chat id). Keys starting with `linq_` are reserved.
+ """
+
+ mode: Literal["payment", "subscription"]
+ """`payment` (default) collects a one-time charge for `amount` + `currency`.
+
+ `subscription` starts an auto-renewing subscription from a recurring `price_id`
+ on your connected Stripe account: the recipient pays the first invoice at
+ checkout and Stripe renews it automatically from then on.
+ """
+
+ price_id: str
+ """
+ Subscription mode only (required there): id of an **active recurring Price** on
+ your connected Stripe account (`price_...`). If you sell through Stripe Payment
+ Links today, pass the same price the link was built from to get the native
+ iMessage checkout for it.
+ """
+
+ quantity: int
+ """Subscription mode only — units of the price to subscribe to."""
+
+ trial_end: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")]
+ """
+ Subscription mode only — end the free trial at a fixed timestamp (must be in the
+ future) instead of a day count. Mutually exclusive with `trial_period_days`.
+ """
+
+ trial_period_days: int
+ """
+ Subscription mode only — start with a free trial of this many days. The
+ recipient's card is still collected at checkout (Apple Pay or card), saved to
+ the subscription, and first charged when the trial ends. Mutually exclusive with
+ `trial_end`.
+ """
+
+ idempotency_key: Annotated[str, PropertyInfo(alias="Idempotency-Key")]
diff --git a/src/linq/types/payment_request_list_params.py b/src/linq/types/payment_request_list_params.py
new file mode 100644
index 0000000..dbd7da8
--- /dev/null
+++ b/src/linq/types/payment_request_list_params.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, TypedDict
+
+__all__ = ["PaymentRequestListParams"]
+
+
+class PaymentRequestListParams(TypedDict, total=False):
+ limit: int
+ """Max results to return (default 20, max 100)."""
+
+ offset: int
+ """Number of results to skip."""
+
+ status: Literal["requested", "succeeded", "canceled", "expired"]
+ """Filter by lifecycle status."""
diff --git a/src/linq/types/payment_request_list_response.py b/src/linq/types/payment_request_list_response.py
new file mode 100644
index 0000000..e911b41
--- /dev/null
+++ b/src/linq/types/payment_request_list_response.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+from typing_extensions import Literal
+
+from .._models import BaseModel
+from .payment_request import PaymentRequest
+
+__all__ = ["PaymentRequestListResponse"]
+
+
+class PaymentRequestListResponse(BaseModel):
+ data: List[PaymentRequest]
+
+ has_more: bool
+ """Whether more results exist beyond this page."""
+
+ object: Literal["list"]
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 13ce18c..ce361b7 100644
--- a/src/linq/types/phone_number_status_updated_webhook_event.py
+++ b/src/linq/types/phone_number_status_updated_webhook_event.py
@@ -97,6 +97,9 @@ class PhoneNumberStatusUpdatedWebhookEvent(BaseModel):
"call.no_answer",
"location.sharing.started",
"location.sharing.stopped",
+ "payment.succeeded",
+ "payment.canceled",
+ "payment.expired",
]
"""The type of event"""
diff --git a/src/linq/types/webhook_event_type.py b/src/linq/types/webhook_event_type.py
index f3e760b..7aef1e6 100644
--- a/src/linq/types/webhook_event_type.py
+++ b/src/linq/types/webhook_event_type.py
@@ -32,4 +32,7 @@
"call.no_answer",
"location.sharing.started",
"location.sharing.stopped",
+ "payment.succeeded",
+ "payment.canceled",
+ "payment.expired",
]
diff --git a/tests/api_resources/test_payment_requests.py b/tests/api_resources/test_payment_requests.py
new file mode 100644
index 0000000..be3260c
--- /dev/null
+++ b/tests/api_resources/test_payment_requests.py
@@ -0,0 +1,364 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from linq import LinqAPIV3, AsyncLinqAPIV3
+from linq.types import (
+ PaymentRequest,
+ PaymentRequestListResponse,
+)
+from linq._utils import parse_datetime
+from tests.utils import assert_matches_type
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestPaymentRequests:
+ 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:
+ payment_request = client.payment_requests.create()
+ assert_matches_type(PaymentRequest, payment_request, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_create_with_all_params(self, client: LinqAPIV3) -> None:
+ payment_request = client.payment_requests.create(
+ amount=497,
+ currency="usd",
+ customer_id="cus_QAbCdEfGhIjKlMn",
+ description="Coffee with Ava",
+ metadata={"order_id": "order_8675309"},
+ mode="payment",
+ price_id="price_1QAbCdEfGhIjKlMn",
+ quantity=1,
+ trial_end=parse_datetime("2019-12-27T18:11:19.117Z"),
+ trial_period_days=14,
+ idempotency_key="pr-abc123xyz",
+ )
+ assert_matches_type(PaymentRequest, payment_request, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_create(self, client: LinqAPIV3) -> None:
+ response = client.payment_requests.with_raw_response.create()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ payment_request = response.parse()
+ assert_matches_type(PaymentRequest, payment_request, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_create(self, client: LinqAPIV3) -> None:
+ with client.payment_requests.with_streaming_response.create() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ payment_request = response.parse()
+ assert_matches_type(PaymentRequest, payment_request, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_retrieve(self, client: LinqAPIV3) -> None:
+ payment_request = client.payment_requests.retrieve(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+ assert_matches_type(PaymentRequest, payment_request, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_retrieve(self, client: LinqAPIV3) -> None:
+ response = client.payment_requests.with_raw_response.retrieve(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ payment_request = response.parse()
+ assert_matches_type(PaymentRequest, payment_request, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_retrieve(self, client: LinqAPIV3) -> None:
+ with client.payment_requests.with_streaming_response.retrieve(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ payment_request = response.parse()
+ assert_matches_type(PaymentRequest, payment_request, 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 `payment_request_id` but received ''"):
+ client.payment_requests.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list(self, client: LinqAPIV3) -> None:
+ payment_request = client.payment_requests.list()
+ assert_matches_type(PaymentRequestListResponse, payment_request, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_list_with_all_params(self, client: LinqAPIV3) -> None:
+ payment_request = client.payment_requests.list(
+ limit=1,
+ offset=0,
+ status="requested",
+ )
+ assert_matches_type(PaymentRequestListResponse, payment_request, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: LinqAPIV3) -> None:
+ response = client.payment_requests.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ payment_request = response.parse()
+ assert_matches_type(PaymentRequestListResponse, payment_request, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: LinqAPIV3) -> None:
+ with client.payment_requests.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ payment_request = response.parse()
+ assert_matches_type(PaymentRequestListResponse, payment_request, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_method_cancel(self, client: LinqAPIV3) -> None:
+ payment_request = client.payment_requests.cancel(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+ assert_matches_type(PaymentRequest, payment_request, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_raw_response_cancel(self, client: LinqAPIV3) -> None:
+ response = client.payment_requests.with_raw_response.cancel(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ payment_request = response.parse()
+ assert_matches_type(PaymentRequest, payment_request, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_streaming_response_cancel(self, client: LinqAPIV3) -> None:
+ with client.payment_requests.with_streaming_response.cancel(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ payment_request = response.parse()
+ assert_matches_type(PaymentRequest, payment_request, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ def test_path_params_cancel(self, client: LinqAPIV3) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `payment_request_id` but received ''"):
+ client.payment_requests.with_raw_response.cancel(
+ "",
+ )
+
+
+class TestAsyncPaymentRequests:
+ 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:
+ payment_request = await async_client.payment_requests.create()
+ assert_matches_type(PaymentRequest, payment_request, 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:
+ payment_request = await async_client.payment_requests.create(
+ amount=497,
+ currency="usd",
+ customer_id="cus_QAbCdEfGhIjKlMn",
+ description="Coffee with Ava",
+ metadata={"order_id": "order_8675309"},
+ mode="payment",
+ price_id="price_1QAbCdEfGhIjKlMn",
+ quantity=1,
+ trial_end=parse_datetime("2019-12-27T18:11:19.117Z"),
+ trial_period_days=14,
+ idempotency_key="pr-abc123xyz",
+ )
+ assert_matches_type(PaymentRequest, payment_request, 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.payment_requests.with_raw_response.create()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ payment_request = await response.parse()
+ assert_matches_type(PaymentRequest, payment_request, 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.payment_requests.with_streaming_response.create() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ payment_request = await response.parse()
+ assert_matches_type(PaymentRequest, payment_request, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_retrieve(self, async_client: AsyncLinqAPIV3) -> None:
+ payment_request = await async_client.payment_requests.retrieve(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+ assert_matches_type(PaymentRequest, payment_request, 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.payment_requests.with_raw_response.retrieve(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ payment_request = await response.parse()
+ assert_matches_type(PaymentRequest, payment_request, 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.payment_requests.with_streaming_response.retrieve(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ payment_request = await response.parse()
+ assert_matches_type(PaymentRequest, payment_request, 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 `payment_request_id` but received ''"):
+ await async_client.payment_requests.with_raw_response.retrieve(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list(self, async_client: AsyncLinqAPIV3) -> None:
+ payment_request = await async_client.payment_requests.list()
+ assert_matches_type(PaymentRequestListResponse, payment_request, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncLinqAPIV3) -> None:
+ payment_request = await async_client.payment_requests.list(
+ limit=1,
+ offset=0,
+ status="requested",
+ )
+ assert_matches_type(PaymentRequestListResponse, payment_request, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncLinqAPIV3) -> None:
+ response = await async_client.payment_requests.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ payment_request = await response.parse()
+ assert_matches_type(PaymentRequestListResponse, payment_request, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncLinqAPIV3) -> None:
+ async with async_client.payment_requests.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ payment_request = await response.parse()
+ assert_matches_type(PaymentRequestListResponse, payment_request, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_method_cancel(self, async_client: AsyncLinqAPIV3) -> None:
+ payment_request = await async_client.payment_requests.cancel(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+ assert_matches_type(PaymentRequest, payment_request, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_raw_response_cancel(self, async_client: AsyncLinqAPIV3) -> None:
+ response = await async_client.payment_requests.with_raw_response.cancel(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ payment_request = await response.parse()
+ assert_matches_type(PaymentRequest, payment_request, path=["response"])
+
+ @pytest.mark.skip(reason="Mock server tests are disabled")
+ @parametrize
+ async def test_streaming_response_cancel(self, async_client: AsyncLinqAPIV3) -> None:
+ async with async_client.payment_requests.with_streaming_response.cancel(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ payment_request = await response.parse()
+ assert_matches_type(PaymentRequest, payment_request, 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_cancel(self, async_client: AsyncLinqAPIV3) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `payment_request_id` but received ''"):
+ await async_client.payment_requests.with_raw_response.cancel(
+ "",
+ )
diff --git a/uv.lock b/uv.lock
index ee08702..f3050eb 100644
--- a/uv.lock
+++ b/uv.lock
@@ -530,7 +530,7 @@ wheels = [
[[package]]
name = "linq-python"
-version = "0.15.0"
+version = "0.15.1"
source = { editable = "." }
dependencies = [
{ name = "anyio" },