From 863b0ab91fbf5e897608fe9a012ccefa341a4be6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 01:17:05 +0000 Subject: [PATCH 01/35] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 8ca1285..e73111c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 195 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-c40acc9d58677fb284fa990c261b2494d9a7f1f1e4e9cf613546972e23255cb8.yml -openapi_spec_hash: adcf3faf651ee3eb5af6e1c0fe9d2b65 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-a4f91035228d14875f11ceb65eac3ba02ff823edc2f13968837527a99de7cf69.yml +openapi_spec_hash: 09814b5014474d8bc8785414416916f4 config_hash: 6ad5a913fda410def47bf2ed841e2064 From c1d9b0b051e02b88b1fd0b23b7cff596c40f6676 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 03:17:05 +0000 Subject: [PATCH 02/35] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index e73111c..9e343a0 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 195 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-a4f91035228d14875f11ceb65eac3ba02ff823edc2f13968837527a99de7cf69.yml -openapi_spec_hash: 09814b5014474d8bc8785414416916f4 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-ce56f2d0afde6433afb904ab0c2e321f289ff339672d1a9a6c65a87c6428d5e4.yml +openapi_spec_hash: 7986b39cf404765fa8915a5c4dff6062 config_hash: 6ad5a913fda410def47bf2ed841e2064 From 0449c0b3e63ac52b829408c1e92d1c9495c235c4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 06:17:06 +0000 Subject: [PATCH 03/35] feat(api): api update --- .stats.yml | 4 ++-- src/whop_sdk/types/payment_method_types.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 9e343a0..00829e7 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 195 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-ce56f2d0afde6433afb904ab0c2e321f289ff339672d1a9a6c65a87c6428d5e4.yml -openapi_spec_hash: 7986b39cf404765fa8915a5c4dff6062 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-ecf80962da95017821f00addb6491c9c6073d1301d41687d5a77d713e47154d1.yml +openapi_spec_hash: d0509a601c19612bc4983b47e4e80364 config_hash: 6ad5a913fda410def47bf2ed841e2064 diff --git a/src/whop_sdk/types/payment_method_types.py b/src/whop_sdk/types/payment_method_types.py index 66d2356..38bd959 100644 --- a/src/whop_sdk/types/payment_method_types.py +++ b/src/whop_sdk/types/payment_method_types.py @@ -45,6 +45,7 @@ "interac", "kakao_pay", "klarna", + "klarna_pay_now", "konbini", "kr_card", "kr_market", From 6f74baa3709b1b00e6196bd9b7caaedd13ecce9e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 21 Apr 2026 17:17:04 +0000 Subject: [PATCH 04/35] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 00829e7..62cf84e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 195 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-ecf80962da95017821f00addb6491c9c6073d1301d41687d5a77d713e47154d1.yml -openapi_spec_hash: d0509a601c19612bc4983b47e4e80364 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-e1fcaa6be00dd932fcdac3a5eac61c578c66308e14ef27ed64117107008c1c23.yml +openapi_spec_hash: f953e018bac8481dd0e3b7c0dfee7824 config_hash: 6ad5a913fda410def47bf2ed841e2064 From eea02e91bfcb142e308c88d7cdc5232b3a2c419e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 00:17:18 +0000 Subject: [PATCH 05/35] feat(api): api update --- .stats.yml | 4 ++-- src/whop_sdk/types/membership_list_response.py | 7 +++++++ src/whop_sdk/types/payment_list_response.py | 6 ++++++ src/whop_sdk/types/shared/membership.py | 7 +++++++ src/whop_sdk/types/shared/payment.py | 6 ++++++ 5 files changed, 28 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 62cf84e..43eb6b0 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 195 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-e1fcaa6be00dd932fcdac3a5eac61c578c66308e14ef27ed64117107008c1c23.yml -openapi_spec_hash: f953e018bac8481dd0e3b7c0dfee7824 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-77dd2aafc4ff7c47577dfe99d60e2c51d457c8ca2bf85ce70d75c76a5d4cd3ac.yml +openapi_spec_hash: 4fc659edb50df6155f4c5e8d681c3325 config_hash: 6ad5a913fda410def47bf2ed841e2064 diff --git a/src/whop_sdk/types/membership_list_response.py b/src/whop_sdk/types/membership_list_response.py index 13586e5..683d67d 100644 --- a/src/whop_sdk/types/membership_list_response.py +++ b/src/whop_sdk/types/membership_list_response.py @@ -114,6 +114,13 @@ class MembershipListResponse(BaseModel): Null if the customer did not provide a reason. """ + checkout_configuration_id: Optional[str] = None + """ + The ID of the checkout session/configuration that produced this membership, if + any. Use this to map memberships back to the checkout configuration that created + them. + """ + company: Company """The company this membership belongs to.""" diff --git a/src/whop_sdk/types/payment_list_response.py b/src/whop_sdk/types/payment_list_response.py index 1c12050..90ab9cc 100644 --- a/src/whop_sdk/types/payment_list_response.py +++ b/src/whop_sdk/types/payment_list_response.py @@ -259,6 +259,12 @@ class PaymentListResponse(BaseModel): Null if the payment was not made with a card. """ + checkout_configuration_id: Optional[str] = None + """The ID of the checkout session/configuration that produced this payment, if any. + + Use this to map payments back to the checkout configuration that created them. + """ + company: Optional[Company] = None """The company for the payment.""" diff --git a/src/whop_sdk/types/shared/membership.py b/src/whop_sdk/types/shared/membership.py index ac7d285..c2bcea6 100644 --- a/src/whop_sdk/types/shared/membership.py +++ b/src/whop_sdk/types/shared/membership.py @@ -127,6 +127,13 @@ class Membership(BaseModel): Null if the customer did not provide a reason. """ + checkout_configuration_id: Optional[str] = None + """ + The ID of the checkout session/configuration that produced this membership, if + any. Use this to map memberships back to the checkout configuration that created + them. + """ + company: Company """The company this membership belongs to.""" diff --git a/src/whop_sdk/types/shared/payment.py b/src/whop_sdk/types/shared/payment.py index 8a5aa2f..579c980 100644 --- a/src/whop_sdk/types/shared/payment.py +++ b/src/whop_sdk/types/shared/payment.py @@ -397,6 +397,12 @@ class Payment(BaseModel): Null if the payment was not made with a card. """ + checkout_configuration_id: Optional[str] = None + """The ID of the checkout session/configuration that produced this payment, if any. + + Use this to map payments back to the checkout configuration that created them. + """ + company: Optional[Company] = None """The company for the payment.""" From 1ea4c8d29ddb502307f75fc72296f7f749d073ce Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 08:16:59 +0000 Subject: [PATCH 06/35] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 43eb6b0..87b88ff 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 195 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-77dd2aafc4ff7c47577dfe99d60e2c51d457c8ca2bf85ce70d75c76a5d4cd3ac.yml -openapi_spec_hash: 4fc659edb50df6155f4c5e8d681c3325 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-b487d5b27ce082e72b2540b48551c9530953af151944d48454bed86663e29113.yml +openapi_spec_hash: c878b67bab854a2594cd9e3aa05db88d config_hash: 6ad5a913fda410def47bf2ed841e2064 From 6b442f0c46d096523edfe775f8bb0c602b8f9bd3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 14:01:12 +0000 Subject: [PATCH 07/35] chore(internal): more robust bootstrap script --- scripts/bootstrap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/bootstrap b/scripts/bootstrap index b430fee..fe8451e 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -4,7 +4,7 @@ set -e cd "$(dirname "$0")/.." -if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then +if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "${SKIP_BREW:-}" != "1" ] && [ -t 0 ]; then brew bundle check >/dev/null 2>&1 || { echo -n "==> Install Homebrew dependencies? (y/N): " read -r response From f1d06ff881c5d7667edde20d8bd2ee7246807173 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 19:16:58 +0000 Subject: [PATCH 08/35] feat(api): api update --- .stats.yml | 4 +-- src/whop_sdk/resources/support_channels.py | 26 +++++++++++----- .../types/support_channel_list_params.py | 15 +++++++--- tests/api_resources/test_support_channels.py | 30 +++++++------------ 4 files changed, 41 insertions(+), 34 deletions(-) diff --git a/.stats.yml b/.stats.yml index 87b88ff..93a53a4 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 195 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-b487d5b27ce082e72b2540b48551c9530953af151944d48454bed86663e29113.yml -openapi_spec_hash: c878b67bab854a2594cd9e3aa05db88d +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-8733d0ecd8e41b7189b8c0c694a01db9533be105906e2c924aeba90ba10b2545.yml +openapi_spec_hash: e36c7496d3cce5cabd6ae59fb097b2b3 config_hash: 6ad5a913fda410def47bf2ed841e2064 diff --git a/src/whop_sdk/resources/support_channels.py b/src/whop_sdk/resources/support_channels.py index 7d919e4..f1fb1e4 100644 --- a/src/whop_sdk/resources/support_channels.py +++ b/src/whop_sdk/resources/support_channels.py @@ -141,14 +141,15 @@ def retrieve( def list( self, *, - company_id: str, after: Optional[str] | Omit = omit, before: Optional[str] | Omit = omit, + company_id: Optional[str] | Omit = omit, direction: Optional[Direction] | Omit = omit, first: Optional[int] | Omit = omit, last: Optional[int] | Omit = omit, open: Optional[bool] | Omit = omit, order: Optional[Literal["created_at", "last_post_sent_at"]] | Omit = omit, + view: Optional[Literal["all", "admin", "customer"]] | 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, @@ -165,12 +166,13 @@ def list( - `support_chat:read` Args: - company_id: The unique identifier of the company to list support channels for. - after: Returns the elements in the list that come after the specified cursor. before: Returns the elements in the list that come before the specified cursor. + company_id: The unique identifier of the company to list support channels for. When omitted, + returns support channels across all companies the user has access to. + direction: The direction of the sort. first: Returns the first _n_ elements from the list. @@ -182,6 +184,8 @@ def list( order: Sort options for message channels + view: The perspective to filter support channels by. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -200,14 +204,15 @@ def list( timeout=timeout, query=maybe_transform( { - "company_id": company_id, "after": after, "before": before, + "company_id": company_id, "direction": direction, "first": first, "last": last, "open": open, "order": order, + "view": view, }, support_channel_list_params.SupportChannelListParams, ), @@ -330,14 +335,15 @@ async def retrieve( def list( self, *, - company_id: str, after: Optional[str] | Omit = omit, before: Optional[str] | Omit = omit, + company_id: Optional[str] | Omit = omit, direction: Optional[Direction] | Omit = omit, first: Optional[int] | Omit = omit, last: Optional[int] | Omit = omit, open: Optional[bool] | Omit = omit, order: Optional[Literal["created_at", "last_post_sent_at"]] | Omit = omit, + view: Optional[Literal["all", "admin", "customer"]] | 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, @@ -354,12 +360,13 @@ def list( - `support_chat:read` Args: - company_id: The unique identifier of the company to list support channels for. - after: Returns the elements in the list that come after the specified cursor. before: Returns the elements in the list that come before the specified cursor. + company_id: The unique identifier of the company to list support channels for. When omitted, + returns support channels across all companies the user has access to. + direction: The direction of the sort. first: Returns the first _n_ elements from the list. @@ -371,6 +378,8 @@ def list( order: Sort options for message channels + view: The perspective to filter support channels by. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -389,14 +398,15 @@ def list( timeout=timeout, query=maybe_transform( { - "company_id": company_id, "after": after, "before": before, + "company_id": company_id, "direction": direction, "first": first, "last": last, "open": open, "order": order, + "view": view, }, support_channel_list_params.SupportChannelListParams, ), diff --git a/src/whop_sdk/types/support_channel_list_params.py b/src/whop_sdk/types/support_channel_list_params.py index 46b2300..b930064 100644 --- a/src/whop_sdk/types/support_channel_list_params.py +++ b/src/whop_sdk/types/support_channel_list_params.py @@ -3,7 +3,7 @@ from __future__ import annotations from typing import Optional -from typing_extensions import Literal, Required, TypedDict +from typing_extensions import Literal, TypedDict from .shared.direction import Direction @@ -11,15 +11,19 @@ class SupportChannelListParams(TypedDict, total=False): - company_id: Required[str] - """The unique identifier of the company to list support channels for.""" - after: Optional[str] """Returns the elements in the list that come after the specified cursor.""" before: Optional[str] """Returns the elements in the list that come before the specified cursor.""" + company_id: Optional[str] + """The unique identifier of the company to list support channels for. + + When omitted, returns support channels across all companies the user has access + to. + """ + direction: Optional[Direction] """The direction of the sort.""" @@ -38,3 +42,6 @@ class SupportChannelListParams(TypedDict, total=False): order: Optional[Literal["created_at", "last_post_sent_at"]] """Sort options for message channels""" + + view: Optional[Literal["all", "admin", "customer"]] + """The perspective to filter support channels by.""" diff --git a/tests/api_resources/test_support_channels.py b/tests/api_resources/test_support_channels.py index 88f4014..272e8e5 100644 --- a/tests/api_resources/test_support_channels.py +++ b/tests/api_resources/test_support_channels.py @@ -113,32 +113,29 @@ def test_path_params_retrieve(self, client: Whop) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list(self, client: Whop) -> None: - support_channel = client.support_channels.list( - company_id="biz_xxxxxxxxxxxxxx", - ) + support_channel = client.support_channels.list() assert_matches_type(SyncCursorPage[SupportChannelListResponse], support_channel, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_method_list_with_all_params(self, client: Whop) -> None: support_channel = client.support_channels.list( - company_id="biz_xxxxxxxxxxxxxx", after="after", before="before", + company_id="biz_xxxxxxxxxxxxxx", direction="asc", first=42, last=42, open=True, order="created_at", + view="all", ) assert_matches_type(SyncCursorPage[SupportChannelListResponse], support_channel, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_list(self, client: Whop) -> None: - response = client.support_channels.with_raw_response.list( - company_id="biz_xxxxxxxxxxxxxx", - ) + response = client.support_channels.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -148,9 +145,7 @@ def test_raw_response_list(self, client: Whop) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_streaming_response_list(self, client: Whop) -> None: - with client.support_channels.with_streaming_response.list( - company_id="biz_xxxxxxxxxxxxxx", - ) as response: + with client.support_channels.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -257,32 +252,29 @@ async def test_path_params_retrieve(self, async_client: AsyncWhop) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list(self, async_client: AsyncWhop) -> None: - support_channel = await async_client.support_channels.list( - company_id="biz_xxxxxxxxxxxxxx", - ) + support_channel = await async_client.support_channels.list() assert_matches_type(AsyncCursorPage[SupportChannelListResponse], support_channel, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None: support_channel = await async_client.support_channels.list( - company_id="biz_xxxxxxxxxxxxxx", after="after", before="before", + company_id="biz_xxxxxxxxxxxxxx", direction="asc", first=42, last=42, open=True, order="created_at", + view="all", ) assert_matches_type(AsyncCursorPage[SupportChannelListResponse], support_channel, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_list(self, async_client: AsyncWhop) -> None: - response = await async_client.support_channels.with_raw_response.list( - company_id="biz_xxxxxxxxxxxxxx", - ) + response = await async_client.support_channels.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -292,9 +284,7 @@ async def test_raw_response_list(self, async_client: AsyncWhop) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: - async with async_client.support_channels.with_streaming_response.list( - company_id="biz_xxxxxxxxxxxxxx", - ) as response: + async with async_client.support_channels.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" From 1e5a3f2d0314c3905ce099b004f4855794e05082 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2026 00:17:24 +0000 Subject: [PATCH 09/35] feat(api): api update --- .stats.yml | 4 ++-- .../types/checkout_configuration_create_params.py | 8 ++++---- src/whop_sdk/types/invoice_create_params.py | 4 ++-- src/whop_sdk/types/invoice_update_params.py | 2 +- src/whop_sdk/types/plan_create_params.py | 2 +- src/whop_sdk/types/plan_update_params.py | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.stats.yml b/.stats.yml index 93a53a4..e72acd6 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 195 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-8733d0ecd8e41b7189b8c0c694a01db9533be105906e2c924aeba90ba10b2545.yml -openapi_spec_hash: e36c7496d3cce5cabd6ae59fb097b2b3 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-56cac25954ca9456abe514b2802d4388d3779c95f0fc01c9add7a6098acee041.yml +openapi_spec_hash: 5a60b28aa11eba6facf3b2e73efcaf49 config_hash: 6ad5a913fda410def47bf2ed841e2064 diff --git a/src/whop_sdk/types/checkout_configuration_create_params.py b/src/whop_sdk/types/checkout_configuration_create_params.py index bb990dc..2ab5782 100644 --- a/src/whop_sdk/types/checkout_configuration_create_params.py +++ b/src/whop_sdk/types/checkout_configuration_create_params.py @@ -124,7 +124,7 @@ class CreateCheckoutSessionInputModePaymentWithPlanPlanPaymentMethodConfiguratio defaults with additional methods. """ - include_platform_defaults: Required[bool] + include_platform_defaults: Optional[bool] """ Whether Whop's platform default payment method enablement settings are included in this configuration. The full list of default payment methods can be found in @@ -328,7 +328,7 @@ class CreateCheckoutSessionInputModePaymentWithPlanPaymentMethodConfiguration(Ty defaults with additional methods. """ - include_platform_defaults: Required[bool] + include_platform_defaults: Optional[bool] """ Whether Whop's platform default payment method enablement settings are included in this configuration. The full list of default payment methods can be found in @@ -416,7 +416,7 @@ class CreateCheckoutSessionInputModePaymentWithPlanIDPaymentMethodConfiguration( defaults with additional methods. """ - include_platform_defaults: Required[bool] + include_platform_defaults: Optional[bool] """ Whether Whop's platform default payment method enablement settings are included in this configuration. The full list of default payment methods can be found in @@ -501,7 +501,7 @@ class CreateCheckoutSessionInputModeSetupPaymentMethodConfiguration(TypedDict, t defaults with additional methods. """ - include_platform_defaults: Required[bool] + include_platform_defaults: Optional[bool] """ Whether Whop's platform default payment method enablement settings are included in this configuration. The full list of default payment methods can be found in diff --git a/src/whop_sdk/types/invoice_create_params.py b/src/whop_sdk/types/invoice_create_params.py index ac722bb..8ff9b33 100644 --- a/src/whop_sdk/types/invoice_create_params.py +++ b/src/whop_sdk/types/invoice_create_params.py @@ -176,7 +176,7 @@ class CreateInvoiceInputWithProductPlanPaymentMethodConfiguration(TypedDict, tot defaults with additional methods. """ - include_platform_defaults: Required[bool] + include_platform_defaults: Optional[bool] """ Whether Whop's platform default payment method enablement settings are included in this configuration. The full list of default payment methods can be found in @@ -462,7 +462,7 @@ class CreateInvoiceInputWithProductIDPlanPaymentMethodConfiguration(TypedDict, t defaults with additional methods. """ - include_platform_defaults: Required[bool] + include_platform_defaults: Optional[bool] """ Whether Whop's platform default payment method enablement settings are included in this configuration. The full list of default payment methods can be found in diff --git a/src/whop_sdk/types/invoice_update_params.py b/src/whop_sdk/types/invoice_update_params.py index 81482ef..69f0c8f 100644 --- a/src/whop_sdk/types/invoice_update_params.py +++ b/src/whop_sdk/types/invoice_update_params.py @@ -161,7 +161,7 @@ class PlanPaymentMethodConfiguration(TypedDict, total=False): defaults with additional methods. """ - include_platform_defaults: Required[bool] + include_platform_defaults: Optional[bool] """ Whether Whop's platform default payment method enablement settings are included in this configuration. The full list of default payment methods can be found in diff --git a/src/whop_sdk/types/plan_create_params.py b/src/whop_sdk/types/plan_create_params.py index d22e29e..c677a4e 100644 --- a/src/whop_sdk/types/plan_create_params.py +++ b/src/whop_sdk/types/plan_create_params.py @@ -180,7 +180,7 @@ class PaymentMethodConfiguration(TypedDict, total=False): defaults with additional methods. """ - include_platform_defaults: Required[bool] + include_platform_defaults: Optional[bool] """ Whether Whop's platform default payment method enablement settings are included in this configuration. The full list of default payment methods can be found in diff --git a/src/whop_sdk/types/plan_update_params.py b/src/whop_sdk/types/plan_update_params.py index 240f022..9d42508 100644 --- a/src/whop_sdk/types/plan_update_params.py +++ b/src/whop_sdk/types/plan_update_params.py @@ -173,7 +173,7 @@ class PaymentMethodConfiguration(TypedDict, total=False): defaults with additional methods. """ - include_platform_defaults: Required[bool] + include_platform_defaults: Optional[bool] """ Whether Whop's platform default payment method enablement settings are included in this configuration. The full list of default payment methods can be found in From f30e380baa7acbf33ccdf33e5d14d477018e3428 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 15:00:02 +0000 Subject: [PATCH 10/35] fix: use correct field name format for multipart file arrays --- src/whop_sdk/_qs.py | 8 ++----- src/whop_sdk/_types.py | 3 +++ src/whop_sdk/_utils/_utils.py | 42 ++++++++++++++++++++++++++++------- tests/test_extract_files.py | 28 ++++++++++++++++++----- tests/test_files.py | 2 +- 5 files changed, 63 insertions(+), 20 deletions(-) diff --git a/src/whop_sdk/_qs.py b/src/whop_sdk/_qs.py index de8c99b..4127c19 100644 --- a/src/whop_sdk/_qs.py +++ b/src/whop_sdk/_qs.py @@ -2,17 +2,13 @@ from typing import Any, List, Tuple, Union, Mapping, TypeVar from urllib.parse import parse_qs, urlencode -from typing_extensions import Literal, get_args +from typing_extensions import get_args -from ._types import NotGiven, not_given +from ._types import NotGiven, ArrayFormat, NestedFormat, not_given from ._utils import flatten _T = TypeVar("_T") - -ArrayFormat = Literal["comma", "repeat", "indices", "brackets"] -NestedFormat = Literal["dots", "brackets"] - PrimitiveData = Union[str, int, float, bool, None] # this should be Data = Union[PrimitiveData, "List[Data]", "Tuple[Data]", "Mapping[str, Data]"] # https://github.com/microsoft/pyright/issues/3555 diff --git a/src/whop_sdk/_types.py b/src/whop_sdk/_types.py index 66da6e5..05b7ccf 100644 --- a/src/whop_sdk/_types.py +++ b/src/whop_sdk/_types.py @@ -47,6 +47,9 @@ ModelT = TypeVar("ModelT", bound=pydantic.BaseModel) _T = TypeVar("_T") +ArrayFormat = Literal["comma", "repeat", "indices", "brackets"] +NestedFormat = Literal["dots", "brackets"] + # Approximates httpx internal ProxiesTypes and RequestFiles types # while adding support for `PathLike` instances diff --git a/src/whop_sdk/_utils/_utils.py b/src/whop_sdk/_utils/_utils.py index 771859f..199cd23 100644 --- a/src/whop_sdk/_utils/_utils.py +++ b/src/whop_sdk/_utils/_utils.py @@ -17,11 +17,11 @@ ) from pathlib import Path from datetime import date, datetime -from typing_extensions import TypeGuard +from typing_extensions import TypeGuard, get_args import sniffio -from .._types import Omit, NotGiven, FileTypes, HeadersLike +from .._types import Omit, NotGiven, FileTypes, ArrayFormat, HeadersLike _T = TypeVar("_T") _TupleT = TypeVar("_TupleT", bound=Tuple[object, ...]) @@ -40,25 +40,45 @@ def extract_files( query: Mapping[str, object], *, paths: Sequence[Sequence[str]], + array_format: ArrayFormat = "brackets", ) -> list[tuple[str, FileTypes]]: """Recursively extract files from the given dictionary based on specified paths. A path may look like this ['foo', 'files', '', 'data']. + ``array_format`` controls how ```` segments contribute to the emitted + field name. Supported values: ``"brackets"`` (``foo[]``), ``"repeat"`` and + ``"comma"`` (``foo``), ``"indices"`` (``foo[0]``, ``foo[1]``). + Note: this mutates the given dictionary. """ files: list[tuple[str, FileTypes]] = [] for path in paths: - files.extend(_extract_items(query, path, index=0, flattened_key=None)) + files.extend(_extract_items(query, path, index=0, flattened_key=None, array_format=array_format)) return files +def _array_suffix(array_format: ArrayFormat, array_index: int) -> str: + if array_format == "brackets": + return "[]" + if array_format == "indices": + return f"[{array_index}]" + if array_format == "repeat" or array_format == "comma": + # Both repeat the bare field name for each file part; there is no + # meaningful way to comma-join binary parts. + return "" + raise NotImplementedError( + f"Unknown array_format value: {array_format}, choose from {', '.join(get_args(ArrayFormat))}" + ) + + def _extract_items( obj: object, path: Sequence[str], *, index: int, flattened_key: str | None, + array_format: ArrayFormat, ) -> list[tuple[str, FileTypes]]: try: key = path[index] @@ -75,9 +95,11 @@ def _extract_items( if is_list(obj): files: list[tuple[str, FileTypes]] = [] - for entry in obj: - assert_is_file_content(entry, key=flattened_key + "[]" if flattened_key else "") - files.append((flattened_key + "[]", cast(FileTypes, entry))) + for array_index, entry in enumerate(obj): + suffix = _array_suffix(array_format, array_index) + emitted_key = (flattened_key + suffix) if flattened_key else suffix + assert_is_file_content(entry, key=emitted_key) + files.append((emitted_key, cast(FileTypes, entry))) return files assert_is_file_content(obj, key=flattened_key) @@ -106,6 +128,7 @@ def _extract_items( path, index=index, flattened_key=flattened_key, + array_format=array_format, ) elif is_list(obj): if key != "": @@ -117,9 +140,12 @@ def _extract_items( item, path, index=index, - flattened_key=flattened_key + "[]" if flattened_key is not None else "[]", + flattened_key=( + (flattened_key if flattened_key is not None else "") + _array_suffix(array_format, array_index) + ), + array_format=array_format, ) - for item in obj + for array_index, item in enumerate(obj) ] ) diff --git a/tests/test_extract_files.py b/tests/test_extract_files.py index f8ca177..fd47243 100644 --- a/tests/test_extract_files.py +++ b/tests/test_extract_files.py @@ -4,7 +4,7 @@ import pytest -from whop_sdk._types import FileTypes +from whop_sdk._types import FileTypes, ArrayFormat from whop_sdk._utils import extract_files @@ -37,10 +37,7 @@ def test_multiple_files() -> None: def test_top_level_file_array() -> None: query = {"files": [b"file one", b"file two"], "title": "hello"} - assert extract_files(query, paths=[["files", ""]]) == [ - ("files[]", b"file one"), - ("files[]", b"file two"), - ] + assert extract_files(query, paths=[["files", ""]]) == [("files[]", b"file one"), ("files[]", b"file two")] assert query == {"title": "hello"} @@ -71,3 +68,24 @@ def test_ignores_incorrect_paths( expected: list[tuple[str, FileTypes]], ) -> None: assert extract_files(query, paths=paths) == expected + + +@pytest.mark.parametrize( + "array_format,expected_top_level,expected_nested", + [ + ("brackets", [("files[]", b"a"), ("files[]", b"b")], [("items[][file]", b"a"), ("items[][file]", b"b")]), + ("repeat", [("files", b"a"), ("files", b"b")], [("items[file]", b"a"), ("items[file]", b"b")]), + ("comma", [("files", b"a"), ("files", b"b")], [("items[file]", b"a"), ("items[file]", b"b")]), + ("indices", [("files[0]", b"a"), ("files[1]", b"b")], [("items[0][file]", b"a"), ("items[1][file]", b"b")]), + ], +) +def test_array_format_controls_file_field_names( + array_format: ArrayFormat, + expected_top_level: list[tuple[str, FileTypes]], + expected_nested: list[tuple[str, FileTypes]], +) -> None: + top_level = {"files": [b"a", b"b"]} + assert extract_files(top_level, paths=[["files", ""]], array_format=array_format) == expected_top_level + + nested = {"items": [{"file": b"a"}, {"file": b"b"}]} + assert extract_files(nested, paths=[["items", "", "file"]], array_format=array_format) == expected_nested diff --git a/tests/test_files.py b/tests/test_files.py index ff5cf46..2abb756 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -131,7 +131,7 @@ def test_extract_files_does_not_mutate_original_nested_array_path(self) -> None: copied = deepcopy_with_paths(original, [["items", "", "file"]]) extracted = extract_files(copied, paths=[["items", "", "file"]]) - assert extracted == [("items[][file]", file1), ("items[][file]", file2)] + assert [entry for _, entry in extracted] == [file1, file2] assert original == { "items": [ {"file": file1, "extra": 1}, From b5be3d54f1884ecdaacaa2ec0de13d961e5e3ab6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 21:16:41 +0000 Subject: [PATCH 11/35] feat(api): api update --- .stats.yml | 2 +- src/whop_sdk/resources/support_channels.py | 10 ++++++---- src/whop_sdk/types/support_channel_list_params.py | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.stats.yml b/.stats.yml index e72acd6..e36e67e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 195 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-56cac25954ca9456abe514b2802d4388d3779c95f0fc01c9add7a6098acee041.yml -openapi_spec_hash: 5a60b28aa11eba6facf3b2e73efcaf49 +openapi_spec_hash: cdf5a15f0ff6243929f6adbed766db59 config_hash: 6ad5a913fda410def47bf2ed841e2064 diff --git a/src/whop_sdk/resources/support_channels.py b/src/whop_sdk/resources/support_channels.py index f1fb1e4..db5bc8c 100644 --- a/src/whop_sdk/resources/support_channels.py +++ b/src/whop_sdk/resources/support_channels.py @@ -170,8 +170,9 @@ def list( before: Returns the elements in the list that come before the specified cursor. - company_id: The unique identifier of the company to list support channels for. When omitted, - returns support channels across all companies the user has access to. + company_id: The unique identifier of the company to list support channels for. Includes + channels of child companies. When omitted, returns support channels across all + companies the user has access to. direction: The direction of the sort. @@ -364,8 +365,9 @@ def list( before: Returns the elements in the list that come before the specified cursor. - company_id: The unique identifier of the company to list support channels for. When omitted, - returns support channels across all companies the user has access to. + company_id: The unique identifier of the company to list support channels for. Includes + channels of child companies. When omitted, returns support channels across all + companies the user has access to. direction: The direction of the sort. diff --git a/src/whop_sdk/types/support_channel_list_params.py b/src/whop_sdk/types/support_channel_list_params.py index b930064..fb189f0 100644 --- a/src/whop_sdk/types/support_channel_list_params.py +++ b/src/whop_sdk/types/support_channel_list_params.py @@ -20,8 +20,8 @@ class SupportChannelListParams(TypedDict, total=False): company_id: Optional[str] """The unique identifier of the company to list support channels for. - When omitted, returns support channels across all companies the user has access - to. + Includes channels of child companies. When omitted, returns support channels + across all companies the user has access to. """ direction: Optional[Direction] From 1a21386e92b840ddaf00dbc077770f6e66c5c28e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 22:16:41 +0000 Subject: [PATCH 12/35] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index e36e67e..966d83f 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 195 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-56cac25954ca9456abe514b2802d4388d3779c95f0fc01c9add7a6098acee041.yml -openapi_spec_hash: cdf5a15f0ff6243929f6adbed766db59 +openapi_spec_hash: c0a2991ab1c57d9d4c5076ea275011f1 config_hash: 6ad5a913fda410def47bf2ed841e2064 From c943353561b164a19d00b96933fe17ecee3a1601 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 22:28:02 +0000 Subject: [PATCH 13/35] feat: support setting headers via env --- src/whop_sdk/_client.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/whop_sdk/_client.py b/src/whop_sdk/_client.py index 98fa7ac..32e8de8 100644 --- a/src/whop_sdk/_client.py +++ b/src/whop_sdk/_client.py @@ -19,7 +19,11 @@ RequestOptions, not_given, ) -from ._utils import is_given, get_async_library +from ._utils import ( + is_given, + is_mapping_t, + get_async_library, +) from ._compat import cached_property from ._version import __version__ from ._streaming import Stream as Stream, AsyncStream as AsyncStream @@ -208,6 +212,15 @@ def __init__( if base_url is None: base_url = f"https://api.whop.com/api/v1" + custom_headers_env = os.environ.get("WHOP_CUSTOM_HEADERS") + if custom_headers_env is not None: + parsed: dict[str, str] = {} + for line in custom_headers_env.split("\n"): + colon = line.find(":") + if colon >= 0: + parsed[line[:colon].strip()] = line[colon + 1 :].strip() + default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})} + super().__init__( version=__version__, base_url=base_url, @@ -717,6 +730,15 @@ def __init__( if base_url is None: base_url = f"https://api.whop.com/api/v1" + custom_headers_env = os.environ.get("WHOP_CUSTOM_HEADERS") + if custom_headers_env is not None: + parsed: dict[str, str] = {} + for line in custom_headers_env.split("\n"): + colon = line.find(":") + if colon >= 0: + parsed[line[:colon].strip()] = line[colon + 1 :].strip() + default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})} + super().__init__( version=__version__, base_url=base_url, From b3f3ebf63f5400817e222df485260eb1e6d972ff Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 16:16:21 +0000 Subject: [PATCH 14/35] feat(api): api update --- .stats.yml | 2 +- .../checkout_configuration_create_params.py | 18 ++++++++++++++++++ src/whop_sdk/types/plan_create_params.py | 6 ++++++ src/whop_sdk/types/plan_update_params.py | 6 ++++++ .../test_checkout_configurations.py | 6 ++++++ tests/api_resources/test_plans.py | 4 ++++ 6 files changed, 41 insertions(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 966d83f..bd71d14 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 195 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-56cac25954ca9456abe514b2802d4388d3779c95f0fc01c9add7a6098acee041.yml -openapi_spec_hash: c0a2991ab1c57d9d4c5076ea275011f1 +openapi_spec_hash: 069dc6df5e7b3db58dd6dccf7c8ddac8 config_hash: 6ad5a913fda410def47bf2ed841e2064 diff --git a/src/whop_sdk/types/checkout_configuration_create_params.py b/src/whop_sdk/types/checkout_configuration_create_params.py index 2ab5782..bdf5197 100644 --- a/src/whop_sdk/types/checkout_configuration_create_params.py +++ b/src/whop_sdk/types/checkout_configuration_create_params.py @@ -298,6 +298,12 @@ class CreateCheckoutSessionInputModePaymentWithPlanCheckoutStyling(TypedDict, to Overrides plan and company defaults. """ + background_color: Optional[str] + """ + A hex color code for the checkout page background, applied to the order summary + panel (e.g. #F4F4F5). + """ + border_style: Optional[CheckoutShape] """The different border-radius styles available for checkout pages.""" @@ -386,6 +392,12 @@ class CreateCheckoutSessionInputModePaymentWithPlanIDCheckoutStyling(TypedDict, Overrides plan and company defaults. """ + background_color: Optional[str] + """ + A hex color code for the checkout page background, applied to the order summary + panel (e.g. #F4F4F5). + """ + border_style: Optional[CheckoutShape] """The different border-radius styles available for checkout pages.""" @@ -471,6 +483,12 @@ class CreateCheckoutSessionInputModeSetupCheckoutStyling(TypedDict, total=False) Overrides plan and company defaults. """ + background_color: Optional[str] + """ + A hex color code for the checkout page background, applied to the order summary + panel (e.g. #F4F4F5). + """ + border_style: Optional[CheckoutShape] """The different border-radius styles available for checkout pages.""" diff --git a/src/whop_sdk/types/plan_create_params.py b/src/whop_sdk/types/plan_create_params.py index c677a4e..fbf052a 100644 --- a/src/whop_sdk/types/plan_create_params.py +++ b/src/whop_sdk/types/plan_create_params.py @@ -123,6 +123,12 @@ class CheckoutStyling(TypedDict, total=False): Pass null to inherit from the company default. """ + background_color: Optional[str] + """ + A hex color code for the checkout page background, applied to the order summary + panel (e.g. #F4F4F5). + """ + border_style: Optional[CheckoutShape] """The different border-radius styles available for checkout pages.""" diff --git a/src/whop_sdk/types/plan_update_params.py b/src/whop_sdk/types/plan_update_params.py index 9d42508..8283769 100644 --- a/src/whop_sdk/types/plan_update_params.py +++ b/src/whop_sdk/types/plan_update_params.py @@ -116,6 +116,12 @@ class CheckoutStyling(TypedDict, total=False): Pass null to remove all overrides and inherit from the company default. """ + background_color: Optional[str] + """ + A hex color code for the checkout page background, applied to the order summary + panel (e.g. #F4F4F5). + """ + border_style: Optional[CheckoutShape] """The different border-radius styles available for checkout pages.""" diff --git a/tests/api_resources/test_checkout_configurations.py b/tests/api_resources/test_checkout_configurations.py index 76c9754..8bf0ce9 100644 --- a/tests/api_resources/test_checkout_configurations.py +++ b/tests/api_resources/test_checkout_configurations.py @@ -91,6 +91,7 @@ def test_method_create_with_all_params_overload_1(self, client: Whop) -> None: affiliate_code="affiliate_code", allow_promo_codes=True, checkout_styling={ + "background_color": "background_color", "border_style": "rounded", "button_color": "button_color", "font_family": "system", @@ -156,6 +157,7 @@ def test_method_create_with_all_params_overload_2(self, client: Whop) -> None: affiliate_code="affiliate_code", allow_promo_codes=True, checkout_styling={ + "background_color": "background_color", "border_style": "rounded", "button_color": "button_color", "font_family": "system", @@ -216,6 +218,7 @@ def test_method_create_with_all_params_overload_3(self, client: Whop) -> None: mode="setup", allow_promo_codes=True, checkout_styling={ + "background_color": "background_color", "border_style": "rounded", "button_color": "button_color", "font_family": "system", @@ -435,6 +438,7 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn affiliate_code="affiliate_code", allow_promo_codes=True, checkout_styling={ + "background_color": "background_color", "border_style": "rounded", "button_color": "button_color", "font_family": "system", @@ -500,6 +504,7 @@ async def test_method_create_with_all_params_overload_2(self, async_client: Asyn affiliate_code="affiliate_code", allow_promo_codes=True, checkout_styling={ + "background_color": "background_color", "border_style": "rounded", "button_color": "button_color", "font_family": "system", @@ -560,6 +565,7 @@ async def test_method_create_with_all_params_overload_3(self, async_client: Asyn mode="setup", allow_promo_codes=True, checkout_styling={ + "background_color": "background_color", "border_style": "rounded", "button_color": "button_color", "font_family": "system", diff --git a/tests/api_resources/test_plans.py b/tests/api_resources/test_plans.py index 33d2cfc..878c551 100644 --- a/tests/api_resources/test_plans.py +++ b/tests/api_resources/test_plans.py @@ -40,6 +40,7 @@ def test_method_create_with_all_params(self, client: Whop) -> None: product_id="prod_xxxxxxxxxxxxx", billing_period=42, checkout_styling={ + "background_color": "background_color", "border_style": "rounded", "button_color": "button_color", "font_family": "system", @@ -164,6 +165,7 @@ def test_method_update_with_all_params(self, client: Whop) -> None: id="plan_xxxxxxxxxxxxx", billing_period=42, checkout_styling={ + "background_color": "background_color", "border_style": "rounded", "button_color": "button_color", "font_family": "system", @@ -356,6 +358,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N product_id="prod_xxxxxxxxxxxxx", billing_period=42, checkout_styling={ + "background_color": "background_color", "border_style": "rounded", "button_color": "button_color", "font_family": "system", @@ -480,6 +483,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N id="plan_xxxxxxxxxxxxx", billing_period=42, checkout_styling={ + "background_color": "background_color", "border_style": "rounded", "button_color": "button_color", "font_family": "system", From 4d4d2d719d5447a6a690afee49a8d6301358ff91 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 18:58:15 +0000 Subject: [PATCH 15/35] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index bd71d14..9ac399e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 195 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-56cac25954ca9456abe514b2802d4388d3779c95f0fc01c9add7a6098acee041.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-c79186fbd39abcd9448be0935c23979ef11a2bf1b9dee6128407be1334a95f1e.yml openapi_spec_hash: 069dc6df5e7b3db58dd6dccf7c8ddac8 config_hash: 6ad5a913fda410def47bf2ed841e2064 From f82efbe0a134d4c1de2f10cc4f3b6bf5651fd82b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 23:16:16 +0000 Subject: [PATCH 16/35] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 9ac399e..d3190b9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 195 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-c79186fbd39abcd9448be0935c23979ef11a2bf1b9dee6128407be1334a95f1e.yml -openapi_spec_hash: 069dc6df5e7b3db58dd6dccf7c8ddac8 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-28aa294b07be9c50af251d4737470c589df48fd3d310174767f9613523ef6abf.yml +openapi_spec_hash: f1ab792af6cc2ad1eed410e58bb17acc config_hash: 6ad5a913fda410def47bf2ed841e2064 From 3189146ef827f1e6db20cbcb38e08ff0632fc031 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2026 04:16:09 +0000 Subject: [PATCH 17/35] feat(api): api update --- .stats.yml | 4 ++-- src/whop_sdk/types/shared/authorized_user_roles.py | 2 +- src/whop_sdk/types/shared_params/authorized_user_roles.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.stats.yml b/.stats.yml index d3190b9..4c697c7 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 195 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-28aa294b07be9c50af251d4737470c589df48fd3d310174767f9613523ef6abf.yml -openapi_spec_hash: f1ab792af6cc2ad1eed410e58bb17acc +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-c5d5590d6eb35110c493656f06f36b65eb15ecaf246d750d0f01b56d4bbd1aee.yml +openapi_spec_hash: 04fb662633822d68cba19a2b48a81576 config_hash: 6ad5a913fda410def47bf2ed841e2064 diff --git a/src/whop_sdk/types/shared/authorized_user_roles.py b/src/whop_sdk/types/shared/authorized_user_roles.py index 877db6d..3003b97 100644 --- a/src/whop_sdk/types/shared/authorized_user_roles.py +++ b/src/whop_sdk/types/shared/authorized_user_roles.py @@ -5,5 +5,5 @@ __all__ = ["AuthorizedUserRoles"] AuthorizedUserRoles: TypeAlias = Literal[ - "owner", "admin", "sales_manager", "moderator", "app_manager", "support", "manager" + "owner", "admin", "sales_manager", "moderator", "advertiser", "app_manager", "support", "manager" ] diff --git a/src/whop_sdk/types/shared_params/authorized_user_roles.py b/src/whop_sdk/types/shared_params/authorized_user_roles.py index 2235fde..9b42c5a 100644 --- a/src/whop_sdk/types/shared_params/authorized_user_roles.py +++ b/src/whop_sdk/types/shared_params/authorized_user_roles.py @@ -7,5 +7,5 @@ __all__ = ["AuthorizedUserRoles"] AuthorizedUserRoles: TypeAlias = Literal[ - "owner", "admin", "sales_manager", "moderator", "app_manager", "support", "manager" + "owner", "admin", "sales_manager", "moderator", "advertiser", "app_manager", "support", "manager" ] From d75fa930669f6e213088575a6c62e0e324105683 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2026 18:16:24 +0000 Subject: [PATCH 18/35] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 4c697c7..6c93de5 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 195 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc%2Fwhopsdk-c5d5590d6eb35110c493656f06f36b65eb15ecaf246d750d0f01b56d4bbd1aee.yml -openapi_spec_hash: 04fb662633822d68cba19a2b48a81576 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-2b39a50338b66e7f596dff4108d6c4b0b02b0e38e339935c6d69161ef031af54.yml +openapi_spec_hash: 99c3c3dbb9db2b4f233c4ea93bd5cb22 config_hash: 6ad5a913fda410def47bf2ed841e2064 From 44f542f1c1083fda63e974e60177a4bbc992b29e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 02:40:10 +0000 Subject: [PATCH 19/35] feat(api): api update --- .stats.yml | 4 ++-- src/whop_sdk/resources/forum_posts.py | 8 ++++++++ src/whop_sdk/types/forum_post_list_params.py | 3 +++ tests/api_resources/test_forum_posts.py | 2 ++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 6c93de5..b67ef02 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 195 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-2b39a50338b66e7f596dff4108d6c4b0b02b0e38e339935c6d69161ef031af54.yml -openapi_spec_hash: 99c3c3dbb9db2b4f233c4ea93bd5cb22 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-e36f5a1696c1c615120cbbaacd6a36818d9643eb4331662ba288b669630908bf.yml +openapi_spec_hash: 4e6da2a7eac1e272b67063a9e85945a9 config_hash: 6ad5a913fda410def47bf2ed841e2064 diff --git a/src/whop_sdk/resources/forum_posts.py b/src/whop_sdk/resources/forum_posts.py index eacff2d..786233e 100644 --- a/src/whop_sdk/resources/forum_posts.py +++ b/src/whop_sdk/resources/forum_posts.py @@ -258,6 +258,7 @@ def list( after: Optional[str] | Omit = omit, before: Optional[str] | Omit = omit, first: Optional[int] | Omit = omit, + include_bounty_anchors: Optional[bool] | Omit = omit, last: Optional[int] | Omit = omit, parent_id: Optional[str] | Omit = omit, pinned: Optional[bool] | Omit = omit, @@ -285,6 +286,8 @@ def list( first: Returns the first _n_ elements from the list. + include_bounty_anchors: Whether to include top-level bounty discussion anchors as rich forum items. + last: Returns the last _n_ elements from the list. parent_id: The unique identifier of a parent post to list comments for. When set, returns @@ -315,6 +318,7 @@ def list( "after": after, "before": before, "first": first, + "include_bounty_anchors": include_bounty_anchors, "last": last, "parent_id": parent_id, "pinned": pinned, @@ -552,6 +556,7 @@ def list( after: Optional[str] | Omit = omit, before: Optional[str] | Omit = omit, first: Optional[int] | Omit = omit, + include_bounty_anchors: Optional[bool] | Omit = omit, last: Optional[int] | Omit = omit, parent_id: Optional[str] | Omit = omit, pinned: Optional[bool] | Omit = omit, @@ -579,6 +584,8 @@ def list( first: Returns the first _n_ elements from the list. + include_bounty_anchors: Whether to include top-level bounty discussion anchors as rich forum items. + last: Returns the last _n_ elements from the list. parent_id: The unique identifier of a parent post to list comments for. When set, returns @@ -609,6 +616,7 @@ def list( "after": after, "before": before, "first": first, + "include_bounty_anchors": include_bounty_anchors, "last": last, "parent_id": parent_id, "pinned": pinned, diff --git a/src/whop_sdk/types/forum_post_list_params.py b/src/whop_sdk/types/forum_post_list_params.py index cb4725f..84dcc7e 100644 --- a/src/whop_sdk/types/forum_post_list_params.py +++ b/src/whop_sdk/types/forum_post_list_params.py @@ -21,6 +21,9 @@ class ForumPostListParams(TypedDict, total=False): first: Optional[int] """Returns the first _n_ elements from the list.""" + include_bounty_anchors: Optional[bool] + """Whether to include top-level bounty discussion anchors as rich forum items.""" + last: Optional[int] """Returns the last _n_ elements from the list.""" diff --git a/tests/api_resources/test_forum_posts.py b/tests/api_resources/test_forum_posts.py index ead26f5..9228148 100644 --- a/tests/api_resources/test_forum_posts.py +++ b/tests/api_resources/test_forum_posts.py @@ -194,6 +194,7 @@ def test_method_list_with_all_params(self, client: Whop) -> None: after="after", before="before", first=42, + include_bounty_anchors=True, last=42, parent_id="parent_id", pinned=True, @@ -405,6 +406,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> Non after="after", before="before", first=42, + include_bounty_anchors=True, last=42, parent_id="parent_id", pinned=True, From db5b20b83f1e2c9418669de41b96101323bee8ad Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 05:16:08 +0000 Subject: [PATCH 20/35] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index b67ef02..12d65df 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 195 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-e36f5a1696c1c615120cbbaacd6a36818d9643eb4331662ba288b669630908bf.yml -openapi_spec_hash: 4e6da2a7eac1e272b67063a9e85945a9 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-5a8f60d1ef50189805cba201c138255f6e19ac6f4f4425e4192b9cb3eb74185d.yml +openapi_spec_hash: 4d027e76962eb6322a89a30662104341 config_hash: 6ad5a913fda410def47bf2ed841e2064 From b671eaa7a5fd7c67ee29af64eaad905b690d9f0f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 16:16:37 +0000 Subject: [PATCH 21/35] feat(api): api update --- .stats.yml | 4 +-- src/whop_sdk/resources/files.py | 29 ++++++++++++++++++-- src/whop_sdk/types/file_create_params.py | 9 +++++- src/whop_sdk/types/file_create_response.py | 9 ++++-- src/whop_sdk/types/file_retrieve_response.py | 9 ++++-- tests/api_resources/test_files.py | 18 ++++++++++++ 6 files changed, 68 insertions(+), 10 deletions(-) diff --git a/.stats.yml b/.stats.yml index 12d65df..f0184da 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 195 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-5a8f60d1ef50189805cba201c138255f6e19ac6f4f4425e4192b9cb3eb74185d.yml -openapi_spec_hash: 4d027e76962eb6322a89a30662104341 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-ca236617ca4beff372ce391cfa572e69ec305e73e957bf9e8a720ddf8cee4d59.yml +openapi_spec_hash: 66ead5bf5c8be9b52ee59c73d6f6df5e config_hash: 6ad5a913fda410def47bf2ed841e2064 diff --git a/src/whop_sdk/resources/files.py b/src/whop_sdk/resources/files.py index 5e1ad1b..a27e5e6 100644 --- a/src/whop_sdk/resources/files.py +++ b/src/whop_sdk/resources/files.py @@ -2,10 +2,13 @@ from __future__ import annotations +from typing import Optional +from typing_extensions import Literal + import httpx from ..types import file_create_params -from .._types import Body, Query, Headers, NotGiven, not_given +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from .._utils import path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource @@ -46,6 +49,7 @@ def create( self, *, filename: str, + visibility: Optional[Literal["public", "private"]] | 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, @@ -61,6 +65,9 @@ def create( filename: The name of the file including its extension (e.g., "photo.png" or "document.pdf"). + visibility: Controls whether an uploaded file is publicly accessible or requires + authentication to access. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -71,7 +78,13 @@ def create( """ return self._post( "/files", - body=maybe_transform({"filename": filename}, file_create_params.FileCreateParams), + body=maybe_transform( + { + "filename": filename, + "visibility": visibility, + }, + file_create_params.FileCreateParams, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -136,6 +149,7 @@ async def create( self, *, filename: str, + visibility: Optional[Literal["public", "private"]] | 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, @@ -151,6 +165,9 @@ async def create( filename: The name of the file including its extension (e.g., "photo.png" or "document.pdf"). + visibility: Controls whether an uploaded file is publicly accessible or requires + authentication to access. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -161,7 +178,13 @@ async def create( """ return await self._post( "/files", - body=await async_maybe_transform({"filename": filename}, file_create_params.FileCreateParams), + body=await async_maybe_transform( + { + "filename": filename, + "visibility": visibility, + }, + file_create_params.FileCreateParams, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), diff --git a/src/whop_sdk/types/file_create_params.py b/src/whop_sdk/types/file_create_params.py index c598f20..6df1411 100644 --- a/src/whop_sdk/types/file_create_params.py +++ b/src/whop_sdk/types/file_create_params.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing_extensions import Required, TypedDict +from typing import Optional +from typing_extensions import Literal, Required, TypedDict __all__ = ["FileCreateParams"] @@ -13,3 +14,9 @@ class FileCreateParams(TypedDict, total=False): The name of the file including its extension (e.g., "photo.png" or "document.pdf"). """ + + visibility: Optional[Literal["public", "private"]] + """ + Controls whether an uploaded file is publicly accessible or requires + authentication to access. + """ diff --git a/src/whop_sdk/types/file_create_response.py b/src/whop_sdk/types/file_create_response.py index 51ac1b4..5c9e2fa 100644 --- a/src/whop_sdk/types/file_create_response.py +++ b/src/whop_sdk/types/file_create_response.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Dict, Optional +from typing_extensions import Literal from .._models import BaseModel from .upload_status import UploadStatus @@ -39,7 +40,11 @@ class FileCreateResponse(BaseModel): """ url: Optional[str] = None - """The CDN URL for accessing the file. + """The URL for accessing the file. - Null if the file has not finished uploading. + For public files, this is a permanent CDN URL. For private files, this is a + signed URL that expires. Null if the file has not finished uploading. """ + + visibility: Literal["public", "private"] + """Whether the file is publicly accessible or requires authentication.""" diff --git a/src/whop_sdk/types/file_retrieve_response.py b/src/whop_sdk/types/file_retrieve_response.py index f5c5c6b..c60e895 100644 --- a/src/whop_sdk/types/file_retrieve_response.py +++ b/src/whop_sdk/types/file_retrieve_response.py @@ -1,6 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional +from typing_extensions import Literal from .._models import BaseModel from .upload_status import UploadStatus @@ -27,7 +28,11 @@ class FileRetrieveResponse(BaseModel): """The current upload status of the file (e.g., pending, ready).""" url: Optional[str] = None - """The CDN URL for accessing the file. + """The URL for accessing the file. - Null if the file has not finished uploading. + For public files, this is a permanent CDN URL. For private files, this is a + signed URL that expires. Null if the file has not finished uploading. """ + + visibility: Literal["public", "private"] + """Whether the file is publicly accessible or requires authentication.""" diff --git a/tests/api_resources/test_files.py b/tests/api_resources/test_files.py index bde3b74..5452a2b 100644 --- a/tests/api_resources/test_files.py +++ b/tests/api_resources/test_files.py @@ -25,6 +25,15 @@ def test_method_create(self, client: Whop) -> None: ) assert_matches_type(FileCreateResponse, file, path=["response"]) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Whop) -> None: + file = client.files.create( + filename="filename", + visibility="public", + ) + assert_matches_type(FileCreateResponse, file, path=["response"]) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize def test_raw_response_create(self, client: Whop) -> None: @@ -107,6 +116,15 @@ async def test_method_create(self, async_client: AsyncWhop) -> None: ) assert_matches_type(FileCreateResponse, file, path=["response"]) + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None: + file = await async_client.files.create( + filename="filename", + visibility="public", + ) + assert_matches_type(FileCreateResponse, file, path=["response"]) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize async def test_raw_response_create(self, async_client: AsyncWhop) -> None: From 0e9d737bb279ee924fc1fd40a5fd37c134302145 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 16:20:55 +0000 Subject: [PATCH 22/35] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index f0184da..15d7c10 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 195 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-ca236617ca4beff372ce391cfa572e69ec305e73e957bf9e8a720ddf8cee4d59.yml +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-76f227a5f6bddda47aea2618cce6106619312a16cf4a9918ba2a8a7cfbaf2489.yml openapi_spec_hash: 66ead5bf5c8be9b52ee59c73d6f6df5e config_hash: 6ad5a913fda410def47bf2ed841e2064 From eb865b00e455c264ee6d9a3ccc99b68372e868da Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 20:57:31 +0000 Subject: [PATCH 23/35] chore(internal): reformat pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fe705c7..82c0844 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -170,7 +170,7 @@ show_error_codes = true # # We also exclude our `tests` as mypy doesn't always infer # types correctly and Pyright will still catch any type errors. -exclude = ['src/whop_sdk/_files.py', '_dev/.*.py', 'tests/.*'] +exclude = ["src/whop_sdk/_files.py", "_dev/.*.py", "tests/.*"] strict_equality = true implicit_reexport = true From 1c011549b9143a2b1434657cc1695b614a2c8409 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 22:16:23 +0000 Subject: [PATCH 24/35] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 15d7c10..e55f65c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 195 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-76f227a5f6bddda47aea2618cce6106619312a16cf4a9918ba2a8a7cfbaf2489.yml -openapi_spec_hash: 66ead5bf5c8be9b52ee59c73d6f6df5e +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-4d92350c9436fc8d84406c6e31fd9f4e366257210da179e6f0a8a872d9b6b54a.yml +openapi_spec_hash: 4c133be8723c3536f4c9bfcfcf2c4787 config_hash: 6ad5a913fda410def47bf2ed841e2064 From d4cc5595e3fc979a901a4e666c7ee29f69690991 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 00:16:21 +0000 Subject: [PATCH 25/35] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index e55f65c..bc94dab 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 195 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-4d92350c9436fc8d84406c6e31fd9f4e366257210da179e6f0a8a872d9b6b54a.yml -openapi_spec_hash: 4c133be8723c3536f4c9bfcfcf2c4787 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-b865bb442bb72b4a74b17b70375db9c304d016b8c82374f6d41abffaf57df77b.yml +openapi_spec_hash: a0164a5b0ab72a341903055896a1720b config_hash: 6ad5a913fda410def47bf2ed841e2064 From da275a3907434c24f3a3433d38e4c883bfa1207d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 19:16:54 +0000 Subject: [PATCH 26/35] feat(api): api update --- .stats.yml | 4 ++-- src/whop_sdk/types/shared/currency.py | 1 + src/whop_sdk/types/shared_params/currency.py | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index bc94dab..43c449e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 195 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-b865bb442bb72b4a74b17b70375db9c304d016b8c82374f6d41abffaf57df77b.yml -openapi_spec_hash: a0164a5b0ab72a341903055896a1720b +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-63a87ba17e96583aa2d0667aafd621661584c30f0df731865c2c9b259b4e0f8b.yml +openapi_spec_hash: 3b538554d177c66a8743ee4ab3c9a096 config_hash: 6ad5a913fda410def47bf2ed841e2064 diff --git a/src/whop_sdk/types/shared/currency.py b/src/whop_sdk/types/shared/currency.py index dbed1ad..43442a3 100644 --- a/src/whop_sdk/types/shared/currency.py +++ b/src/whop_sdk/types/shared/currency.py @@ -91,4 +91,5 @@ "btc", "cny", "usdt", + "kzt", ] diff --git a/src/whop_sdk/types/shared_params/currency.py b/src/whop_sdk/types/shared_params/currency.py index 4efdb1e..3d0b58a 100644 --- a/src/whop_sdk/types/shared_params/currency.py +++ b/src/whop_sdk/types/shared_params/currency.py @@ -93,4 +93,5 @@ "btc", "cny", "usdt", + "kzt", ] From 676391668c2a8d78e447d000f01c38b960bd542a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 2 May 2026 00:16:54 +0000 Subject: [PATCH 27/35] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 43c449e..5e12d42 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 195 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-63a87ba17e96583aa2d0667aafd621661584c30f0df731865c2c9b259b4e0f8b.yml -openapi_spec_hash: 3b538554d177c66a8743ee4ab3c9a096 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-c906e5b0fedc891c08b0a12498a4056715bb43e64984274ac7d5b5de1da8a882.yml +openapi_spec_hash: f64c7be15955a81b89d7a4c46733b08e config_hash: 6ad5a913fda410def47bf2ed841e2064 From b61b0ee58364761c8c904d82e8d195b2d5f73efb Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 2 May 2026 05:16:54 +0000 Subject: [PATCH 28/35] feat(api): api update --- .stats.yml | 4 ++-- src/whop_sdk/resources/payments.py | 10 ++++++---- src/whop_sdk/resources/plans.py | 16 ++++++++++++++++ .../checkout_configuration_create_params.py | 3 +++ .../checkout_configuration_list_response.py | 6 ++++++ src/whop_sdk/types/payment_list_response.py | 7 +++++++ src/whop_sdk/types/payment_refund_params.py | 6 ++++-- src/whop_sdk/types/plan_create_params.py | 3 +++ src/whop_sdk/types/plan_list_response.py | 6 ++++++ src/whop_sdk/types/plan_update_params.py | 3 +++ .../types/shared/checkout_configuration.py | 6 ++++++ src/whop_sdk/types/shared/payment.py | 19 +++++++++++++++++++ src/whop_sdk/types/shared/plan.py | 6 ++++++ .../test_checkout_configurations.py | 2 ++ tests/api_resources/test_plans.py | 4 ++++ 15 files changed, 93 insertions(+), 8 deletions(-) diff --git a/.stats.yml b/.stats.yml index 5e12d42..f9a9223 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 195 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-c906e5b0fedc891c08b0a12498a4056715bb43e64984274ac7d5b5de1da8a882.yml -openapi_spec_hash: f64c7be15955a81b89d7a4c46733b08e +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-f2ca2af230fc105022e0ed59b10ab75dbcdd9f0726c492c49b5d2dca0c141daf.yml +openapi_spec_hash: 1db260b14e71594936e3ebe4438ad2cf config_hash: 6ad5a913fda410def47bf2ed841e2064 diff --git a/src/whop_sdk/resources/payments.py b/src/whop_sdk/resources/payments.py index db84e49..b343532 100644 --- a/src/whop_sdk/resources/payments.py +++ b/src/whop_sdk/resources/payments.py @@ -478,8 +478,9 @@ def refund( - `payment:resolution_center_case:read` Args: - partial_amount: The amount to refund in the payment currency. If omitted, the full payment - amount is refunded. + partial_amount: The amount to refund. For multi-currency payments, this is in the charge + currency (what the buyer paid). For single-currency, this is in the payment + currency. If omitted, the full payment amount is refunded. extra_headers: Send extra headers @@ -1040,8 +1041,9 @@ async def refund( - `payment:resolution_center_case:read` Args: - partial_amount: The amount to refund in the payment currency. If omitted, the full payment - amount is refunded. + partial_amount: The amount to refund. For multi-currency payments, this is in the charge + currency (what the buyer paid). For single-currency, this is in the payment + currency. If omitted, the full payment amount is refunded. extra_headers: Send extra headers diff --git a/src/whop_sdk/resources/plans.py b/src/whop_sdk/resources/plans.py index a0f6dac..8885f8b 100644 --- a/src/whop_sdk/resources/plans.py +++ b/src/whop_sdk/resources/plans.py @@ -60,6 +60,7 @@ def create( *, company_id: str, product_id: str, + adaptive_pricing_enabled: Optional[bool] | Omit = omit, billing_period: Optional[int] | Omit = omit, checkout_styling: Optional[plan_create_params.CheckoutStyling] | Omit = omit, currency: Optional[Currency] | Omit = omit, @@ -104,6 +105,8 @@ def create( product_id: The unique identifier of the product to attach this plan to. + adaptive_pricing_enabled: Whether this plan accepts local currency payments via adaptive pricing. + billing_period: The number of days between recurring charges. For example, 30 for monthly or 365 for yearly. @@ -170,6 +173,7 @@ def create( { "company_id": company_id, "product_id": product_id, + "adaptive_pricing_enabled": adaptive_pricing_enabled, "billing_period": billing_period, "checkout_styling": checkout_styling, "currency": currency, @@ -241,6 +245,7 @@ def update( self, id: str, *, + adaptive_pricing_enabled: Optional[bool] | Omit = omit, billing_period: Optional[int] | Omit = omit, checkout_styling: Optional[plan_update_params.CheckoutStyling] | Omit = omit, currency: Optional[Currency] | Omit = omit, @@ -280,6 +285,8 @@ def update( - `plan:basic:read` Args: + adaptive_pricing_enabled: Whether this plan accepts local currency payments via adaptive pricing. + billing_period: The number of days between recurring charges. For example, 30 for monthly or 365 for yearly. @@ -346,6 +353,7 @@ def update( path_template("/plans/{id}", id=id), body=maybe_transform( { + "adaptive_pricing_enabled": adaptive_pricing_enabled, "billing_period": billing_period, "checkout_styling": checkout_styling, "currency": currency, @@ -538,6 +546,7 @@ async def create( *, company_id: str, product_id: str, + adaptive_pricing_enabled: Optional[bool] | Omit = omit, billing_period: Optional[int] | Omit = omit, checkout_styling: Optional[plan_create_params.CheckoutStyling] | Omit = omit, currency: Optional[Currency] | Omit = omit, @@ -582,6 +591,8 @@ async def create( product_id: The unique identifier of the product to attach this plan to. + adaptive_pricing_enabled: Whether this plan accepts local currency payments via adaptive pricing. + billing_period: The number of days between recurring charges. For example, 30 for monthly or 365 for yearly. @@ -648,6 +659,7 @@ async def create( { "company_id": company_id, "product_id": product_id, + "adaptive_pricing_enabled": adaptive_pricing_enabled, "billing_period": billing_period, "checkout_styling": checkout_styling, "currency": currency, @@ -719,6 +731,7 @@ async def update( self, id: str, *, + adaptive_pricing_enabled: Optional[bool] | Omit = omit, billing_period: Optional[int] | Omit = omit, checkout_styling: Optional[plan_update_params.CheckoutStyling] | Omit = omit, currency: Optional[Currency] | Omit = omit, @@ -758,6 +771,8 @@ async def update( - `plan:basic:read` Args: + adaptive_pricing_enabled: Whether this plan accepts local currency payments via adaptive pricing. + billing_period: The number of days between recurring charges. For example, 30 for monthly or 365 for yearly. @@ -824,6 +839,7 @@ async def update( path_template("/plans/{id}", id=id), body=await async_maybe_transform( { + "adaptive_pricing_enabled": adaptive_pricing_enabled, "billing_period": billing_period, "checkout_styling": checkout_styling, "currency": currency, diff --git a/src/whop_sdk/types/checkout_configuration_create_params.py b/src/whop_sdk/types/checkout_configuration_create_params.py index bdf5197..ec80485 100644 --- a/src/whop_sdk/types/checkout_configuration_create_params.py +++ b/src/whop_sdk/types/checkout_configuration_create_params.py @@ -195,6 +195,9 @@ class CreateCheckoutSessionInputModePaymentWithPlanPlan(TypedDict, total=False): currency: Required[Currency] """The respective currency identifier for the plan.""" + adaptive_pricing_enabled: Optional[bool] + """Whether this plan accepts local currency payments via adaptive pricing.""" + application_fee_amount: Optional[float] """The application fee amount collected by the platform from this connected account. diff --git a/src/whop_sdk/types/checkout_configuration_list_response.py b/src/whop_sdk/types/checkout_configuration_list_response.py index 41b6742..bb54ed0 100644 --- a/src/whop_sdk/types/checkout_configuration_list_response.py +++ b/src/whop_sdk/types/checkout_configuration_list_response.py @@ -47,6 +47,12 @@ class Plan(BaseModel): id: str """The unique identifier for the plan.""" + adaptive_pricing_enabled: bool + """Whether the creator has turned on adaptive pricing for this plan. + + Raw setting — does not check processor compatibility or feature flags. + """ + billing_period: Optional[int] = None """The number of days between each recurring charge. diff --git a/src/whop_sdk/types/payment_list_response.py b/src/whop_sdk/types/payment_list_response.py index 90ab9cc..2af1a1d 100644 --- a/src/whop_sdk/types/payment_list_response.py +++ b/src/whop_sdk/types/payment_list_response.py @@ -345,6 +345,13 @@ class PaymentListResponse(BaseModel): otherwise false. Used to decide if Whop can attempt the charge again. """ + settlement_currency: str + """ + The currency in which the creator receives payouts and fees are charged (e.g., + 'usd', 'eur'). For multi-currency payments this differs from the payment + currency. + """ + status: Optional[ReceiptStatus] = None """The status of a receipt""" diff --git a/src/whop_sdk/types/payment_refund_params.py b/src/whop_sdk/types/payment_refund_params.py index 9d7d2bc..df2eef7 100644 --- a/src/whop_sdk/types/payment_refund_params.py +++ b/src/whop_sdk/types/payment_refund_params.py @@ -10,7 +10,9 @@ class PaymentRefundParams(TypedDict, total=False): partial_amount: Optional[float] - """The amount to refund in the payment currency. + """The amount to refund. - If omitted, the full payment amount is refunded. + For multi-currency payments, this is in the charge currency (what the buyer + paid). For single-currency, this is in the payment currency. If omitted, the + full payment amount is refunded. """ diff --git a/src/whop_sdk/types/plan_create_params.py b/src/whop_sdk/types/plan_create_params.py index fbf052a..c0d6f89 100644 --- a/src/whop_sdk/types/plan_create_params.py +++ b/src/whop_sdk/types/plan_create_params.py @@ -24,6 +24,9 @@ class PlanCreateParams(TypedDict, total=False): product_id: Required[str] """The unique identifier of the product to attach this plan to.""" + adaptive_pricing_enabled: Optional[bool] + """Whether this plan accepts local currency payments via adaptive pricing.""" + billing_period: Optional[int] """The number of days between recurring charges. diff --git a/src/whop_sdk/types/plan_list_response.py b/src/whop_sdk/types/plan_list_response.py index 688d7f4..b034018 100644 --- a/src/whop_sdk/types/plan_list_response.py +++ b/src/whop_sdk/types/plan_list_response.py @@ -88,6 +88,12 @@ class PlanListResponse(BaseModel): id: str """The unique identifier for the plan.""" + adaptive_pricing_enabled: bool + """Whether the creator has turned on adaptive pricing for this plan. + + Raw setting — does not check processor compatibility or feature flags. + """ + billing_period: Optional[int] = None """The number of days between each recurring charge. diff --git a/src/whop_sdk/types/plan_update_params.py b/src/whop_sdk/types/plan_update_params.py index 8283769..d150d06 100644 --- a/src/whop_sdk/types/plan_update_params.py +++ b/src/whop_sdk/types/plan_update_params.py @@ -16,6 +16,9 @@ class PlanUpdateParams(TypedDict, total=False): + adaptive_pricing_enabled: Optional[bool] + """Whether this plan accepts local currency payments via adaptive pricing.""" + billing_period: Optional[int] """The number of days between recurring charges. diff --git a/src/whop_sdk/types/shared/checkout_configuration.py b/src/whop_sdk/types/shared/checkout_configuration.py index e1fcc4c..28dc317 100644 --- a/src/whop_sdk/types/shared/checkout_configuration.py +++ b/src/whop_sdk/types/shared/checkout_configuration.py @@ -47,6 +47,12 @@ class Plan(BaseModel): id: str """The unique identifier for the plan.""" + adaptive_pricing_enabled: bool + """Whether the creator has turned on adaptive pricing for this plan. + + Raw setting — does not check processor compatibility or feature flags. + """ + billing_period: Optional[int] = None """The number of days between each recurring charge. diff --git a/src/whop_sdk/types/shared/payment.py b/src/whop_sdk/types/shared/payment.py index 579c980..bc9a6dc 100644 --- a/src/whop_sdk/types/shared/payment.py +++ b/src/whop_sdk/types/shared/payment.py @@ -508,6 +508,25 @@ class Payment(BaseModel): otherwise false. Used to decide if Whop can attempt the charge again. """ + settlement_amount: float + """ + The payment amount in the creator's settlement currency (what the creator priced + in). Equal to final_amount for single-currency payments. + """ + + settlement_currency: str + """ + The currency in which the creator receives payouts and fees are charged (e.g., + 'usd', 'eur'). For multi-currency payments this differs from the payment + currency. + """ + + settlement_exchange_rate: Optional[float] = None + """ + The locked exchange rate used to convert from the buyer's payment currency to + the creator's settlement currency. Null for single-currency payments. + """ + status: Optional[ReceiptStatus] = None """The status of a receipt""" diff --git a/src/whop_sdk/types/shared/plan.py b/src/whop_sdk/types/shared/plan.py index a132f58..c603ac9 100644 --- a/src/whop_sdk/types/shared/plan.py +++ b/src/whop_sdk/types/shared/plan.py @@ -112,6 +112,12 @@ class Plan(BaseModel): id: str """The unique identifier for the plan.""" + adaptive_pricing_enabled: bool + """Whether the creator has turned on adaptive pricing for this plan. + + Raw setting — does not check processor compatibility or feature flags. + """ + billing_period: Optional[int] = None """The number of days between each recurring charge. diff --git a/tests/api_resources/test_checkout_configurations.py b/tests/api_resources/test_checkout_configurations.py index 8bf0ce9..32f786e 100644 --- a/tests/api_resources/test_checkout_configurations.py +++ b/tests/api_resources/test_checkout_configurations.py @@ -40,6 +40,7 @@ def test_method_create_with_all_params_overload_1(self, client: Whop) -> None: plan={ "company_id": "biz_xxxxxxxxxxxxxx", "currency": "usd", + "adaptive_pricing_enabled": True, "application_fee_amount": 6.9, "billing_period": 42, "custom_fields": [ @@ -387,6 +388,7 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn plan={ "company_id": "biz_xxxxxxxxxxxxxx", "currency": "usd", + "adaptive_pricing_enabled": True, "application_fee_amount": 6.9, "billing_period": 42, "custom_fields": [ diff --git a/tests/api_resources/test_plans.py b/tests/api_resources/test_plans.py index 878c551..fbe3db1 100644 --- a/tests/api_resources/test_plans.py +++ b/tests/api_resources/test_plans.py @@ -38,6 +38,7 @@ def test_method_create_with_all_params(self, client: Whop) -> None: plan = client.plans.create( company_id="biz_xxxxxxxxxxxxxx", product_id="prod_xxxxxxxxxxxxx", + adaptive_pricing_enabled=True, billing_period=42, checkout_styling={ "background_color": "background_color", @@ -163,6 +164,7 @@ def test_method_update(self, client: Whop) -> None: def test_method_update_with_all_params(self, client: Whop) -> None: plan = client.plans.update( id="plan_xxxxxxxxxxxxx", + adaptive_pricing_enabled=True, billing_period=42, checkout_styling={ "background_color": "background_color", @@ -356,6 +358,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> N plan = await async_client.plans.create( company_id="biz_xxxxxxxxxxxxxx", product_id="prod_xxxxxxxxxxxxx", + adaptive_pricing_enabled=True, billing_period=42, checkout_styling={ "background_color": "background_color", @@ -481,6 +484,7 @@ async def test_method_update(self, async_client: AsyncWhop) -> None: async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> None: plan = await async_client.plans.update( id="plan_xxxxxxxxxxxxx", + adaptive_pricing_enabled=True, billing_period=42, checkout_styling={ "background_color": "background_color", From e2a497c259e84822aec5a13ad5cbc7cc8e8b882c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 3 May 2026 06:16:56 +0000 Subject: [PATCH 29/35] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index f9a9223..89c1fac 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 195 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-f2ca2af230fc105022e0ed59b10ab75dbcdd9f0726c492c49b5d2dca0c141daf.yml -openapi_spec_hash: 1db260b14e71594936e3ebe4438ad2cf +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-bb72eb761be4974d4fd84c6820d993ed4500858f4022391f3a8ff71480a9c0ce.yml +openapi_spec_hash: a4c166d585688a123caa9a5abb575406 config_hash: 6ad5a913fda410def47bf2ed841e2064 From 69d6c5cadc4d7a1bb7f2bb44b06e8f424d8e7bd0 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 00:43:22 +0000 Subject: [PATCH 30/35] feat(api): manual updates --- .stats.yml | 6 +- api.md | 109 +- src/whop_sdk/_client.py | 264 +++ src/whop_sdk/resources/__init__.py | 84 + src/whop_sdk/resources/ad_campaigns.py | 911 ++++++++ src/whop_sdk/resources/ad_groups.py | 762 +++++++ src/whop_sdk/resources/ads.py | 510 +++++ src/whop_sdk/resources/bounties.py | 495 +++++ src/whop_sdk/resources/companies.py | 135 +- src/whop_sdk/resources/conversions.py | 324 +++ src/whop_sdk/resources/stats.py | 760 +++++++ src/whop_sdk/types/__init__.py | 39 + .../types/ad_campaign_create_params.py | 85 + .../types/ad_campaign_create_response.py | 509 +++++ src/whop_sdk/types/ad_campaign_list_params.py | 44 + .../types/ad_campaign_list_response.py | 106 + .../types/ad_campaign_pause_response.py | 509 +++++ .../types/ad_campaign_retrieve_response.py | 509 +++++ .../types/ad_campaign_unpause_response.py | 509 +++++ .../types/ad_campaign_update_params.py | 76 + .../types/ad_campaign_update_response.py | 509 +++++ src/whop_sdk/types/ad_create_params.py | 473 +++++ src/whop_sdk/types/ad_create_response.py | 249 +++ src/whop_sdk/types/ad_group_create_params.py | 1219 +++++++++++ .../types/ad_group_create_response.py | 438 ++++ .../types/ad_group_delete_response.py | 7 + src/whop_sdk/types/ad_group_list_params.py | 43 + src/whop_sdk/types/ad_group_list_response.py | 416 ++++ .../types/ad_group_retrieve_response.py | 438 ++++ src/whop_sdk/types/ad_group_update_params.py | 1216 +++++++++++ .../types/ad_group_update_response.py | 438 ++++ src/whop_sdk/types/ad_list_params.py | 52 + src/whop_sdk/types/ad_list_response.py | 221 ++ src/whop_sdk/types/ad_retrieve_response.py | 249 +++ src/whop_sdk/types/bounty_create_params.py | 50 + src/whop_sdk/types/bounty_create_response.py | 46 + src/whop_sdk/types/bounty_list_params.py | 36 + src/whop_sdk/types/bounty_list_response.py | 46 + .../types/bounty_retrieve_response.py | 46 + .../types/company_create_api_key_params.py | 47 + .../types/company_create_api_key_response.py | 20 + .../types/conversion_create_params.py | 183 ++ .../types/conversion_create_response.py | 12 + src/whop_sdk/types/stat_describe_params.py | 22 + src/whop_sdk/types/stat_describe_response.py | 256 +++ .../types/stat_query_metric_params.py | 47 + .../types/stat_query_metric_response.py | 53 + src/whop_sdk/types/stat_query_raw_params.py | 41 + src/whop_sdk/types/stat_query_raw_response.py | 49 + src/whop_sdk/types/stat_run_sql_params.py | 44 + src/whop_sdk/types/stat_run_sql_response.py | 53 + tests/api_resources/test_ad_campaigns.py | 661 ++++++ tests/api_resources/test_ad_groups.py | 1838 +++++++++++++++++ tests/api_resources/test_ads.py | 482 +++++ tests/api_resources/test_bounties.py | 313 +++ tests/api_resources/test_companies.py | 129 ++ tests/api_resources/test_conversions.py | 215 ++ tests/api_resources/test_stats.py | 414 ++++ 58 files changed, 17812 insertions(+), 5 deletions(-) create mode 100644 src/whop_sdk/resources/ad_campaigns.py create mode 100644 src/whop_sdk/resources/ad_groups.py create mode 100644 src/whop_sdk/resources/ads.py create mode 100644 src/whop_sdk/resources/bounties.py create mode 100644 src/whop_sdk/resources/conversions.py create mode 100644 src/whop_sdk/resources/stats.py create mode 100644 src/whop_sdk/types/ad_campaign_create_params.py create mode 100644 src/whop_sdk/types/ad_campaign_create_response.py create mode 100644 src/whop_sdk/types/ad_campaign_list_params.py create mode 100644 src/whop_sdk/types/ad_campaign_list_response.py create mode 100644 src/whop_sdk/types/ad_campaign_pause_response.py create mode 100644 src/whop_sdk/types/ad_campaign_retrieve_response.py create mode 100644 src/whop_sdk/types/ad_campaign_unpause_response.py create mode 100644 src/whop_sdk/types/ad_campaign_update_params.py create mode 100644 src/whop_sdk/types/ad_campaign_update_response.py create mode 100644 src/whop_sdk/types/ad_create_params.py create mode 100644 src/whop_sdk/types/ad_create_response.py create mode 100644 src/whop_sdk/types/ad_group_create_params.py create mode 100644 src/whop_sdk/types/ad_group_create_response.py create mode 100644 src/whop_sdk/types/ad_group_delete_response.py create mode 100644 src/whop_sdk/types/ad_group_list_params.py create mode 100644 src/whop_sdk/types/ad_group_list_response.py create mode 100644 src/whop_sdk/types/ad_group_retrieve_response.py create mode 100644 src/whop_sdk/types/ad_group_update_params.py create mode 100644 src/whop_sdk/types/ad_group_update_response.py create mode 100644 src/whop_sdk/types/ad_list_params.py create mode 100644 src/whop_sdk/types/ad_list_response.py create mode 100644 src/whop_sdk/types/ad_retrieve_response.py create mode 100644 src/whop_sdk/types/bounty_create_params.py create mode 100644 src/whop_sdk/types/bounty_create_response.py create mode 100644 src/whop_sdk/types/bounty_list_params.py create mode 100644 src/whop_sdk/types/bounty_list_response.py create mode 100644 src/whop_sdk/types/bounty_retrieve_response.py create mode 100644 src/whop_sdk/types/company_create_api_key_params.py create mode 100644 src/whop_sdk/types/company_create_api_key_response.py create mode 100644 src/whop_sdk/types/conversion_create_params.py create mode 100644 src/whop_sdk/types/conversion_create_response.py create mode 100644 src/whop_sdk/types/stat_describe_params.py create mode 100644 src/whop_sdk/types/stat_describe_response.py create mode 100644 src/whop_sdk/types/stat_query_metric_params.py create mode 100644 src/whop_sdk/types/stat_query_metric_response.py create mode 100644 src/whop_sdk/types/stat_query_raw_params.py create mode 100644 src/whop_sdk/types/stat_query_raw_response.py create mode 100644 src/whop_sdk/types/stat_run_sql_params.py create mode 100644 src/whop_sdk/types/stat_run_sql_response.py create mode 100644 tests/api_resources/test_ad_campaigns.py create mode 100644 tests/api_resources/test_ad_groups.py create mode 100644 tests/api_resources/test_ads.py create mode 100644 tests/api_resources/test_bounties.py create mode 100644 tests/api_resources/test_conversions.py create mode 100644 tests/api_resources/test_stats.py diff --git a/.stats.yml b/.stats.yml index 89c1fac..75c2f68 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 195 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-bb72eb761be4974d4fd84c6820d993ed4500858f4022391f3a8ff71480a9c0ce.yml +configured_endpoints: 218 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-dcbc5afc2351d027e101397493a5ec8352a81a727258ea1bbdfc15059c953772.yml openapi_spec_hash: a4c166d585688a123caa9a5abb575406 -config_hash: 6ad5a913fda410def47bf2ed841e2064 +config_hash: 82b4c2cb9b7d1707feb0f11c7c2426e9 diff --git a/api.md b/api.md index 11bd5e6..5a56f6a 100644 --- a/api.md +++ b/api.md @@ -131,7 +131,7 @@ Methods: Types: ```python -from whop_sdk.types import SocialLinkWebsites, CompanyListResponse +from whop_sdk.types import SocialLinkWebsites, CompanyListResponse, CompanyCreateAPIKeyResponse ``` Methods: @@ -140,6 +140,7 @@ Methods: - client.companies.retrieve(id) -> Company - client.companies.update(id, \*\*params) -> Company - client.companies.list(\*\*params) -> SyncCursorPage[CompanyListResponse] +- client.companies.create_api_key(parent_company_id, \*\*params) -> CompanyCreateAPIKeyResponse # Webhooks @@ -984,3 +985,109 @@ Methods: - client.affiliates.overrides.update(override_id, \*, id, \*\*params) -> OverrideUpdateResponse - client.affiliates.overrides.list(id, \*\*params) -> SyncCursorPage[OverrideListResponse] - client.affiliates.overrides.delete(override_id, \*, id) -> OverrideDeleteResponse + +# Bounties + +Types: + +```python +from whop_sdk.types import BountyCreateResponse, BountyRetrieveResponse, BountyListResponse +``` + +Methods: + +- client.bounties.create(\*\*params) -> BountyCreateResponse +- client.bounties.retrieve(id) -> BountyRetrieveResponse +- client.bounties.list(\*\*params) -> SyncCursorPage[BountyListResponse] + +# Stats + +Types: + +```python +from whop_sdk.types import ( + StatDescribeResponse, + StatQueryMetricResponse, + StatQueryRawResponse, + StatRunSqlResponse, +) +``` + +Methods: + +- client.stats.describe(\*\*params) -> StatDescribeResponse +- client.stats.query_metric(\*\*params) -> StatQueryMetricResponse +- client.stats.query_raw(\*\*params) -> StatQueryRawResponse +- client.stats.run_sql(\*\*params) -> StatRunSqlResponse + +# AdCampaigns + +Types: + +```python +from whop_sdk.types import ( + AdCampaignCreateResponse, + AdCampaignRetrieveResponse, + AdCampaignUpdateResponse, + AdCampaignListResponse, + AdCampaignPauseResponse, + AdCampaignUnpauseResponse, +) +``` + +Methods: + +- client.ad_campaigns.create(\*\*params) -> AdCampaignCreateResponse +- client.ad_campaigns.retrieve(id) -> AdCampaignRetrieveResponse +- client.ad_campaigns.update(id, \*\*params) -> AdCampaignUpdateResponse +- client.ad_campaigns.list(\*\*params) -> SyncCursorPage[AdCampaignListResponse] +- client.ad_campaigns.pause(id) -> AdCampaignPauseResponse +- client.ad_campaigns.unpause(id) -> AdCampaignUnpauseResponse + +# AdGroups + +Types: + +```python +from whop_sdk.types import ( + AdGroupCreateResponse, + AdGroupRetrieveResponse, + AdGroupUpdateResponse, + AdGroupListResponse, + AdGroupDeleteResponse, +) +``` + +Methods: + +- client.ad_groups.create(\*\*params) -> AdGroupCreateResponse +- client.ad_groups.retrieve(id) -> AdGroupRetrieveResponse +- client.ad_groups.update(id, \*\*params) -> AdGroupUpdateResponse +- client.ad_groups.list(\*\*params) -> SyncCursorPage[AdGroupListResponse] +- client.ad_groups.delete(id) -> AdGroupDeleteResponse + +# Ads + +Types: + +```python +from whop_sdk.types import AdCreateResponse, AdRetrieveResponse, AdListResponse +``` + +Methods: + +- client.ads.create(\*\*params) -> AdCreateResponse +- client.ads.retrieve(id) -> AdRetrieveResponse +- client.ads.list(\*\*params) -> SyncCursorPage[AdListResponse] + +# Conversions + +Types: + +```python +from whop_sdk.types import ConversionCreateResponse +``` + +Methods: + +- client.conversions.create(\*\*params) -> ConversionCreateResponse diff --git a/src/whop_sdk/_client.py b/src/whop_sdk/_client.py index 32e8de8..8da7e48 100644 --- a/src/whop_sdk/_client.py +++ b/src/whop_sdk/_client.py @@ -36,10 +36,12 @@ if TYPE_CHECKING: from .resources import ( + ads, apps, files, leads, plans, + stats, users, forums, topups, @@ -49,12 +51,14 @@ refunds, reviews, ai_chats, + bounties, disputes, invoices, messages, payments, products, webhooks, + ad_groups, companies, reactions, shipments, @@ -62,6 +66,7 @@ affiliates, app_builds, dm_members, + conversions, dm_channels, experiences, fee_markups, @@ -69,6 +74,7 @@ memberships, promo_codes, withdrawals, + ad_campaigns, access_tokens, account_links, chat_channels, @@ -90,10 +96,12 @@ company_token_transactions, course_lesson_interactions, ) + from .resources.ads import AdsResource, AsyncAdsResource from .resources.apps import AppsResource, AsyncAppsResource from .resources.files import FilesResource, AsyncFilesResource from .resources.leads import LeadsResource, AsyncLeadsResource from .resources.plans import PlansResource, AsyncPlansResource + from .resources.stats import StatsResource, AsyncStatsResource from .resources.users import UsersResource, AsyncUsersResource from .resources.forums import ForumsResource, AsyncForumsResource from .resources.topups import TopupsResource, AsyncTopupsResource @@ -103,18 +111,21 @@ from .resources.refunds import RefundsResource, AsyncRefundsResource from .resources.reviews import ReviewsResource, AsyncReviewsResource from .resources.ai_chats import AIChatsResource, AsyncAIChatsResource + from .resources.bounties import BountiesResource, AsyncBountiesResource from .resources.disputes import DisputesResource, AsyncDisputesResource from .resources.invoices import InvoicesResource, AsyncInvoicesResource from .resources.messages import MessagesResource, AsyncMessagesResource from .resources.payments import PaymentsResource, AsyncPaymentsResource from .resources.products import ProductsResource, AsyncProductsResource from .resources.webhooks import WebhooksResource, AsyncWebhooksResource + from .resources.ad_groups import AdGroupsResource, AsyncAdGroupsResource from .resources.companies import CompaniesResource, AsyncCompaniesResource from .resources.reactions import ReactionsResource, AsyncReactionsResource from .resources.shipments import ShipmentsResource, AsyncShipmentsResource from .resources.transfers import TransfersResource, AsyncTransfersResource from .resources.app_builds import AppBuildsResource, AsyncAppBuildsResource from .resources.dm_members import DmMembersResource, AsyncDmMembersResource + from .resources.conversions import ConversionsResource, AsyncConversionsResource from .resources.dm_channels import DmChannelsResource, AsyncDmChannelsResource from .resources.experiences import ExperiencesResource, AsyncExperiencesResource from .resources.fee_markups import FeeMarkupsResource, AsyncFeeMarkupsResource @@ -122,6 +133,7 @@ from .resources.memberships import MembershipsResource, AsyncMembershipsResource from .resources.promo_codes import PromoCodesResource, AsyncPromoCodesResource from .resources.withdrawals import WithdrawalsResource, AsyncWithdrawalsResource + from .resources.ad_campaigns import AdCampaignsResource, AsyncAdCampaignsResource from .resources.access_tokens import AccessTokensResource, AsyncAccessTokensResource from .resources.account_links import AccountLinksResource, AsyncAccountLinksResource from .resources.chat_channels import ChatChannelsResource, AsyncChatChannelsResource @@ -553,6 +565,48 @@ def affiliates(self) -> AffiliatesResource: return AffiliatesResource(self) + @cached_property + def bounties(self) -> BountiesResource: + """Bounties""" + from .resources.bounties import BountiesResource + + return BountiesResource(self) + + @cached_property + def stats(self) -> StatsResource: + """Stats""" + from .resources.stats import StatsResource + + return StatsResource(self) + + @cached_property + def ad_campaigns(self) -> AdCampaignsResource: + """Ad campaigns""" + from .resources.ad_campaigns import AdCampaignsResource + + return AdCampaignsResource(self) + + @cached_property + def ad_groups(self) -> AdGroupsResource: + """Ad groups""" + from .resources.ad_groups import AdGroupsResource + + return AdGroupsResource(self) + + @cached_property + def ads(self) -> AdsResource: + """Ads""" + from .resources.ads import AdsResource + + return AdsResource(self) + + @cached_property + def conversions(self) -> ConversionsResource: + """Conversions""" + from .resources.conversions import ConversionsResource + + return ConversionsResource(self) + @cached_property def with_raw_response(self) -> WhopWithRawResponse: return WhopWithRawResponse(self) @@ -1071,6 +1125,48 @@ def affiliates(self) -> AsyncAffiliatesResource: return AsyncAffiliatesResource(self) + @cached_property + def bounties(self) -> AsyncBountiesResource: + """Bounties""" + from .resources.bounties import AsyncBountiesResource + + return AsyncBountiesResource(self) + + @cached_property + def stats(self) -> AsyncStatsResource: + """Stats""" + from .resources.stats import AsyncStatsResource + + return AsyncStatsResource(self) + + @cached_property + def ad_campaigns(self) -> AsyncAdCampaignsResource: + """Ad campaigns""" + from .resources.ad_campaigns import AsyncAdCampaignsResource + + return AsyncAdCampaignsResource(self) + + @cached_property + def ad_groups(self) -> AsyncAdGroupsResource: + """Ad groups""" + from .resources.ad_groups import AsyncAdGroupsResource + + return AsyncAdGroupsResource(self) + + @cached_property + def ads(self) -> AsyncAdsResource: + """Ads""" + from .resources.ads import AsyncAdsResource + + return AsyncAdsResource(self) + + @cached_property + def conversions(self) -> AsyncConversionsResource: + """Conversions""" + from .resources.conversions import AsyncConversionsResource + + return AsyncConversionsResource(self) + @cached_property def with_raw_response(self) -> AsyncWhopWithRawResponse: return AsyncWhopWithRawResponse(self) @@ -1516,6 +1612,48 @@ def affiliates(self) -> affiliates.AffiliatesResourceWithRawResponse: return AffiliatesResourceWithRawResponse(self._client.affiliates) + @cached_property + def bounties(self) -> bounties.BountiesResourceWithRawResponse: + """Bounties""" + from .resources.bounties import BountiesResourceWithRawResponse + + return BountiesResourceWithRawResponse(self._client.bounties) + + @cached_property + def stats(self) -> stats.StatsResourceWithRawResponse: + """Stats""" + from .resources.stats import StatsResourceWithRawResponse + + return StatsResourceWithRawResponse(self._client.stats) + + @cached_property + def ad_campaigns(self) -> ad_campaigns.AdCampaignsResourceWithRawResponse: + """Ad campaigns""" + from .resources.ad_campaigns import AdCampaignsResourceWithRawResponse + + return AdCampaignsResourceWithRawResponse(self._client.ad_campaigns) + + @cached_property + def ad_groups(self) -> ad_groups.AdGroupsResourceWithRawResponse: + """Ad groups""" + from .resources.ad_groups import AdGroupsResourceWithRawResponse + + return AdGroupsResourceWithRawResponse(self._client.ad_groups) + + @cached_property + def ads(self) -> ads.AdsResourceWithRawResponse: + """Ads""" + from .resources.ads import AdsResourceWithRawResponse + + return AdsResourceWithRawResponse(self._client.ads) + + @cached_property + def conversions(self) -> conversions.ConversionsResourceWithRawResponse: + """Conversions""" + from .resources.conversions import ConversionsResourceWithRawResponse + + return ConversionsResourceWithRawResponse(self._client.conversions) + class AsyncWhopWithRawResponse: _client: AsyncWhop @@ -1848,6 +1986,48 @@ def affiliates(self) -> affiliates.AsyncAffiliatesResourceWithRawResponse: return AsyncAffiliatesResourceWithRawResponse(self._client.affiliates) + @cached_property + def bounties(self) -> bounties.AsyncBountiesResourceWithRawResponse: + """Bounties""" + from .resources.bounties import AsyncBountiesResourceWithRawResponse + + return AsyncBountiesResourceWithRawResponse(self._client.bounties) + + @cached_property + def stats(self) -> stats.AsyncStatsResourceWithRawResponse: + """Stats""" + from .resources.stats import AsyncStatsResourceWithRawResponse + + return AsyncStatsResourceWithRawResponse(self._client.stats) + + @cached_property + def ad_campaigns(self) -> ad_campaigns.AsyncAdCampaignsResourceWithRawResponse: + """Ad campaigns""" + from .resources.ad_campaigns import AsyncAdCampaignsResourceWithRawResponse + + return AsyncAdCampaignsResourceWithRawResponse(self._client.ad_campaigns) + + @cached_property + def ad_groups(self) -> ad_groups.AsyncAdGroupsResourceWithRawResponse: + """Ad groups""" + from .resources.ad_groups import AsyncAdGroupsResourceWithRawResponse + + return AsyncAdGroupsResourceWithRawResponse(self._client.ad_groups) + + @cached_property + def ads(self) -> ads.AsyncAdsResourceWithRawResponse: + """Ads""" + from .resources.ads import AsyncAdsResourceWithRawResponse + + return AsyncAdsResourceWithRawResponse(self._client.ads) + + @cached_property + def conversions(self) -> conversions.AsyncConversionsResourceWithRawResponse: + """Conversions""" + from .resources.conversions import AsyncConversionsResourceWithRawResponse + + return AsyncConversionsResourceWithRawResponse(self._client.conversions) + class WhopWithStreamedResponse: _client: Whop @@ -2180,6 +2360,48 @@ def affiliates(self) -> affiliates.AffiliatesResourceWithStreamingResponse: return AffiliatesResourceWithStreamingResponse(self._client.affiliates) + @cached_property + def bounties(self) -> bounties.BountiesResourceWithStreamingResponse: + """Bounties""" + from .resources.bounties import BountiesResourceWithStreamingResponse + + return BountiesResourceWithStreamingResponse(self._client.bounties) + + @cached_property + def stats(self) -> stats.StatsResourceWithStreamingResponse: + """Stats""" + from .resources.stats import StatsResourceWithStreamingResponse + + return StatsResourceWithStreamingResponse(self._client.stats) + + @cached_property + def ad_campaigns(self) -> ad_campaigns.AdCampaignsResourceWithStreamingResponse: + """Ad campaigns""" + from .resources.ad_campaigns import AdCampaignsResourceWithStreamingResponse + + return AdCampaignsResourceWithStreamingResponse(self._client.ad_campaigns) + + @cached_property + def ad_groups(self) -> ad_groups.AdGroupsResourceWithStreamingResponse: + """Ad groups""" + from .resources.ad_groups import AdGroupsResourceWithStreamingResponse + + return AdGroupsResourceWithStreamingResponse(self._client.ad_groups) + + @cached_property + def ads(self) -> ads.AdsResourceWithStreamingResponse: + """Ads""" + from .resources.ads import AdsResourceWithStreamingResponse + + return AdsResourceWithStreamingResponse(self._client.ads) + + @cached_property + def conversions(self) -> conversions.ConversionsResourceWithStreamingResponse: + """Conversions""" + from .resources.conversions import ConversionsResourceWithStreamingResponse + + return ConversionsResourceWithStreamingResponse(self._client.conversions) + class AsyncWhopWithStreamedResponse: _client: AsyncWhop @@ -2516,6 +2738,48 @@ def affiliates(self) -> affiliates.AsyncAffiliatesResourceWithStreamingResponse: return AsyncAffiliatesResourceWithStreamingResponse(self._client.affiliates) + @cached_property + def bounties(self) -> bounties.AsyncBountiesResourceWithStreamingResponse: + """Bounties""" + from .resources.bounties import AsyncBountiesResourceWithStreamingResponse + + return AsyncBountiesResourceWithStreamingResponse(self._client.bounties) + + @cached_property + def stats(self) -> stats.AsyncStatsResourceWithStreamingResponse: + """Stats""" + from .resources.stats import AsyncStatsResourceWithStreamingResponse + + return AsyncStatsResourceWithStreamingResponse(self._client.stats) + + @cached_property + def ad_campaigns(self) -> ad_campaigns.AsyncAdCampaignsResourceWithStreamingResponse: + """Ad campaigns""" + from .resources.ad_campaigns import AsyncAdCampaignsResourceWithStreamingResponse + + return AsyncAdCampaignsResourceWithStreamingResponse(self._client.ad_campaigns) + + @cached_property + def ad_groups(self) -> ad_groups.AsyncAdGroupsResourceWithStreamingResponse: + """Ad groups""" + from .resources.ad_groups import AsyncAdGroupsResourceWithStreamingResponse + + return AsyncAdGroupsResourceWithStreamingResponse(self._client.ad_groups) + + @cached_property + def ads(self) -> ads.AsyncAdsResourceWithStreamingResponse: + """Ads""" + from .resources.ads import AsyncAdsResourceWithStreamingResponse + + return AsyncAdsResourceWithStreamingResponse(self._client.ads) + + @cached_property + def conversions(self) -> conversions.AsyncConversionsResourceWithStreamingResponse: + """Conversions""" + from .resources.conversions import AsyncConversionsResourceWithStreamingResponse + + return AsyncConversionsResourceWithStreamingResponse(self._client.conversions) + Client = Whop diff --git a/src/whop_sdk/resources/__init__.py b/src/whop_sdk/resources/__init__.py index e95bf53..cfbbf5f 100644 --- a/src/whop_sdk/resources/__init__.py +++ b/src/whop_sdk/resources/__init__.py @@ -1,5 +1,13 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from .ads import ( + AdsResource, + AsyncAdsResource, + AdsResourceWithRawResponse, + AsyncAdsResourceWithRawResponse, + AdsResourceWithStreamingResponse, + AsyncAdsResourceWithStreamingResponse, +) from .apps import ( AppsResource, AsyncAppsResource, @@ -32,6 +40,14 @@ PlansResourceWithStreamingResponse, AsyncPlansResourceWithStreamingResponse, ) +from .stats import ( + StatsResource, + AsyncStatsResource, + StatsResourceWithRawResponse, + AsyncStatsResourceWithRawResponse, + StatsResourceWithStreamingResponse, + AsyncStatsResourceWithStreamingResponse, +) from .users import ( UsersResource, AsyncUsersResource, @@ -104,6 +120,14 @@ AIChatsResourceWithStreamingResponse, AsyncAIChatsResourceWithStreamingResponse, ) +from .bounties import ( + BountiesResource, + AsyncBountiesResource, + BountiesResourceWithRawResponse, + AsyncBountiesResourceWithRawResponse, + BountiesResourceWithStreamingResponse, + AsyncBountiesResourceWithStreamingResponse, +) from .disputes import ( DisputesResource, AsyncDisputesResource, @@ -152,6 +176,14 @@ WebhooksResourceWithStreamingResponse, AsyncWebhooksResourceWithStreamingResponse, ) +from .ad_groups import ( + AdGroupsResource, + AsyncAdGroupsResource, + AdGroupsResourceWithRawResponse, + AsyncAdGroupsResourceWithRawResponse, + AdGroupsResourceWithStreamingResponse, + AsyncAdGroupsResourceWithStreamingResponse, +) from .companies import ( CompaniesResource, AsyncCompaniesResource, @@ -208,6 +240,14 @@ DmMembersResourceWithStreamingResponse, AsyncDmMembersResourceWithStreamingResponse, ) +from .conversions import ( + ConversionsResource, + AsyncConversionsResource, + ConversionsResourceWithRawResponse, + AsyncConversionsResourceWithRawResponse, + ConversionsResourceWithStreamingResponse, + AsyncConversionsResourceWithStreamingResponse, +) from .dm_channels import ( DmChannelsResource, AsyncDmChannelsResource, @@ -264,6 +304,14 @@ WithdrawalsResourceWithStreamingResponse, AsyncWithdrawalsResourceWithStreamingResponse, ) +from .ad_campaigns import ( + AdCampaignsResource, + AsyncAdCampaignsResource, + AdCampaignsResourceWithRawResponse, + AsyncAdCampaignsResourceWithRawResponse, + AdCampaignsResourceWithStreamingResponse, + AsyncAdCampaignsResourceWithStreamingResponse, +) from .access_tokens import ( AccessTokensResource, AsyncAccessTokensResource, @@ -744,4 +792,40 @@ "AsyncAffiliatesResourceWithRawResponse", "AffiliatesResourceWithStreamingResponse", "AsyncAffiliatesResourceWithStreamingResponse", + "BountiesResource", + "AsyncBountiesResource", + "BountiesResourceWithRawResponse", + "AsyncBountiesResourceWithRawResponse", + "BountiesResourceWithStreamingResponse", + "AsyncBountiesResourceWithStreamingResponse", + "StatsResource", + "AsyncStatsResource", + "StatsResourceWithRawResponse", + "AsyncStatsResourceWithRawResponse", + "StatsResourceWithStreamingResponse", + "AsyncStatsResourceWithStreamingResponse", + "AdCampaignsResource", + "AsyncAdCampaignsResource", + "AdCampaignsResourceWithRawResponse", + "AsyncAdCampaignsResourceWithRawResponse", + "AdCampaignsResourceWithStreamingResponse", + "AsyncAdCampaignsResourceWithStreamingResponse", + "AdGroupsResource", + "AsyncAdGroupsResource", + "AdGroupsResourceWithRawResponse", + "AsyncAdGroupsResourceWithRawResponse", + "AdGroupsResourceWithStreamingResponse", + "AsyncAdGroupsResourceWithStreamingResponse", + "AdsResource", + "AsyncAdsResource", + "AdsResourceWithRawResponse", + "AsyncAdsResourceWithRawResponse", + "AdsResourceWithStreamingResponse", + "AsyncAdsResourceWithStreamingResponse", + "ConversionsResource", + "AsyncConversionsResource", + "ConversionsResourceWithRawResponse", + "AsyncConversionsResourceWithRawResponse", + "ConversionsResourceWithStreamingResponse", + "AsyncConversionsResourceWithStreamingResponse", ] diff --git a/src/whop_sdk/resources/ad_campaigns.py b/src/whop_sdk/resources/ad_campaigns.py new file mode 100644 index 0000000..b14aa71 --- /dev/null +++ b/src/whop_sdk/resources/ad_campaigns.py @@ -0,0 +1,911 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from datetime import datetime +from typing_extensions import Literal + +import httpx + +from ..types import ad_campaign_list_params, ad_campaign_create_params, ad_campaign_update_params +from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..pagination import SyncCursorPage, AsyncCursorPage +from .._base_client import AsyncPaginator, make_request_options +from ..types.ad_campaign_list_response import AdCampaignListResponse +from ..types.ad_campaign_pause_response import AdCampaignPauseResponse +from ..types.ad_campaign_create_response import AdCampaignCreateResponse +from ..types.ad_campaign_update_response import AdCampaignUpdateResponse +from ..types.ad_campaign_unpause_response import AdCampaignUnpauseResponse +from ..types.ad_campaign_retrieve_response import AdCampaignRetrieveResponse + +__all__ = ["AdCampaignsResource", "AsyncAdCampaignsResource"] + + +class AdCampaignsResource(SyncAPIResource): + """Ad campaigns""" + + @cached_property + def with_raw_response(self) -> AdCampaignsResourceWithRawResponse: + """ + 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/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return AdCampaignsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AdCampaignsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return AdCampaignsResourceWithStreamingResponse(self) + + def create( + self, + *, + company_id: str, + config: ad_campaign_create_params.Config, + platform: Literal["meta", "tiktok"], + title: str, + ad_creative_set_ids: Optional[SequenceNotStr[str]] | Omit = omit, + budget: Optional[float] | Omit = omit, + budget_type: Optional[Literal["daily", "lifetime"]] | Omit = omit, + daily_budget: Optional[float] | Omit = omit, + product_id: Optional[str] | Omit = omit, + target_country_codes: Optional[SequenceNotStr[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, + ) -> AdCampaignCreateResponse: + """ + Creates a new ad campaign for a product. + + Required permissions: + + - `ad_campaign:create` + - `access_pass:basic:read` + - `company:balance:read` + + Args: + company_id: The company ID to create this ad campaign for. + + config: Unified campaign configuration (conversion goal, budget, bidding, etc.). + + platform: The ad platform to run on (e.g., meta, tiktok). + + title: The title of the ad campaign. Must be max 100 characters. + + ad_creative_set_ids: Array of creative set IDs to link to this campaign. + + budget: Budget amount in dollars. + + budget_type: The budget type for an ad campaign or ad group. + + daily_budget: Daily budget in dollars (minimum $5). Required unless lifetime_budget is set in + config. + + product_id: The unique identifier of the product to promote. + + target_country_codes: Array of ISO3166 country codes for territory targeting. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/ad_campaigns", + body=maybe_transform( + { + "company_id": company_id, + "config": config, + "platform": platform, + "title": title, + "ad_creative_set_ids": ad_creative_set_ids, + "budget": budget, + "budget_type": budget_type, + "daily_budget": daily_budget, + "product_id": product_id, + "target_country_codes": target_country_codes, + }, + ad_campaign_create_params.AdCampaignCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdCampaignCreateResponse, + ) + + def retrieve( + self, + 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, + ) -> AdCampaignRetrieveResponse: + """ + Retrieves a single ad campaign by its unique identifier. + + Required permissions: + + - `ad_campaign:basic:read` + - `access_pass:basic:read` + - `company:balance:read` + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._get( + path_template("/ad_campaigns/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdCampaignRetrieveResponse, + ) + + def update( + self, + id: str, + *, + ad_creative_set_ids: Optional[SequenceNotStr[str]] | Omit = omit, + budget: Optional[float] | Omit = omit, + budget_type: Optional[Literal["daily", "lifetime"]] | Omit = omit, + config: Optional[ad_campaign_update_params.Config] | Omit = omit, + daily_budget: Optional[float] | Omit = omit, + product_id: Optional[str] | Omit = omit, + target_country_codes: Optional[SequenceNotStr[str]] | Omit = omit, + title: Optional[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, + ) -> AdCampaignUpdateResponse: + """ + Updates an existing ad campaign. + + Required permissions: + + - `ad_campaign:update` + - `access_pass:basic:read` + - `company:balance:read` + + Args: + ad_creative_set_ids: Array of creative set IDs to link to this campaign. + + budget: Budget amount in dollars. + + budget_type: The budget type for an ad campaign or ad group. + + config: Unified campaign configuration (conversion goal, budget, bidding, etc.). + + daily_budget: Daily budget in dollars (minimum $5). + + product_id: The unique identifier of the product (access pass) to promote. + + target_country_codes: Array of ISO3166 country codes for territory targeting. + + title: The title of the ad campaign. Must be max 100 characters. + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._patch( + path_template("/ad_campaigns/{id}", id=id), + body=maybe_transform( + { + "ad_creative_set_ids": ad_creative_set_ids, + "budget": budget, + "budget_type": budget_type, + "config": config, + "daily_budget": daily_budget, + "product_id": product_id, + "target_country_codes": target_country_codes, + "title": title, + }, + ad_campaign_update_params.AdCampaignUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdCampaignUpdateResponse, + ) + + def list( + self, + *, + company_id: str, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + created_after: Union[str, datetime, None] | Omit = omit, + created_before: Union[str, datetime, None] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + query: Optional[str] | Omit = omit, + status: Optional[ + Literal[ + "active", + "paused", + "inactive", + "stale", + "pending_refund", + "payment_failed", + "draft", + "in_review", + "flagged", + ] + ] + | 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, + ) -> SyncCursorPage[AdCampaignListResponse]: + """ + Returns a paginated list of ad campaigns for a company, with optional filtering + by status, and creation date. + + Required permissions: + + - `ad_campaign:basic:read` + - `access_pass:basic:read` + + Args: + company_id: The unique identifier of the company to list ad campaigns for. + + after: Returns the elements in the list that come after the specified cursor. + + before: Returns the elements in the list that come before the specified cursor. + + created_after: Only return ad campaigns created after this timestamp. + + created_before: Only return ad campaigns created before this timestamp. + + first: Returns the first _n_ elements from the list. + + last: Returns the last _n_ elements from the list. + + query: Case-insensitive substring match against the campaign title. + + status: The status of an ad campaign. + + 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_api_list( + "/ad_campaigns", + page=SyncCursorPage[AdCampaignListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "company_id": company_id, + "after": after, + "before": before, + "created_after": created_after, + "created_before": created_before, + "first": first, + "last": last, + "query": query, + "status": status, + }, + ad_campaign_list_params.AdCampaignListParams, + ), + ), + model=AdCampaignListResponse, + ) + + def pause( + self, + 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, + ) -> AdCampaignPauseResponse: + """ + Pauses an ad campaign, optionally until a specific date. + + Required permissions: + + - `ad_campaign:update` + - `access_pass:basic:read` + - `company:balance:read` + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._post( + path_template("/ad_campaigns/{id}/pause", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdCampaignPauseResponse, + ) + + def unpause( + self, + 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, + ) -> AdCampaignUnpauseResponse: + """ + Resumes a paused ad campaign. + + Required permissions: + + - `ad_campaign:update` + - `access_pass:basic:read` + - `company:balance:read` + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._post( + path_template("/ad_campaigns/{id}/unpause", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdCampaignUnpauseResponse, + ) + + +class AsyncAdCampaignsResource(AsyncAPIResource): + """Ad campaigns""" + + @cached_property + def with_raw_response(self) -> AsyncAdCampaignsResourceWithRawResponse: + """ + 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/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return AsyncAdCampaignsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncAdCampaignsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return AsyncAdCampaignsResourceWithStreamingResponse(self) + + async def create( + self, + *, + company_id: str, + config: ad_campaign_create_params.Config, + platform: Literal["meta", "tiktok"], + title: str, + ad_creative_set_ids: Optional[SequenceNotStr[str]] | Omit = omit, + budget: Optional[float] | Omit = omit, + budget_type: Optional[Literal["daily", "lifetime"]] | Omit = omit, + daily_budget: Optional[float] | Omit = omit, + product_id: Optional[str] | Omit = omit, + target_country_codes: Optional[SequenceNotStr[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, + ) -> AdCampaignCreateResponse: + """ + Creates a new ad campaign for a product. + + Required permissions: + + - `ad_campaign:create` + - `access_pass:basic:read` + - `company:balance:read` + + Args: + company_id: The company ID to create this ad campaign for. + + config: Unified campaign configuration (conversion goal, budget, bidding, etc.). + + platform: The ad platform to run on (e.g., meta, tiktok). + + title: The title of the ad campaign. Must be max 100 characters. + + ad_creative_set_ids: Array of creative set IDs to link to this campaign. + + budget: Budget amount in dollars. + + budget_type: The budget type for an ad campaign or ad group. + + daily_budget: Daily budget in dollars (minimum $5). Required unless lifetime_budget is set in + config. + + product_id: The unique identifier of the product to promote. + + target_country_codes: Array of ISO3166 country codes for territory targeting. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/ad_campaigns", + body=await async_maybe_transform( + { + "company_id": company_id, + "config": config, + "platform": platform, + "title": title, + "ad_creative_set_ids": ad_creative_set_ids, + "budget": budget, + "budget_type": budget_type, + "daily_budget": daily_budget, + "product_id": product_id, + "target_country_codes": target_country_codes, + }, + ad_campaign_create_params.AdCampaignCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdCampaignCreateResponse, + ) + + async def retrieve( + self, + 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, + ) -> AdCampaignRetrieveResponse: + """ + Retrieves a single ad campaign by its unique identifier. + + Required permissions: + + - `ad_campaign:basic:read` + - `access_pass:basic:read` + - `company:balance:read` + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._get( + path_template("/ad_campaigns/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdCampaignRetrieveResponse, + ) + + async def update( + self, + id: str, + *, + ad_creative_set_ids: Optional[SequenceNotStr[str]] | Omit = omit, + budget: Optional[float] | Omit = omit, + budget_type: Optional[Literal["daily", "lifetime"]] | Omit = omit, + config: Optional[ad_campaign_update_params.Config] | Omit = omit, + daily_budget: Optional[float] | Omit = omit, + product_id: Optional[str] | Omit = omit, + target_country_codes: Optional[SequenceNotStr[str]] | Omit = omit, + title: Optional[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, + ) -> AdCampaignUpdateResponse: + """ + Updates an existing ad campaign. + + Required permissions: + + - `ad_campaign:update` + - `access_pass:basic:read` + - `company:balance:read` + + Args: + ad_creative_set_ids: Array of creative set IDs to link to this campaign. + + budget: Budget amount in dollars. + + budget_type: The budget type for an ad campaign or ad group. + + config: Unified campaign configuration (conversion goal, budget, bidding, etc.). + + daily_budget: Daily budget in dollars (minimum $5). + + product_id: The unique identifier of the product (access pass) to promote. + + target_country_codes: Array of ISO3166 country codes for territory targeting. + + title: The title of the ad campaign. Must be max 100 characters. + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._patch( + path_template("/ad_campaigns/{id}", id=id), + body=await async_maybe_transform( + { + "ad_creative_set_ids": ad_creative_set_ids, + "budget": budget, + "budget_type": budget_type, + "config": config, + "daily_budget": daily_budget, + "product_id": product_id, + "target_country_codes": target_country_codes, + "title": title, + }, + ad_campaign_update_params.AdCampaignUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdCampaignUpdateResponse, + ) + + def list( + self, + *, + company_id: str, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + created_after: Union[str, datetime, None] | Omit = omit, + created_before: Union[str, datetime, None] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + query: Optional[str] | Omit = omit, + status: Optional[ + Literal[ + "active", + "paused", + "inactive", + "stale", + "pending_refund", + "payment_failed", + "draft", + "in_review", + "flagged", + ] + ] + | 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, + ) -> AsyncPaginator[AdCampaignListResponse, AsyncCursorPage[AdCampaignListResponse]]: + """ + Returns a paginated list of ad campaigns for a company, with optional filtering + by status, and creation date. + + Required permissions: + + - `ad_campaign:basic:read` + - `access_pass:basic:read` + + Args: + company_id: The unique identifier of the company to list ad campaigns for. + + after: Returns the elements in the list that come after the specified cursor. + + before: Returns the elements in the list that come before the specified cursor. + + created_after: Only return ad campaigns created after this timestamp. + + created_before: Only return ad campaigns created before this timestamp. + + first: Returns the first _n_ elements from the list. + + last: Returns the last _n_ elements from the list. + + query: Case-insensitive substring match against the campaign title. + + status: The status of an ad campaign. + + 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_api_list( + "/ad_campaigns", + page=AsyncCursorPage[AdCampaignListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "company_id": company_id, + "after": after, + "before": before, + "created_after": created_after, + "created_before": created_before, + "first": first, + "last": last, + "query": query, + "status": status, + }, + ad_campaign_list_params.AdCampaignListParams, + ), + ), + model=AdCampaignListResponse, + ) + + async def pause( + self, + 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, + ) -> AdCampaignPauseResponse: + """ + Pauses an ad campaign, optionally until a specific date. + + Required permissions: + + - `ad_campaign:update` + - `access_pass:basic:read` + - `company:balance:read` + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._post( + path_template("/ad_campaigns/{id}/pause", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdCampaignPauseResponse, + ) + + async def unpause( + self, + 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, + ) -> AdCampaignUnpauseResponse: + """ + Resumes a paused ad campaign. + + Required permissions: + + - `ad_campaign:update` + - `access_pass:basic:read` + - `company:balance:read` + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._post( + path_template("/ad_campaigns/{id}/unpause", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdCampaignUnpauseResponse, + ) + + +class AdCampaignsResourceWithRawResponse: + def __init__(self, ad_campaigns: AdCampaignsResource) -> None: + self._ad_campaigns = ad_campaigns + + self.create = to_raw_response_wrapper( + ad_campaigns.create, + ) + self.retrieve = to_raw_response_wrapper( + ad_campaigns.retrieve, + ) + self.update = to_raw_response_wrapper( + ad_campaigns.update, + ) + self.list = to_raw_response_wrapper( + ad_campaigns.list, + ) + self.pause = to_raw_response_wrapper( + ad_campaigns.pause, + ) + self.unpause = to_raw_response_wrapper( + ad_campaigns.unpause, + ) + + +class AsyncAdCampaignsResourceWithRawResponse: + def __init__(self, ad_campaigns: AsyncAdCampaignsResource) -> None: + self._ad_campaigns = ad_campaigns + + self.create = async_to_raw_response_wrapper( + ad_campaigns.create, + ) + self.retrieve = async_to_raw_response_wrapper( + ad_campaigns.retrieve, + ) + self.update = async_to_raw_response_wrapper( + ad_campaigns.update, + ) + self.list = async_to_raw_response_wrapper( + ad_campaigns.list, + ) + self.pause = async_to_raw_response_wrapper( + ad_campaigns.pause, + ) + self.unpause = async_to_raw_response_wrapper( + ad_campaigns.unpause, + ) + + +class AdCampaignsResourceWithStreamingResponse: + def __init__(self, ad_campaigns: AdCampaignsResource) -> None: + self._ad_campaigns = ad_campaigns + + self.create = to_streamed_response_wrapper( + ad_campaigns.create, + ) + self.retrieve = to_streamed_response_wrapper( + ad_campaigns.retrieve, + ) + self.update = to_streamed_response_wrapper( + ad_campaigns.update, + ) + self.list = to_streamed_response_wrapper( + ad_campaigns.list, + ) + self.pause = to_streamed_response_wrapper( + ad_campaigns.pause, + ) + self.unpause = to_streamed_response_wrapper( + ad_campaigns.unpause, + ) + + +class AsyncAdCampaignsResourceWithStreamingResponse: + def __init__(self, ad_campaigns: AsyncAdCampaignsResource) -> None: + self._ad_campaigns = ad_campaigns + + self.create = async_to_streamed_response_wrapper( + ad_campaigns.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + ad_campaigns.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + ad_campaigns.update, + ) + self.list = async_to_streamed_response_wrapper( + ad_campaigns.list, + ) + self.pause = async_to_streamed_response_wrapper( + ad_campaigns.pause, + ) + self.unpause = async_to_streamed_response_wrapper( + ad_campaigns.unpause, + ) diff --git a/src/whop_sdk/resources/ad_groups.py b/src/whop_sdk/resources/ad_groups.py new file mode 100644 index 0000000..0eae840 --- /dev/null +++ b/src/whop_sdk/resources/ad_groups.py @@ -0,0 +1,762 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from datetime import datetime +from typing_extensions import Literal + +import httpx + +from ..types import ad_group_list_params, ad_group_create_params, ad_group_update_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..pagination import SyncCursorPage, AsyncCursorPage +from .._base_client import AsyncPaginator, make_request_options +from ..types.ad_group_list_response import AdGroupListResponse +from ..types.ad_group_create_response import AdGroupCreateResponse +from ..types.ad_group_delete_response import AdGroupDeleteResponse +from ..types.ad_group_update_response import AdGroupUpdateResponse +from ..types.ad_group_retrieve_response import AdGroupRetrieveResponse + +__all__ = ["AdGroupsResource", "AsyncAdGroupsResource"] + + +class AdGroupsResource(SyncAPIResource): + """Ad groups""" + + @cached_property + def with_raw_response(self) -> AdGroupsResourceWithRawResponse: + """ + 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/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return AdGroupsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AdGroupsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return AdGroupsResourceWithStreamingResponse(self) + + def create( + self, + *, + campaign_id: str, + budget: Optional[float] | Omit = omit, + budget_type: Optional[Literal["daily", "lifetime"]] | Omit = omit, + config: Optional[ad_group_create_params.Config] | Omit = omit, + daily_budget: Optional[float] | Omit = omit, + name: Optional[str] | Omit = omit, + platform_config: Optional[ad_group_create_params.PlatformConfig] | Omit = omit, + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] | 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, + ) -> AdGroupCreateResponse: + """ + Creates a new ad group within a campaign. + + Required permissions: + + - `ad_campaign:create` + - `ad_campaign:basic:read` + + Args: + campaign_id: The ad campaign to create this ad group within. + + budget: Budget amount in dollars. + + budget_type: The budget type for an ad campaign or ad group. + + config: Unified ad group configuration (bidding, optimization, targeting). + + daily_budget: Daily budget in dollars. + + name: Human-readable ad group name. + + platform_config: Platform-specific ad group configuration. + + status: The status of an external ad group. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/ad_groups", + body=maybe_transform( + { + "campaign_id": campaign_id, + "budget": budget, + "budget_type": budget_type, + "config": config, + "daily_budget": daily_budget, + "name": name, + "platform_config": platform_config, + "status": status, + }, + ad_group_create_params.AdGroupCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdGroupCreateResponse, + ) + + def retrieve( + self, + 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, + ) -> AdGroupRetrieveResponse: + """ + Retrieves a single ad group by its unique identifier. + + Required permissions: + + - `ad_campaign:basic:read` + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._get( + path_template("/ad_groups/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdGroupRetrieveResponse, + ) + + def update( + self, + id: str, + *, + budget: Optional[float] | Omit = omit, + budget_type: Optional[Literal["daily", "lifetime"]] | Omit = omit, + config: Optional[ad_group_update_params.Config] | Omit = omit, + daily_budget: Optional[float] | Omit = omit, + name: Optional[str] | Omit = omit, + platform_config: Optional[ad_group_update_params.PlatformConfig] | Omit = omit, + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] | 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, + ) -> AdGroupUpdateResponse: + """ + Updates an existing ad group. + + Required permissions: + + - `ad_campaign:update` + - `ad_campaign:basic:read` + + Args: + budget: Budget amount in dollars. + + budget_type: The budget type for an ad campaign or ad group. + + config: Unified ad group configuration (bidding, optimization, targeting). + + daily_budget: Daily budget in dollars. + + name: Human-readable ad group name. + + platform_config: Platform-specific ad group configuration. + + status: The status of an external ad group. + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._patch( + path_template("/ad_groups/{id}", id=id), + body=maybe_transform( + { + "budget": budget, + "budget_type": budget_type, + "config": config, + "daily_budget": daily_budget, + "name": name, + "platform_config": platform_config, + "status": status, + }, + ad_group_update_params.AdGroupUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdGroupUpdateResponse, + ) + + def list( + self, + *, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + campaign_id: Optional[str] | Omit = omit, + company_id: Optional[str] | Omit = omit, + created_after: Union[str, datetime, None] | Omit = omit, + created_before: Union[str, datetime, None] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + query: Optional[str] | Omit = omit, + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] | 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, + ) -> SyncCursorPage[AdGroupListResponse]: + """ + Returns a paginated list of ad groups scoped by campaign or company, with + optional filtering by status and creation date. + + Required permissions: + + - `ad_campaign:basic:read` + + Args: + after: Returns the elements in the list that come after the specified cursor. + + before: Returns the elements in the list that come before the specified cursor. + + campaign_id: Filter by campaign. Provide exactly one of campaign_id or company_id. + + company_id: Filter by company. Provide exactly one of campaign_id or company_id. + + created_after: Only return ad groups created after this timestamp. + + created_before: Only return ad groups created before this timestamp. + + first: Returns the first _n_ elements from the list. + + last: Returns the last _n_ elements from the list. + + query: Case-insensitive substring match against the ad group name. + + status: The status of an external ad group. + + 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_api_list( + "/ad_groups", + page=SyncCursorPage[AdGroupListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "before": before, + "campaign_id": campaign_id, + "company_id": company_id, + "created_after": created_after, + "created_before": created_before, + "first": first, + "last": last, + "query": query, + "status": status, + }, + ad_group_list_params.AdGroupListParams, + ), + ), + model=AdGroupListResponse, + ) + + def delete( + self, + 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, + ) -> AdGroupDeleteResponse: + """ + Soft-deletes an ad group. + + Required permissions: + + - `ad_campaign:delete` + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._delete( + path_template("/ad_groups/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdGroupDeleteResponse, + ) + + +class AsyncAdGroupsResource(AsyncAPIResource): + """Ad groups""" + + @cached_property + def with_raw_response(self) -> AsyncAdGroupsResourceWithRawResponse: + """ + 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/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return AsyncAdGroupsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncAdGroupsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return AsyncAdGroupsResourceWithStreamingResponse(self) + + async def create( + self, + *, + campaign_id: str, + budget: Optional[float] | Omit = omit, + budget_type: Optional[Literal["daily", "lifetime"]] | Omit = omit, + config: Optional[ad_group_create_params.Config] | Omit = omit, + daily_budget: Optional[float] | Omit = omit, + name: Optional[str] | Omit = omit, + platform_config: Optional[ad_group_create_params.PlatformConfig] | Omit = omit, + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] | 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, + ) -> AdGroupCreateResponse: + """ + Creates a new ad group within a campaign. + + Required permissions: + + - `ad_campaign:create` + - `ad_campaign:basic:read` + + Args: + campaign_id: The ad campaign to create this ad group within. + + budget: Budget amount in dollars. + + budget_type: The budget type for an ad campaign or ad group. + + config: Unified ad group configuration (bidding, optimization, targeting). + + daily_budget: Daily budget in dollars. + + name: Human-readable ad group name. + + platform_config: Platform-specific ad group configuration. + + status: The status of an external ad group. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/ad_groups", + body=await async_maybe_transform( + { + "campaign_id": campaign_id, + "budget": budget, + "budget_type": budget_type, + "config": config, + "daily_budget": daily_budget, + "name": name, + "platform_config": platform_config, + "status": status, + }, + ad_group_create_params.AdGroupCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdGroupCreateResponse, + ) + + async def retrieve( + self, + 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, + ) -> AdGroupRetrieveResponse: + """ + Retrieves a single ad group by its unique identifier. + + Required permissions: + + - `ad_campaign:basic:read` + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._get( + path_template("/ad_groups/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdGroupRetrieveResponse, + ) + + async def update( + self, + id: str, + *, + budget: Optional[float] | Omit = omit, + budget_type: Optional[Literal["daily", "lifetime"]] | Omit = omit, + config: Optional[ad_group_update_params.Config] | Omit = omit, + daily_budget: Optional[float] | Omit = omit, + name: Optional[str] | Omit = omit, + platform_config: Optional[ad_group_update_params.PlatformConfig] | Omit = omit, + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] | 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, + ) -> AdGroupUpdateResponse: + """ + Updates an existing ad group. + + Required permissions: + + - `ad_campaign:update` + - `ad_campaign:basic:read` + + Args: + budget: Budget amount in dollars. + + budget_type: The budget type for an ad campaign or ad group. + + config: Unified ad group configuration (bidding, optimization, targeting). + + daily_budget: Daily budget in dollars. + + name: Human-readable ad group name. + + platform_config: Platform-specific ad group configuration. + + status: The status of an external ad group. + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._patch( + path_template("/ad_groups/{id}", id=id), + body=await async_maybe_transform( + { + "budget": budget, + "budget_type": budget_type, + "config": config, + "daily_budget": daily_budget, + "name": name, + "platform_config": platform_config, + "status": status, + }, + ad_group_update_params.AdGroupUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdGroupUpdateResponse, + ) + + def list( + self, + *, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + campaign_id: Optional[str] | Omit = omit, + company_id: Optional[str] | Omit = omit, + created_after: Union[str, datetime, None] | Omit = omit, + created_before: Union[str, datetime, None] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + query: Optional[str] | Omit = omit, + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] | 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, + ) -> AsyncPaginator[AdGroupListResponse, AsyncCursorPage[AdGroupListResponse]]: + """ + Returns a paginated list of ad groups scoped by campaign or company, with + optional filtering by status and creation date. + + Required permissions: + + - `ad_campaign:basic:read` + + Args: + after: Returns the elements in the list that come after the specified cursor. + + before: Returns the elements in the list that come before the specified cursor. + + campaign_id: Filter by campaign. Provide exactly one of campaign_id or company_id. + + company_id: Filter by company. Provide exactly one of campaign_id or company_id. + + created_after: Only return ad groups created after this timestamp. + + created_before: Only return ad groups created before this timestamp. + + first: Returns the first _n_ elements from the list. + + last: Returns the last _n_ elements from the list. + + query: Case-insensitive substring match against the ad group name. + + status: The status of an external ad group. + + 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_api_list( + "/ad_groups", + page=AsyncCursorPage[AdGroupListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "before": before, + "campaign_id": campaign_id, + "company_id": company_id, + "created_after": created_after, + "created_before": created_before, + "first": first, + "last": last, + "query": query, + "status": status, + }, + ad_group_list_params.AdGroupListParams, + ), + ), + model=AdGroupListResponse, + ) + + async def delete( + self, + 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, + ) -> AdGroupDeleteResponse: + """ + Soft-deletes an ad group. + + Required permissions: + + - `ad_campaign:delete` + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._delete( + path_template("/ad_groups/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdGroupDeleteResponse, + ) + + +class AdGroupsResourceWithRawResponse: + def __init__(self, ad_groups: AdGroupsResource) -> None: + self._ad_groups = ad_groups + + self.create = to_raw_response_wrapper( + ad_groups.create, + ) + self.retrieve = to_raw_response_wrapper( + ad_groups.retrieve, + ) + self.update = to_raw_response_wrapper( + ad_groups.update, + ) + self.list = to_raw_response_wrapper( + ad_groups.list, + ) + self.delete = to_raw_response_wrapper( + ad_groups.delete, + ) + + +class AsyncAdGroupsResourceWithRawResponse: + def __init__(self, ad_groups: AsyncAdGroupsResource) -> None: + self._ad_groups = ad_groups + + self.create = async_to_raw_response_wrapper( + ad_groups.create, + ) + self.retrieve = async_to_raw_response_wrapper( + ad_groups.retrieve, + ) + self.update = async_to_raw_response_wrapper( + ad_groups.update, + ) + self.list = async_to_raw_response_wrapper( + ad_groups.list, + ) + self.delete = async_to_raw_response_wrapper( + ad_groups.delete, + ) + + +class AdGroupsResourceWithStreamingResponse: + def __init__(self, ad_groups: AdGroupsResource) -> None: + self._ad_groups = ad_groups + + self.create = to_streamed_response_wrapper( + ad_groups.create, + ) + self.retrieve = to_streamed_response_wrapper( + ad_groups.retrieve, + ) + self.update = to_streamed_response_wrapper( + ad_groups.update, + ) + self.list = to_streamed_response_wrapper( + ad_groups.list, + ) + self.delete = to_streamed_response_wrapper( + ad_groups.delete, + ) + + +class AsyncAdGroupsResourceWithStreamingResponse: + def __init__(self, ad_groups: AsyncAdGroupsResource) -> None: + self._ad_groups = ad_groups + + self.create = async_to_streamed_response_wrapper( + ad_groups.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + ad_groups.retrieve, + ) + self.update = async_to_streamed_response_wrapper( + ad_groups.update, + ) + self.list = async_to_streamed_response_wrapper( + ad_groups.list, + ) + self.delete = async_to_streamed_response_wrapper( + ad_groups.delete, + ) diff --git a/src/whop_sdk/resources/ads.py b/src/whop_sdk/resources/ads.py new file mode 100644 index 0000000..466957e --- /dev/null +++ b/src/whop_sdk/resources/ads.py @@ -0,0 +1,510 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from datetime import datetime +from typing_extensions import Literal + +import httpx + +from ..types import ad_list_params, ad_create_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..pagination import SyncCursorPage, AsyncCursorPage +from .._base_client import AsyncPaginator, make_request_options +from ..types.ad_list_response import AdListResponse +from ..types.ad_create_response import AdCreateResponse +from ..types.ad_retrieve_response import AdRetrieveResponse + +__all__ = ["AdsResource", "AsyncAdsResource"] + + +class AdsResource(SyncAPIResource): + """Ads""" + + @cached_property + def with_raw_response(self) -> AdsResourceWithRawResponse: + """ + 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/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return AdsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AdsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return AdsResourceWithStreamingResponse(self) + + def create( + self, + *, + ad_group_id: str, + creative_set_id: Optional[str] | Omit = omit, + existing_instagram_media_id: Optional[str] | Omit = omit, + existing_post_id: Optional[str] | Omit = omit, + platform_config: Optional[ad_create_params.PlatformConfig] | Omit = omit, + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] | 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, + ) -> AdCreateResponse: + """ + Create an ad within an ad group. + + Required permissions: + + - `ad_campaign:create` + + Args: + ad_group_id: The unique identifier of the ad group to create this ad in. + + creative_set_id: The unique identifier of the creative set to use. + + existing_instagram_media_id: ID of an existing Instagram media item to use as the ad creative (instead of a + creative set or Facebook post). + + existing_post_id: ID of an existing Facebook post to use as the ad creative (instead of a creative + set). + + platform_config: Platform-specific configuration. Must match the campaign platform. + + status: The status of an external ad. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/ads", + body=maybe_transform( + { + "ad_group_id": ad_group_id, + "creative_set_id": creative_set_id, + "existing_instagram_media_id": existing_instagram_media_id, + "existing_post_id": existing_post_id, + "platform_config": platform_config, + "status": status, + }, + ad_create_params.AdCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdCreateResponse, + ) + + def retrieve( + self, + 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, + ) -> AdRetrieveResponse: + """ + Retrieve an ad by its unique identifier. + + Required permissions: + + - `ad_campaign:basic:read` + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._get( + path_template("/ads/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdRetrieveResponse, + ) + + def list( + self, + *, + ad_group_id: Optional[str] | Omit = omit, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + campaign_id: Optional[str] | Omit = omit, + company_id: Optional[str] | Omit = omit, + created_after: Union[str, datetime, None] | Omit = omit, + created_before: Union[str, datetime, None] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] | 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, + ) -> SyncCursorPage[AdListResponse]: + """ + List ads scoped by ad group, campaign, or company. + + Required permissions: + + - `ad_campaign:basic:read` + + Args: + ad_group_id: Filter by ad group. Provide exactly one of ad_group_id, campaign_id, or + company_id. + + after: Returns the elements in the list that come after the specified cursor. + + before: Returns the elements in the list that come before the specified cursor. + + campaign_id: Filter by campaign. Provide exactly one of ad_group_id, campaign_id, or + company_id. + + company_id: Filter by company. Provide exactly one of ad_group_id, campaign_id, or + company_id. + + created_after: Only return ads created after this timestamp. + + created_before: Only return ads created before this timestamp. + + first: Returns the first _n_ elements from the list. + + last: Returns the last _n_ elements from the list. + + status: The status of an external ad. + + 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_api_list( + "/ads", + page=SyncCursorPage[AdListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "ad_group_id": ad_group_id, + "after": after, + "before": before, + "campaign_id": campaign_id, + "company_id": company_id, + "created_after": created_after, + "created_before": created_before, + "first": first, + "last": last, + "status": status, + }, + ad_list_params.AdListParams, + ), + ), + model=AdListResponse, + ) + + +class AsyncAdsResource(AsyncAPIResource): + """Ads""" + + @cached_property + def with_raw_response(self) -> AsyncAdsResourceWithRawResponse: + """ + 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/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return AsyncAdsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncAdsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return AsyncAdsResourceWithStreamingResponse(self) + + async def create( + self, + *, + ad_group_id: str, + creative_set_id: Optional[str] | Omit = omit, + existing_instagram_media_id: Optional[str] | Omit = omit, + existing_post_id: Optional[str] | Omit = omit, + platform_config: Optional[ad_create_params.PlatformConfig] | Omit = omit, + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] | 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, + ) -> AdCreateResponse: + """ + Create an ad within an ad group. + + Required permissions: + + - `ad_campaign:create` + + Args: + ad_group_id: The unique identifier of the ad group to create this ad in. + + creative_set_id: The unique identifier of the creative set to use. + + existing_instagram_media_id: ID of an existing Instagram media item to use as the ad creative (instead of a + creative set or Facebook post). + + existing_post_id: ID of an existing Facebook post to use as the ad creative (instead of a creative + set). + + platform_config: Platform-specific configuration. Must match the campaign platform. + + status: The status of an external ad. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/ads", + body=await async_maybe_transform( + { + "ad_group_id": ad_group_id, + "creative_set_id": creative_set_id, + "existing_instagram_media_id": existing_instagram_media_id, + "existing_post_id": existing_post_id, + "platform_config": platform_config, + "status": status, + }, + ad_create_params.AdCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdCreateResponse, + ) + + async def retrieve( + self, + 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, + ) -> AdRetrieveResponse: + """ + Retrieve an ad by its unique identifier. + + Required permissions: + + - `ad_campaign:basic:read` + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._get( + path_template("/ads/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AdRetrieveResponse, + ) + + def list( + self, + *, + ad_group_id: Optional[str] | Omit = omit, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + campaign_id: Optional[str] | Omit = omit, + company_id: Optional[str] | Omit = omit, + created_after: Union[str, datetime, None] | Omit = omit, + created_before: Union[str, datetime, None] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] | 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, + ) -> AsyncPaginator[AdListResponse, AsyncCursorPage[AdListResponse]]: + """ + List ads scoped by ad group, campaign, or company. + + Required permissions: + + - `ad_campaign:basic:read` + + Args: + ad_group_id: Filter by ad group. Provide exactly one of ad_group_id, campaign_id, or + company_id. + + after: Returns the elements in the list that come after the specified cursor. + + before: Returns the elements in the list that come before the specified cursor. + + campaign_id: Filter by campaign. Provide exactly one of ad_group_id, campaign_id, or + company_id. + + company_id: Filter by company. Provide exactly one of ad_group_id, campaign_id, or + company_id. + + created_after: Only return ads created after this timestamp. + + created_before: Only return ads created before this timestamp. + + first: Returns the first _n_ elements from the list. + + last: Returns the last _n_ elements from the list. + + status: The status of an external ad. + + 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_api_list( + "/ads", + page=AsyncCursorPage[AdListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "ad_group_id": ad_group_id, + "after": after, + "before": before, + "campaign_id": campaign_id, + "company_id": company_id, + "created_after": created_after, + "created_before": created_before, + "first": first, + "last": last, + "status": status, + }, + ad_list_params.AdListParams, + ), + ), + model=AdListResponse, + ) + + +class AdsResourceWithRawResponse: + def __init__(self, ads: AdsResource) -> None: + self._ads = ads + + self.create = to_raw_response_wrapper( + ads.create, + ) + self.retrieve = to_raw_response_wrapper( + ads.retrieve, + ) + self.list = to_raw_response_wrapper( + ads.list, + ) + + +class AsyncAdsResourceWithRawResponse: + def __init__(self, ads: AsyncAdsResource) -> None: + self._ads = ads + + self.create = async_to_raw_response_wrapper( + ads.create, + ) + self.retrieve = async_to_raw_response_wrapper( + ads.retrieve, + ) + self.list = async_to_raw_response_wrapper( + ads.list, + ) + + +class AdsResourceWithStreamingResponse: + def __init__(self, ads: AdsResource) -> None: + self._ads = ads + + self.create = to_streamed_response_wrapper( + ads.create, + ) + self.retrieve = to_streamed_response_wrapper( + ads.retrieve, + ) + self.list = to_streamed_response_wrapper( + ads.list, + ) + + +class AsyncAdsResourceWithStreamingResponse: + def __init__(self, ads: AsyncAdsResource) -> None: + self._ads = ads + + self.create = async_to_streamed_response_wrapper( + ads.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + ads.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + ads.list, + ) diff --git a/src/whop_sdk/resources/bounties.py b/src/whop_sdk/resources/bounties.py new file mode 100644 index 0000000..53dc898 --- /dev/null +++ b/src/whop_sdk/resources/bounties.py @@ -0,0 +1,495 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal + +import httpx + +from ..types import bounty_list_params, bounty_create_params +from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from .._utils import path_template, maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..pagination import SyncCursorPage, AsyncCursorPage +from .._base_client import AsyncPaginator, make_request_options +from ..types.shared.currency import Currency +from ..types.shared.direction import Direction +from ..types.bounty_list_response import BountyListResponse +from ..types.bounty_create_response import BountyCreateResponse +from ..types.bounty_retrieve_response import BountyRetrieveResponse + +__all__ = ["BountiesResource", "AsyncBountiesResource"] + + +class BountiesResource(SyncAPIResource): + """Bounties""" + + @cached_property + def with_raw_response(self) -> BountiesResourceWithRawResponse: + """ + 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/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return BountiesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> BountiesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return BountiesResourceWithStreamingResponse(self) + + def create( + self, + *, + base_unit_amount: float, + currency: Currency, + description: str, + title: str, + accepted_submissions_limit: Optional[int] | Omit = omit, + allowed_country_codes: Optional[SequenceNotStr[str]] | Omit = omit, + experience_id: Optional[str] | Omit = omit, + origin_account_id: Optional[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, + ) -> BountyCreateResponse: + """ + Create a new workforce bounty by funding a dedicated bounty pool. + + Required permissions: + + - `bounty:create` + + Args: + base_unit_amount: The amount paid to each approved submission. The total bounty pool funded is + this amount times accepted_submissions_limit. + + currency: The currency for the bounty pool funding amount. + + description: The description of the bounty. + + title: The title of the bounty. + + accepted_submissions_limit: The number of submissions that can be approved before the bounty closes. + Defaults to 1. + + allowed_country_codes: The ISO3166 country codes where this bounty should be visible. Empty means + globally visible. + + experience_id: An optional experience to scope the bounty to. + + origin_account_id: The user (user*\\**) or company (biz*\\**) tag whose balance funds this bounty pool. + Defaults to the requester's personal balance when omitted. The requester must be + the user themself or an owner/admin of the company. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/bounties", + body=maybe_transform( + { + "base_unit_amount": base_unit_amount, + "currency": currency, + "description": description, + "title": title, + "accepted_submissions_limit": accepted_submissions_limit, + "allowed_country_codes": allowed_country_codes, + "experience_id": experience_id, + "origin_account_id": origin_account_id, + }, + bounty_create_params.BountyCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BountyCreateResponse, + ) + + def retrieve( + self, + 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, + ) -> BountyRetrieveResponse: + """ + Retrieves a workforce bounty for the current authenticated user. + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._get( + path_template("/bounties/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BountyRetrieveResponse, + ) + + def list( + self, + *, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + direction: Optional[Direction] | Omit = omit, + experience_id: Optional[str] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + status: Optional[Literal["published", "archived"]] | 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, + ) -> SyncCursorPage[BountyListResponse]: + """Returns a paginated list of workforce bounties. + + When experienceId is provided, + returns bounties scoped to that experience. When omitted, returns bounties with + no experience. + + Args: + after: Returns the elements in the list that come after the specified cursor. + + before: Returns the elements in the list that come before the specified cursor. + + direction: The direction of the sort. + + experience_id: The experience to list bounties for. When omitted, returns bounties with no + experience. + + first: Returns the first _n_ elements from the list. + + last: Returns the last _n_ elements from the list. + + status: The available bounty statuses to choose from. + + 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_api_list( + "/bounties", + page=SyncCursorPage[BountyListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "before": before, + "direction": direction, + "experience_id": experience_id, + "first": first, + "last": last, + "status": status, + }, + bounty_list_params.BountyListParams, + ), + ), + model=BountyListResponse, + ) + + +class AsyncBountiesResource(AsyncAPIResource): + """Bounties""" + + @cached_property + def with_raw_response(self) -> AsyncBountiesResourceWithRawResponse: + """ + 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/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return AsyncBountiesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncBountiesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return AsyncBountiesResourceWithStreamingResponse(self) + + async def create( + self, + *, + base_unit_amount: float, + currency: Currency, + description: str, + title: str, + accepted_submissions_limit: Optional[int] | Omit = omit, + allowed_country_codes: Optional[SequenceNotStr[str]] | Omit = omit, + experience_id: Optional[str] | Omit = omit, + origin_account_id: Optional[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, + ) -> BountyCreateResponse: + """ + Create a new workforce bounty by funding a dedicated bounty pool. + + Required permissions: + + - `bounty:create` + + Args: + base_unit_amount: The amount paid to each approved submission. The total bounty pool funded is + this amount times accepted_submissions_limit. + + currency: The currency for the bounty pool funding amount. + + description: The description of the bounty. + + title: The title of the bounty. + + accepted_submissions_limit: The number of submissions that can be approved before the bounty closes. + Defaults to 1. + + allowed_country_codes: The ISO3166 country codes where this bounty should be visible. Empty means + globally visible. + + experience_id: An optional experience to scope the bounty to. + + origin_account_id: The user (user*\\**) or company (biz*\\**) tag whose balance funds this bounty pool. + Defaults to the requester's personal balance when omitted. The requester must be + the user themself or an owner/admin of the company. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/bounties", + body=await async_maybe_transform( + { + "base_unit_amount": base_unit_amount, + "currency": currency, + "description": description, + "title": title, + "accepted_submissions_limit": accepted_submissions_limit, + "allowed_country_codes": allowed_country_codes, + "experience_id": experience_id, + "origin_account_id": origin_account_id, + }, + bounty_create_params.BountyCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BountyCreateResponse, + ) + + async def retrieve( + self, + 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, + ) -> BountyRetrieveResponse: + """ + Retrieves a workforce bounty for the current authenticated user. + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._get( + path_template("/bounties/{id}", id=id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BountyRetrieveResponse, + ) + + def list( + self, + *, + after: Optional[str] | Omit = omit, + before: Optional[str] | Omit = omit, + direction: Optional[Direction] | Omit = omit, + experience_id: Optional[str] | Omit = omit, + first: Optional[int] | Omit = omit, + last: Optional[int] | Omit = omit, + status: Optional[Literal["published", "archived"]] | 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, + ) -> AsyncPaginator[BountyListResponse, AsyncCursorPage[BountyListResponse]]: + """Returns a paginated list of workforce bounties. + + When experienceId is provided, + returns bounties scoped to that experience. When omitted, returns bounties with + no experience. + + Args: + after: Returns the elements in the list that come after the specified cursor. + + before: Returns the elements in the list that come before the specified cursor. + + direction: The direction of the sort. + + experience_id: The experience to list bounties for. When omitted, returns bounties with no + experience. + + first: Returns the first _n_ elements from the list. + + last: Returns the last _n_ elements from the list. + + status: The available bounty statuses to choose from. + + 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_api_list( + "/bounties", + page=AsyncCursorPage[BountyListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "after": after, + "before": before, + "direction": direction, + "experience_id": experience_id, + "first": first, + "last": last, + "status": status, + }, + bounty_list_params.BountyListParams, + ), + ), + model=BountyListResponse, + ) + + +class BountiesResourceWithRawResponse: + def __init__(self, bounties: BountiesResource) -> None: + self._bounties = bounties + + self.create = to_raw_response_wrapper( + bounties.create, + ) + self.retrieve = to_raw_response_wrapper( + bounties.retrieve, + ) + self.list = to_raw_response_wrapper( + bounties.list, + ) + + +class AsyncBountiesResourceWithRawResponse: + def __init__(self, bounties: AsyncBountiesResource) -> None: + self._bounties = bounties + + self.create = async_to_raw_response_wrapper( + bounties.create, + ) + self.retrieve = async_to_raw_response_wrapper( + bounties.retrieve, + ) + self.list = async_to_raw_response_wrapper( + bounties.list, + ) + + +class BountiesResourceWithStreamingResponse: + def __init__(self, bounties: BountiesResource) -> None: + self._bounties = bounties + + self.create = to_streamed_response_wrapper( + bounties.create, + ) + self.retrieve = to_streamed_response_wrapper( + bounties.retrieve, + ) + self.list = to_streamed_response_wrapper( + bounties.list, + ) + + +class AsyncBountiesResourceWithStreamingResponse: + def __init__(self, bounties: AsyncBountiesResource) -> None: + self._bounties = bounties + + self.create = async_to_streamed_response_wrapper( + bounties.create, + ) + self.retrieve = async_to_streamed_response_wrapper( + bounties.retrieve, + ) + self.list = async_to_streamed_response_wrapper( + bounties.list, + ) diff --git a/src/whop_sdk/resources/companies.py b/src/whop_sdk/resources/companies.py index 3850775..a8a824d 100644 --- a/src/whop_sdk/resources/companies.py +++ b/src/whop_sdk/resources/companies.py @@ -4,10 +4,16 @@ from typing import Dict, Union, Optional from datetime import datetime +from typing_extensions import Literal import httpx -from ..types import company_list_params, company_create_params, company_update_params +from ..types import ( + company_list_params, + company_create_params, + company_update_params, + company_create_api_key_params, +) from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from .._utils import path_template, maybe_transform, async_maybe_transform from .._compat import cached_property @@ -23,6 +29,7 @@ from ..types.shared.company import Company from ..types.shared.direction import Direction from ..types.company_list_response import CompanyListResponse +from ..types.company_create_api_key_response import CompanyCreateAPIKeyResponse __all__ = ["CompaniesResource", "AsyncCompaniesResource"] @@ -330,6 +337,63 @@ def list( model=CompanyListResponse, ) + def create_api_key( + self, + parent_company_id: str, + *, + child_company_id: str, + name: Optional[str] | Omit = omit, + permissions: Optional[Iterable[company_create_api_key_params.Permission]] | Omit = omit, + role: Optional[Literal["owner", "admin", "moderator", "sales_manager", "advertiser"]] | 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, + ) -> CompanyCreateAPIKeyResponse: + """ + Create an API key for a connected account (child company) owned by a parent + company. + + Args: + child_company_id: The unique identifier of the connected account to create the API key for (e.g. + 'biz_xxx'). + + name: A human-readable name for the API key, such as 'Production API Key'. + + permissions: Granular permission statements defining which actions this API key can perform. + Either permissions or role must be provided. + + role: The different system roles that can be assigned. + + 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 parent_company_id: + raise ValueError(f"Expected a non-empty value for `parent_company_id` but received {parent_company_id!r}") + return self._post( + path_template("/companies/{parent_company_id}/api_keys", parent_company_id=parent_company_id), + body=maybe_transform( + { + "child_company_id": child_company_id, + "name": name, + "permissions": permissions, + "role": role, + }, + company_create_api_key_params.CompanyCreateAPIKeyParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CompanyCreateAPIKeyResponse, + ) + class AsyncCompaniesResource(AsyncAPIResource): @cached_property @@ -634,6 +698,63 @@ def list( model=CompanyListResponse, ) + async def create_api_key( + self, + parent_company_id: str, + *, + child_company_id: str, + name: Optional[str] | Omit = omit, + permissions: Optional[Iterable[company_create_api_key_params.Permission]] | Omit = omit, + role: Optional[Literal["owner", "admin", "moderator", "sales_manager", "advertiser"]] | 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, + ) -> CompanyCreateAPIKeyResponse: + """ + Create an API key for a connected account (child company) owned by a parent + company. + + Args: + child_company_id: The unique identifier of the connected account to create the API key for (e.g. + 'biz_xxx'). + + name: A human-readable name for the API key, such as 'Production API Key'. + + permissions: Granular permission statements defining which actions this API key can perform. + Either permissions or role must be provided. + + role: The different system roles that can be assigned. + + 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 parent_company_id: + raise ValueError(f"Expected a non-empty value for `parent_company_id` but received {parent_company_id!r}") + return await self._post( + path_template("/companies/{parent_company_id}/api_keys", parent_company_id=parent_company_id), + body=await async_maybe_transform( + { + "child_company_id": child_company_id, + "name": name, + "permissions": permissions, + "role": role, + }, + company_create_api_key_params.CompanyCreateAPIKeyParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=CompanyCreateAPIKeyResponse, + ) + class CompaniesResourceWithRawResponse: def __init__(self, companies: CompaniesResource) -> None: @@ -651,6 +772,9 @@ def __init__(self, companies: CompaniesResource) -> None: self.list = to_raw_response_wrapper( companies.list, ) + self.create_api_key = to_raw_response_wrapper( + companies.create_api_key, + ) class AsyncCompaniesResourceWithRawResponse: @@ -669,6 +793,9 @@ def __init__(self, companies: AsyncCompaniesResource) -> None: self.list = async_to_raw_response_wrapper( companies.list, ) + self.create_api_key = async_to_raw_response_wrapper( + companies.create_api_key, + ) class CompaniesResourceWithStreamingResponse: @@ -687,6 +814,9 @@ def __init__(self, companies: CompaniesResource) -> None: self.list = to_streamed_response_wrapper( companies.list, ) + self.create_api_key = to_streamed_response_wrapper( + companies.create_api_key, + ) class AsyncCompaniesResourceWithStreamingResponse: @@ -705,3 +835,6 @@ def __init__(self, companies: AsyncCompaniesResource) -> None: self.list = async_to_streamed_response_wrapper( companies.list, ) + self.create_api_key = async_to_streamed_response_wrapper( + companies.create_api_key, + ) diff --git a/src/whop_sdk/resources/conversions.py b/src/whop_sdk/resources/conversions.py new file mode 100644 index 0000000..a3dd625 --- /dev/null +++ b/src/whop_sdk/resources/conversions.py @@ -0,0 +1,324 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from datetime import datetime +from typing_extensions import Literal + +import httpx + +from ..types import conversion_create_params +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from .._utils import maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from .._base_client import make_request_options +from ..types.shared.currency import Currency +from ..types.conversion_create_response import ConversionCreateResponse + +__all__ = ["ConversionsResource", "AsyncConversionsResource"] + + +class ConversionsResource(SyncAPIResource): + """Conversions""" + + @cached_property + def with_raw_response(self) -> ConversionsResourceWithRawResponse: + """ + 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/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return ConversionsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ConversionsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return ConversionsResourceWithStreamingResponse(self) + + def create( + self, + *, + company_id: str, + event_name: Literal["lead", "submit_application", "contact", "complete_registration", "schedule", "custom"], + action_source: Optional[ + Literal[ + "email", + "website", + "app", + "phone_call", + "chat", + "physical_store", + "system_generated", + "business_messaging", + "other", + ] + ] + | Omit = omit, + context: Optional[conversion_create_params.Context] | Omit = omit, + currency: Optional[Currency] | Omit = omit, + custom_name: Optional[str] | Omit = omit, + event_id: Optional[str] | Omit = omit, + event_time: Union[str, datetime, None] | Omit = omit, + plan_id: Optional[str] | Omit = omit, + product_id: Optional[str] | Omit = omit, + referrer_url: Optional[str] | Omit = omit, + url: Optional[str] | Omit = omit, + user: Optional[conversion_create_params.User] | Omit = omit, + value: Optional[float] | 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, + ) -> ConversionCreateResponse: + """ + Track a conversion or engagement event for a company. + + Required permissions: + + - `event:create` + + Args: + company_id: The company to associate with this event. + + event_name: The type of event. + + action_source: The channel where an event originated + + context: Tracking and attribution context. + + currency: The available currencies on the platform + + custom_name: Custom event name when event_name is 'custom'. + + event_id: Client-provided identifier for deduplication. Generated if omitted. + + event_time: When the event occurred. Defaults to now. + + plan_id: The plan associated with the event. + + product_id: The product associated with the event. + + referrer_url: The referring URL. + + url: The URL where the event occurred. + + user: User identity and profile data. + + value: Monetary value associated with the event. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/conversions", + body=maybe_transform( + { + "company_id": company_id, + "event_name": event_name, + "action_source": action_source, + "context": context, + "currency": currency, + "custom_name": custom_name, + "event_id": event_id, + "event_time": event_time, + "plan_id": plan_id, + "product_id": product_id, + "referrer_url": referrer_url, + "url": url, + "user": user, + "value": value, + }, + conversion_create_params.ConversionCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ConversionCreateResponse, + ) + + +class AsyncConversionsResource(AsyncAPIResource): + """Conversions""" + + @cached_property + def with_raw_response(self) -> AsyncConversionsResourceWithRawResponse: + """ + 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/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return AsyncConversionsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncConversionsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return AsyncConversionsResourceWithStreamingResponse(self) + + async def create( + self, + *, + company_id: str, + event_name: Literal["lead", "submit_application", "contact", "complete_registration", "schedule", "custom"], + action_source: Optional[ + Literal[ + "email", + "website", + "app", + "phone_call", + "chat", + "physical_store", + "system_generated", + "business_messaging", + "other", + ] + ] + | Omit = omit, + context: Optional[conversion_create_params.Context] | Omit = omit, + currency: Optional[Currency] | Omit = omit, + custom_name: Optional[str] | Omit = omit, + event_id: Optional[str] | Omit = omit, + event_time: Union[str, datetime, None] | Omit = omit, + plan_id: Optional[str] | Omit = omit, + product_id: Optional[str] | Omit = omit, + referrer_url: Optional[str] | Omit = omit, + url: Optional[str] | Omit = omit, + user: Optional[conversion_create_params.User] | Omit = omit, + value: Optional[float] | 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, + ) -> ConversionCreateResponse: + """ + Track a conversion or engagement event for a company. + + Required permissions: + + - `event:create` + + Args: + company_id: The company to associate with this event. + + event_name: The type of event. + + action_source: The channel where an event originated + + context: Tracking and attribution context. + + currency: The available currencies on the platform + + custom_name: Custom event name when event_name is 'custom'. + + event_id: Client-provided identifier for deduplication. Generated if omitted. + + event_time: When the event occurred. Defaults to now. + + plan_id: The plan associated with the event. + + product_id: The product associated with the event. + + referrer_url: The referring URL. + + url: The URL where the event occurred. + + user: User identity and profile data. + + value: Monetary value associated with the event. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/conversions", + body=await async_maybe_transform( + { + "company_id": company_id, + "event_name": event_name, + "action_source": action_source, + "context": context, + "currency": currency, + "custom_name": custom_name, + "event_id": event_id, + "event_time": event_time, + "plan_id": plan_id, + "product_id": product_id, + "referrer_url": referrer_url, + "url": url, + "user": user, + "value": value, + }, + conversion_create_params.ConversionCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ConversionCreateResponse, + ) + + +class ConversionsResourceWithRawResponse: + def __init__(self, conversions: ConversionsResource) -> None: + self._conversions = conversions + + self.create = to_raw_response_wrapper( + conversions.create, + ) + + +class AsyncConversionsResourceWithRawResponse: + def __init__(self, conversions: AsyncConversionsResource) -> None: + self._conversions = conversions + + self.create = async_to_raw_response_wrapper( + conversions.create, + ) + + +class ConversionsResourceWithStreamingResponse: + def __init__(self, conversions: ConversionsResource) -> None: + self._conversions = conversions + + self.create = to_streamed_response_wrapper( + conversions.create, + ) + + +class AsyncConversionsResourceWithStreamingResponse: + def __init__(self, conversions: AsyncConversionsResource) -> None: + self._conversions = conversions + + self.create = async_to_streamed_response_wrapper( + conversions.create, + ) diff --git a/src/whop_sdk/resources/stats.py b/src/whop_sdk/resources/stats.py new file mode 100644 index 0000000..c4b200f --- /dev/null +++ b/src/whop_sdk/resources/stats.py @@ -0,0 +1,760 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Any, Dict, Union, Optional, cast +from datetime import datetime + +import httpx + +from ..types import stat_run_sql_params, stat_describe_params, stat_query_raw_params, stat_query_metric_params +from .._types import Body, Omit, Query, Headers, NotGiven, SequenceNotStr, omit, not_given +from .._utils import maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from .._base_client import make_request_options +from ..types.shared.direction import Direction +from ..types.stat_run_sql_response import StatRunSqlResponse +from ..types.stat_describe_response import StatDescribeResponse +from ..types.stat_query_raw_response import StatQueryRawResponse +from ..types.stat_query_metric_response import StatQueryMetricResponse + +__all__ = ["StatsResource", "AsyncStatsResource"] + + +class StatsResource(SyncAPIResource): + """Stats""" + + @cached_property + def with_raw_response(self) -> StatsResourceWithRawResponse: + """ + 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/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return StatsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> StatsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return StatsResourceWithStreamingResponse(self) + + def describe( + self, + *, + company_id: Optional[str] | Omit = omit, + resource: Optional[str] | Omit = omit, + user_id: Optional[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, + ) -> StatDescribeResponse: + """Describe available stats schema. + + Without resource returns root nodes and + metrics. With resource returns node columns, associations, and available + metrics. + + Required permissions: + + - `stats:read` + + Args: + company_id: Scope query to a specific company. + + resource: Resource path using : as separator (e.g., 'receipts', 'payments:membership', + 'receipts:gross_revenue'). + + user_id: Scope query to a specific user. + + 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 cast( + StatDescribeResponse, + self._get( + "/stats/describe", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "company_id": company_id, + "resource": resource, + "user_id": user_id, + }, + stat_describe_params.StatDescribeParams, + ), + ), + cast_to=cast( + Any, StatDescribeResponse + ), # Union types cannot be passed in as arguments in the type system + ), + ) + + def query_metric( + self, + *, + resource: str, + breakdowns: Optional[SequenceNotStr[str]] | Omit = omit, + company_id: Optional[str] | Omit = omit, + filters: Optional[Dict[str, object]] | Omit = omit, + from_: Union[str, datetime, None] | Omit = omit, + granularity: Optional[str] | Omit = omit, + time_zone: Optional[str] | Omit = omit, + to: Union[str, datetime, None] | Omit = omit, + user_id: Optional[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, + ) -> StatQueryMetricResponse: + """Query an aggregated metric. + + Returns data grouped by period with optional + breakdowns. + + Required permissions: + + - `stats:read` + + Args: + resource: Metric resource using : as separator (e.g., 'receipts:gross_revenue', + 'members:new_users'). + + breakdowns: Columns to break down the metric by. + + company_id: Scope query to a specific company. + + filters: Key-value pairs to filter the data. + + from_: Start of time range (unix timestamp). + + granularity: Time granularity (daily, weekly, monthly). + + time_zone: IANA timezone for period bucketing (e.g. 'America/New_York'). Defaults to UTC. + Only applies to ClickHouse metrics. + + to: End of time range (unix timestamp). + + user_id: Scope query to a specific user. + + 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( + "/stats/metric", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "resource": resource, + "breakdowns": breakdowns, + "company_id": company_id, + "filters": filters, + "from_": from_, + "granularity": granularity, + "time_zone": time_zone, + "to": to, + "user_id": user_id, + }, + stat_query_metric_params.StatQueryMetricParams, + ), + ), + cast_to=StatQueryMetricResponse, + ) + + def query_raw( + self, + *, + resource: str, + company_id: Optional[str] | Omit = omit, + cursor: Optional[str] | Omit = omit, + from_: Union[str, datetime, None] | Omit = omit, + limit: Optional[int] | Omit = omit, + sort: Optional[str] | Omit = omit, + sort_direction: Optional[Direction] | Omit = omit, + to: Union[str, datetime, None] | Omit = omit, + user_id: Optional[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, + ) -> StatQueryRawResponse: + """Query raw data from a resource. + + Returns paginated rows with all columns. + + Required permissions: + + - `stats:read` + + Args: + resource: Resource path using : as separator (e.g., 'members', 'payments:membership'). + + company_id: Scope query to a specific company. + + cursor: Pagination cursor for next page. + + from_: Start of time range (unix timestamp). + + limit: Number of records to return (max 10000). + + sort: Column to sort by. + + sort_direction: The direction of the sort. + + to: End of time range (unix timestamp). + + user_id: Scope query to a specific user. + + 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( + "/stats/raw", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "resource": resource, + "company_id": company_id, + "cursor": cursor, + "from_": from_, + "limit": limit, + "sort": sort, + "sort_direction": sort_direction, + "to": to, + "user_id": user_id, + }, + stat_query_raw_params.StatQueryRawParams, + ), + ), + cast_to=StatQueryRawResponse, + ) + + def run_sql( + self, + *, + resource: str, + sql: str, + company_id: Optional[str] | Omit = omit, + cursor: Optional[str] | Omit = omit, + from_: Union[str, datetime, None] | Omit = omit, + limit: Optional[int] | Omit = omit, + sort: Optional[str] | Omit = omit, + sort_direction: Optional[Direction] | Omit = omit, + to: Union[str, datetime, None] | Omit = omit, + user_id: Optional[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, + ) -> StatRunSqlResponse: + """Run custom SQL against a scoped resource. + + Use SCOPED_DATA as the table name. + + Required permissions: + + - `stats:read` + + Args: + resource: Resource path using : as separator (e.g., 'receipts', 'payments:membership'). + + sql: SQL query. Use SCOPED_DATA as the table name. + + company_id: Scope query to a specific company. + + cursor: Pagination cursor for next page. + + from_: Start of time range (unix timestamp). + + limit: Number of records to return (max 10000). + + sort: Column to sort by. + + sort_direction: The direction of the sort. + + to: End of time range (unix timestamp). + + user_id: Scope query to a specific user. + + 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( + "/stats/sql", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "resource": resource, + "sql": sql, + "company_id": company_id, + "cursor": cursor, + "from_": from_, + "limit": limit, + "sort": sort, + "sort_direction": sort_direction, + "to": to, + "user_id": user_id, + }, + stat_run_sql_params.StatRunSqlParams, + ), + ), + cast_to=StatRunSqlResponse, + ) + + +class AsyncStatsResource(AsyncAPIResource): + """Stats""" + + @cached_property + def with_raw_response(self) -> AsyncStatsResourceWithRawResponse: + """ + 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/whopio/whopsdk-python#accessing-raw-response-data-eg-headers + """ + return AsyncStatsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncStatsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/whopio/whopsdk-python#with_streaming_response + """ + return AsyncStatsResourceWithStreamingResponse(self) + + async def describe( + self, + *, + company_id: Optional[str] | Omit = omit, + resource: Optional[str] | Omit = omit, + user_id: Optional[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, + ) -> StatDescribeResponse: + """Describe available stats schema. + + Without resource returns root nodes and + metrics. With resource returns node columns, associations, and available + metrics. + + Required permissions: + + - `stats:read` + + Args: + company_id: Scope query to a specific company. + + resource: Resource path using : as separator (e.g., 'receipts', 'payments:membership', + 'receipts:gross_revenue'). + + user_id: Scope query to a specific user. + + 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 cast( + StatDescribeResponse, + await self._get( + "/stats/describe", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "company_id": company_id, + "resource": resource, + "user_id": user_id, + }, + stat_describe_params.StatDescribeParams, + ), + ), + cast_to=cast( + Any, StatDescribeResponse + ), # Union types cannot be passed in as arguments in the type system + ), + ) + + async def query_metric( + self, + *, + resource: str, + breakdowns: Optional[SequenceNotStr[str]] | Omit = omit, + company_id: Optional[str] | Omit = omit, + filters: Optional[Dict[str, object]] | Omit = omit, + from_: Union[str, datetime, None] | Omit = omit, + granularity: Optional[str] | Omit = omit, + time_zone: Optional[str] | Omit = omit, + to: Union[str, datetime, None] | Omit = omit, + user_id: Optional[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, + ) -> StatQueryMetricResponse: + """Query an aggregated metric. + + Returns data grouped by period with optional + breakdowns. + + Required permissions: + + - `stats:read` + + Args: + resource: Metric resource using : as separator (e.g., 'receipts:gross_revenue', + 'members:new_users'). + + breakdowns: Columns to break down the metric by. + + company_id: Scope query to a specific company. + + filters: Key-value pairs to filter the data. + + from_: Start of time range (unix timestamp). + + granularity: Time granularity (daily, weekly, monthly). + + time_zone: IANA timezone for period bucketing (e.g. 'America/New_York'). Defaults to UTC. + Only applies to ClickHouse metrics. + + to: End of time range (unix timestamp). + + user_id: Scope query to a specific user. + + 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( + "/stats/metric", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "resource": resource, + "breakdowns": breakdowns, + "company_id": company_id, + "filters": filters, + "from_": from_, + "granularity": granularity, + "time_zone": time_zone, + "to": to, + "user_id": user_id, + }, + stat_query_metric_params.StatQueryMetricParams, + ), + ), + cast_to=StatQueryMetricResponse, + ) + + async def query_raw( + self, + *, + resource: str, + company_id: Optional[str] | Omit = omit, + cursor: Optional[str] | Omit = omit, + from_: Union[str, datetime, None] | Omit = omit, + limit: Optional[int] | Omit = omit, + sort: Optional[str] | Omit = omit, + sort_direction: Optional[Direction] | Omit = omit, + to: Union[str, datetime, None] | Omit = omit, + user_id: Optional[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, + ) -> StatQueryRawResponse: + """Query raw data from a resource. + + Returns paginated rows with all columns. + + Required permissions: + + - `stats:read` + + Args: + resource: Resource path using : as separator (e.g., 'members', 'payments:membership'). + + company_id: Scope query to a specific company. + + cursor: Pagination cursor for next page. + + from_: Start of time range (unix timestamp). + + limit: Number of records to return (max 10000). + + sort: Column to sort by. + + sort_direction: The direction of the sort. + + to: End of time range (unix timestamp). + + user_id: Scope query to a specific user. + + 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( + "/stats/raw", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "resource": resource, + "company_id": company_id, + "cursor": cursor, + "from_": from_, + "limit": limit, + "sort": sort, + "sort_direction": sort_direction, + "to": to, + "user_id": user_id, + }, + stat_query_raw_params.StatQueryRawParams, + ), + ), + cast_to=StatQueryRawResponse, + ) + + async def run_sql( + self, + *, + resource: str, + sql: str, + company_id: Optional[str] | Omit = omit, + cursor: Optional[str] | Omit = omit, + from_: Union[str, datetime, None] | Omit = omit, + limit: Optional[int] | Omit = omit, + sort: Optional[str] | Omit = omit, + sort_direction: Optional[Direction] | Omit = omit, + to: Union[str, datetime, None] | Omit = omit, + user_id: Optional[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, + ) -> StatRunSqlResponse: + """Run custom SQL against a scoped resource. + + Use SCOPED_DATA as the table name. + + Required permissions: + + - `stats:read` + + Args: + resource: Resource path using : as separator (e.g., 'receipts', 'payments:membership'). + + sql: SQL query. Use SCOPED_DATA as the table name. + + company_id: Scope query to a specific company. + + cursor: Pagination cursor for next page. + + from_: Start of time range (unix timestamp). + + limit: Number of records to return (max 10000). + + sort: Column to sort by. + + sort_direction: The direction of the sort. + + to: End of time range (unix timestamp). + + user_id: Scope query to a specific user. + + 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( + "/stats/sql", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "resource": resource, + "sql": sql, + "company_id": company_id, + "cursor": cursor, + "from_": from_, + "limit": limit, + "sort": sort, + "sort_direction": sort_direction, + "to": to, + "user_id": user_id, + }, + stat_run_sql_params.StatRunSqlParams, + ), + ), + cast_to=StatRunSqlResponse, + ) + + +class StatsResourceWithRawResponse: + def __init__(self, stats: StatsResource) -> None: + self._stats = stats + + self.describe = to_raw_response_wrapper( + stats.describe, + ) + self.query_metric = to_raw_response_wrapper( + stats.query_metric, + ) + self.query_raw = to_raw_response_wrapper( + stats.query_raw, + ) + self.run_sql = to_raw_response_wrapper( + stats.run_sql, + ) + + +class AsyncStatsResourceWithRawResponse: + def __init__(self, stats: AsyncStatsResource) -> None: + self._stats = stats + + self.describe = async_to_raw_response_wrapper( + stats.describe, + ) + self.query_metric = async_to_raw_response_wrapper( + stats.query_metric, + ) + self.query_raw = async_to_raw_response_wrapper( + stats.query_raw, + ) + self.run_sql = async_to_raw_response_wrapper( + stats.run_sql, + ) + + +class StatsResourceWithStreamingResponse: + def __init__(self, stats: StatsResource) -> None: + self._stats = stats + + self.describe = to_streamed_response_wrapper( + stats.describe, + ) + self.query_metric = to_streamed_response_wrapper( + stats.query_metric, + ) + self.query_raw = to_streamed_response_wrapper( + stats.query_raw, + ) + self.run_sql = to_streamed_response_wrapper( + stats.run_sql, + ) + + +class AsyncStatsResourceWithStreamingResponse: + def __init__(self, stats: AsyncStatsResource) -> None: + self._stats = stats + + self.describe = async_to_streamed_response_wrapper( + stats.describe, + ) + self.query_metric = async_to_streamed_response_wrapper( + stats.query_metric, + ) + self.query_raw = async_to_streamed_response_wrapper( + stats.query_raw, + ) + self.run_sql = async_to_streamed_response_wrapper( + stats.run_sql, + ) diff --git a/src/whop_sdk/types/__init__.py b/src/whop_sdk/types/__init__.py index c8683b5..3167ebe 100644 --- a/src/whop_sdk/types/__init__.py +++ b/src/whop_sdk/types/__init__.py @@ -87,6 +87,7 @@ from .review_status import ReviewStatus as ReviewStatus from .upload_status import UploadStatus as UploadStatus from .webhook_event import WebhookEvent as WebhookEvent +from .ad_list_params import AdListParams as AdListParams from .cancel_options import CancelOptions as CancelOptions from .checkout_modes import CheckoutModes as CheckoutModes from .checkout_shape import CheckoutShape as CheckoutShape @@ -96,6 +97,8 @@ from .authorized_user import AuthorizedUser as AuthorizedUser from .billing_reasons import BillingReasons as BillingReasons from .fee_markup_type import FeeMarkupType as FeeMarkupType +from .ad_create_params import AdCreateParams as AdCreateParams +from .ad_list_response import AdListResponse as AdListResponse from .dispute_statuses import DisputeStatuses as DisputeStatuses from .lead_list_params import LeadListParams as LeadListParams from .payment_provider import PaymentProvider as PaymentProvider @@ -109,6 +112,8 @@ from .promo_code_status import PromoCodeStatus as PromoCodeStatus from .withdrawal_speeds import WithdrawalSpeeds as WithdrawalSpeeds from .withdrawal_status import WithdrawalStatus as WithdrawalStatus +from .ad_create_response import AdCreateResponse as AdCreateResponse +from .bounty_list_params import BountyListParams as BountyListParams from .course_list_params import CourseListParams as CourseListParams from .dispute_alert_type import DisputeAlertType as DisputeAlertType from .file_create_params import FileCreateParams as FileCreateParams @@ -136,10 +141,15 @@ from .payment_list_params import PaymentListParams as PaymentListParams from .product_list_params import ProductListParams as ProductListParams from .setup_intent_status import SetupIntentStatus as SetupIntentStatus +from .stat_run_sql_params import StatRunSqlParams as StatRunSqlParams from .tax_identifier_type import TaxIdentifierType as TaxIdentifierType from .topup_create_params import TopupCreateParams as TopupCreateParams from .verification_status import VerificationStatus as VerificationStatus from .webhook_list_params import WebhookListParams as WebhookListParams +from .ad_group_list_params import AdGroupListParams as AdGroupListParams +from .ad_retrieve_response import AdRetrieveResponse as AdRetrieveResponse +from .bounty_create_params import BountyCreateParams as BountyCreateParams +from .bounty_list_response import BountyListResponse as BountyListResponse from .course_create_params import CourseCreateParams as CourseCreateParams from .course_list_response import CourseListResponse as CourseListResponse from .course_update_params import CourseUpdateParams as CourseUpdateParams @@ -153,6 +163,7 @@ from .review_list_response import ReviewListResponse as ReviewListResponse from .shipment_list_params import ShipmentListParams as ShipmentListParams from .social_link_websites import SocialLinkWebsites as SocialLinkWebsites +from .stat_describe_params import StatDescribeParams as StatDescribeParams from .transfer_list_params import TransferListParams as TransferListParams from .unwrap_webhook_event import UnwrapWebhookEvent as UnwrapWebhookEvent from .user_retrieve_params import UserRetrieveParams as UserRetrieveParams @@ -179,10 +190,16 @@ from .product_create_params import ProductCreateParams as ProductCreateParams from .product_update_params import ProductUpdateParams as ProductUpdateParams from .refund_reference_type import RefundReferenceType as RefundReferenceType +from .stat_query_raw_params import StatQueryRawParams as StatQueryRawParams +from .stat_run_sql_response import StatRunSqlResponse as StatRunSqlResponse from .topup_create_response import TopupCreateResponse as TopupCreateResponse from .webhook_create_params import WebhookCreateParams as WebhookCreateParams from .webhook_list_response import WebhookListResponse as WebhookListResponse from .webhook_update_params import WebhookUpdateParams as WebhookUpdateParams +from .ad_group_create_params import AdGroupCreateParams as AdGroupCreateParams +from .ad_group_list_response import AdGroupListResponse as AdGroupListResponse +from .ad_group_update_params import AdGroupUpdateParams as AdGroupUpdateParams +from .bounty_create_response import BountyCreateResponse as BountyCreateResponse from .course_delete_response import CourseDeleteResponse as CourseDeleteResponse from .dm_channel_list_params import DmChannelListParams as DmChannelListParams from .entry_approve_response import EntryApproveResponse as EntryApproveResponse @@ -197,9 +214,11 @@ from .reaction_list_response import ReactionListResponse as ReactionListResponse from .shipment_create_params import ShipmentCreateParams as ShipmentCreateParams from .shipment_list_response import ShipmentListResponse as ShipmentListResponse +from .stat_describe_response import StatDescribeResponse as StatDescribeResponse from .transfer_create_params import TransferCreateParams as TransferCreateParams from .transfer_list_response import TransferListResponse as TransferListResponse from .withdrawal_list_params import WithdrawalListParams as WithdrawalListParams +from .ad_campaign_list_params import AdCampaignListParams as AdCampaignListParams from .affiliate_create_params import AffiliateCreateParams as AffiliateCreateParams from .affiliate_list_response import AffiliateListResponse as AffiliateListResponse from .ai_chat_delete_response import AIChatDeleteResponse as AIChatDeleteResponse @@ -214,10 +233,16 @@ from .message_delete_response import MessageDeleteResponse as MessageDeleteResponse from .product_delete_response import ProductDeleteResponse as ProductDeleteResponse from .refund_reference_status import RefundReferenceStatus as RefundReferenceStatus +from .stat_query_raw_response import StatQueryRawResponse as StatQueryRawResponse from .verification_error_code import VerificationErrorCode as VerificationErrorCode from .webhook_create_response import WebhookCreateResponse as WebhookCreateResponse from .webhook_delete_response import WebhookDeleteResponse as WebhookDeleteResponse +from .ad_group_create_response import AdGroupCreateResponse as AdGroupCreateResponse +from .ad_group_delete_response import AdGroupDeleteResponse as AdGroupDeleteResponse +from .ad_group_update_response import AdGroupUpdateResponse as AdGroupUpdateResponse +from .bounty_retrieve_response import BountyRetrieveResponse as BountyRetrieveResponse from .chat_channel_list_params import ChatChannelListParams as ChatChannelListParams +from .conversion_create_params import ConversionCreateParams as ConversionCreateParams from .dm_channel_create_params import DmChannelCreateParams as DmChannelCreateParams from .dm_channel_list_response import DmChannelListResponse as DmChannelListResponse from .dm_channel_update_params import DmChannelUpdateParams as DmChannelUpdateParams @@ -243,9 +268,13 @@ from .refund_retrieve_response import RefundRetrieveResponse as RefundRetrieveResponse from .review_retrieve_response import ReviewRetrieveResponse as ReviewRetrieveResponse from .setup_intent_list_params import SetupIntentListParams as SetupIntentListParams +from .stat_query_metric_params import StatQueryMetricParams as StatQueryMetricParams from .verification_list_params import VerificationListParams as VerificationListParams from .withdrawal_create_params import WithdrawalCreateParams as WithdrawalCreateParams from .withdrawal_list_response import WithdrawalListResponse as WithdrawalListResponse +from .ad_campaign_create_params import AdCampaignCreateParams as AdCampaignCreateParams +from .ad_campaign_list_response import AdCampaignListResponse as AdCampaignListResponse +from .ad_campaign_update_params import AdCampaignUpdateParams as AdCampaignUpdateParams from .assessment_question_types import AssessmentQuestionTypes as AssessmentQuestionTypes from .company_token_transaction import CompanyTokenTransaction as CompanyTokenTransaction from .course_lesson_list_params import CourseLessonListParams as CourseLessonListParams @@ -254,9 +283,12 @@ from .payout_method_list_params import PayoutMethodListParams as PayoutMethodListParams from .access_token_create_params import AccessTokenCreateParams as AccessTokenCreateParams from .account_link_create_params import AccountLinkCreateParams as AccountLinkCreateParams +from .ad_campaign_pause_response import AdCampaignPauseResponse as AdCampaignPauseResponse +from .ad_group_retrieve_response import AdGroupRetrieveResponse as AdGroupRetrieveResponse from .affiliate_archive_response import AffiliateArchiveResponse as AffiliateArchiveResponse from .chat_channel_list_response import ChatChannelListResponse as ChatChannelListResponse from .chat_channel_update_params import ChatChannelUpdateParams as ChatChannelUpdateParams +from .conversion_create_response import ConversionCreateResponse as ConversionCreateResponse from .course_chapter_list_params import CourseChapterListParams as CourseChapterListParams from .course_student_list_params import CourseStudentListParams as CourseStudentListParams from .dm_channel_delete_response import DmChannelDeleteResponse as DmChannelDeleteResponse @@ -272,8 +304,11 @@ from .payment_method_list_params import PaymentMethodListParams as PaymentMethodListParams from .promo_code_delete_response import PromoCodeDeleteResponse as PromoCodeDeleteResponse from .setup_intent_list_response import SetupIntentListResponse as SetupIntentListResponse +from .stat_query_metric_response import StatQueryMetricResponse as StatQueryMetricResponse from .user_check_access_response import UserCheckAccessResponse as UserCheckAccessResponse from .verification_list_response import VerificationListResponse as VerificationListResponse +from .ad_campaign_create_response import AdCampaignCreateResponse as AdCampaignCreateResponse +from .ad_campaign_update_response import AdCampaignUpdateResponse as AdCampaignUpdateResponse from .authorized_user_list_params import AuthorizedUserListParams as AuthorizedUserListParams from .course_lesson_create_params import CourseLessonCreateParams as CourseLessonCreateParams from .course_lesson_list_response import CourseLessonListResponse as CourseLessonListResponse @@ -287,6 +322,7 @@ from .support_channel_list_params import SupportChannelListParams as SupportChannelListParams from .access_token_create_response import AccessTokenCreateResponse as AccessTokenCreateResponse from .account_link_create_response import AccountLinkCreateResponse as AccountLinkCreateResponse +from .ad_campaign_unpause_response import AdCampaignUnpauseResponse as AdCampaignUnpauseResponse from .affiliate_unarchive_response import AffiliateUnarchiveResponse as AffiliateUnarchiveResponse from .course_chapter_create_params import CourseChapterCreateParams as CourseChapterCreateParams from .course_chapter_list_response import CourseChapterListResponse as CourseChapterListResponse @@ -300,9 +336,11 @@ from .payment_method_list_response import PaymentMethodListResponse as PaymentMethodListResponse from .refund_created_webhook_event import RefundCreatedWebhookEvent as RefundCreatedWebhookEvent from .refund_updated_webhook_event import RefundUpdatedWebhookEvent as RefundUpdatedWebhookEvent +from .ad_campaign_retrieve_response import AdCampaignRetrieveResponse as AdCampaignRetrieveResponse from .authorized_user_create_params import AuthorizedUserCreateParams as AuthorizedUserCreateParams from .authorized_user_delete_params import AuthorizedUserDeleteParams as AuthorizedUserDeleteParams from .authorized_user_list_response import AuthorizedUserListResponse as AuthorizedUserListResponse +from .company_create_api_key_params import CompanyCreateAPIKeyParams as CompanyCreateAPIKeyParams from .course_lesson_delete_response import CourseLessonDeleteResponse as CourseLessonDeleteResponse from .dispute_created_webhook_event import DisputeCreatedWebhookEvent as DisputeCreatedWebhookEvent from .dispute_updated_webhook_event import DisputeUpdatedWebhookEvent as DisputeUpdatedWebhookEvent @@ -319,6 +357,7 @@ from .payment_method_retrieve_params import PaymentMethodRetrieveParams as PaymentMethodRetrieveParams from .verification_retrieve_response import VerificationRetrieveResponse as VerificationRetrieveResponse from .authorized_user_delete_response import AuthorizedUserDeleteResponse as AuthorizedUserDeleteResponse +from .company_create_api_key_response import CompanyCreateAPIKeyResponse as CompanyCreateAPIKeyResponse from .dispute_alert_retrieve_response import DisputeAlertRetrieveResponse as DisputeAlertRetrieveResponse from .membership_add_free_days_params import MembershipAddFreeDaysParams as MembershipAddFreeDaysParams from .payment_succeeded_webhook_event import PaymentSucceededWebhookEvent as PaymentSucceededWebhookEvent diff --git a/src/whop_sdk/types/ad_campaign_create_params.py b/src/whop_sdk/types/ad_campaign_create_params.py new file mode 100644 index 0000000..061ab6d --- /dev/null +++ b/src/whop_sdk/types/ad_campaign_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 Optional +from typing_extensions import Literal, Required, TypedDict + +from .._types import SequenceNotStr + +__all__ = ["AdCampaignCreateParams", "Config"] + + +class AdCampaignCreateParams(TypedDict, total=False): + company_id: Required[str] + """The company ID to create this ad campaign for.""" + + config: Required[Config] + """Unified campaign configuration (conversion goal, budget, bidding, etc.).""" + + platform: Required[Literal["meta", "tiktok"]] + """The ad platform to run on (e.g., meta, tiktok).""" + + title: Required[str] + """The title of the ad campaign. Must be max 100 characters.""" + + ad_creative_set_ids: Optional[SequenceNotStr[str]] + """Array of creative set IDs to link to this campaign.""" + + budget: Optional[float] + """Budget amount in dollars.""" + + budget_type: Optional[Literal["daily", "lifetime"]] + """The budget type for an ad campaign or ad group.""" + + daily_budget: Optional[float] + """Daily budget in dollars (minimum $5). + + Required unless lifetime_budget is set in config. + """ + + product_id: Optional[str] + """The unique identifier of the product to promote.""" + + target_country_codes: Optional[SequenceNotStr[str]] + """Array of ISO3166 country codes for territory targeting.""" + + +class Config(TypedDict, total=False): + """Unified campaign configuration (conversion goal, budget, bidding, etc.).""" + + bid_amount: Optional[int] + """Bid cap amount in cents. Only used when bid_strategy is bid_cap.""" + + bid_strategy: Optional[Literal["lowest_cost", "bid_cap", "cost_cap"]] + """The bidding strategy used to optimize spend for this campaign.""" + + budget_optimization: Optional[bool] + """ + Whether campaign budget optimization (CBO) is enabled, allowing the platform to + distribute budget across ad groups. + """ + + end_time: Optional[str] + """The scheduled end time of the campaign (ISO8601).""" + + lifetime_budget: Optional[int] + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + objective: Optional[Literal["awareness", "traffic", "engagement", "leads", "sales"]] + """The campaign objective that determines how Meta optimizes delivery.""" + + special_categories: Optional[SequenceNotStr[str]] + """ + Special ad categories required by the platform (e.g., housing, employment, + credit). + """ + + start_time: Optional[str] + """The scheduled start time of the campaign (ISO8601).""" + + status: Optional[Literal["active", "paused"]] + """The campaign status as set by the advertiser (active or paused).""" diff --git a/src/whop_sdk/types/ad_campaign_create_response.py b/src/whop_sdk/types/ad_campaign_create_response.py new file mode 100644 index 0000000..b269877 --- /dev/null +++ b/src/whop_sdk/types/ad_campaign_create_response.py @@ -0,0 +1,509 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel +from .card_brands import CardBrands +from .payment_method_types import PaymentMethodTypes + +__all__ = [ + "AdCampaignCreateResponse", + "BillingLedgerAccount", + "Config", + "CreatedByUser", + "PaymentMethod", + "PaymentMethodBasePaymentMethod", + "PaymentMethodCardPaymentMethod", + "PaymentMethodCardPaymentMethodCard", + "PaymentMethodUsBankAccountPaymentMethod", + "PaymentMethodUsBankAccountPaymentMethodUsBankAccount", + "PaymentMethodCashappPaymentMethod", + "PaymentMethodCashappPaymentMethodCashapp", + "PaymentMethodIdealPaymentMethod", + "PaymentMethodIdealPaymentMethodIdeal", + "PaymentMethodSepaDebitPaymentMethod", + "PaymentMethodSepaDebitPaymentMethodSepaDebit", + "Product", +] + + +class BillingLedgerAccount(BaseModel): + """ + The ledger account being charged for platform balance billing (null if using card) + """ + + id: str + """The unique identifier for the ledger account.""" + + +class Config(BaseModel): + """Meta campaign configuration (objective, budget, bidding, etc.). + + Null for non-Meta campaigns — use `tiktokConfig` for TikTok. + """ + + bid_amount: Optional[int] = None + """Bid cap amount in cents. Only used when bid_strategy is bid_cap.""" + + bid_strategy: Optional[Literal["lowest_cost", "bid_cap", "cost_cap"]] = None + """The bidding strategy used to optimize spend for this campaign.""" + + budget_optimization: Optional[bool] = None + """ + Whether campaign budget optimization (CBO) is enabled, allowing the platform to + distribute budget across ad groups. + """ + + end_time: Optional[str] = None + """The scheduled end time of the campaign (ISO8601).""" + + objective: Optional[Literal["awareness", "traffic", "engagement", "leads", "sales"]] = None + """The campaign objective that determines how Meta optimizes delivery.""" + + special_categories: Optional[List[str]] = None + """ + Special ad categories required by the platform (e.g., housing, employment, + credit). + """ + + start_time: Optional[str] = None + """The scheduled start time of the campaign (ISO8601).""" + + status: Optional[Literal["active", "paused"]] = None + """The campaign status as set by the advertiser (active or paused).""" + + +class CreatedByUser(BaseModel): + """The user who created the campaign""" + + id: str + """The unique identifier for the user.""" + + name: Optional[str] = None + """The user's display name shown on their public profile.""" + + username: str + """The user's unique username shown on their public profile.""" + + +class PaymentMethodBasePaymentMethod(BaseModel): + """A saved payment method with no type-specific details available.""" + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["BasePaymentMethod"] + """The typename of this object""" + + +class PaymentMethodCardPaymentMethodCard(BaseModel): + """ + The card-specific details for this payment method, including brand, last four digits, and expiration. + """ + + brand: Optional[CardBrands] = None + """Possible card brands that a payment token can have""" + + exp_month: Optional[int] = None + """The two-digit expiration month of the card (1-12). Null if not available.""" + + exp_year: Optional[int] = None + """The two-digit expiration year of the card (e.g., 27 for 2027). + + Null if not available. + """ + + last4: Optional[str] = None + """The last four digits of the card number. Null if not available.""" + + +class PaymentMethodCardPaymentMethod(BaseModel): + """ + A saved card payment method, including brand, last four digits, and expiration details. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + card: PaymentMethodCardPaymentMethodCard + """ + The card-specific details for this payment method, including brand, last four + digits, and expiration. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["CardPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodUsBankAccountPaymentMethodUsBankAccount(BaseModel): + """ + The bank account-specific details for this payment method, including bank name and last four digits. + """ + + account_type: str + """The type of bank account (e.g., checking, savings).""" + + bank_name: str + """The name of the financial institution holding the account.""" + + last4: str + """The last four digits of the bank account number.""" + + +class PaymentMethodUsBankAccountPaymentMethod(BaseModel): + """ + A saved US bank account payment method, including bank name, last four digits, and account type. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["UsBankAccountPaymentMethod"] + """The typename of this object""" + + us_bank_account: PaymentMethodUsBankAccountPaymentMethodUsBankAccount + """ + The bank account-specific details for this payment method, including bank name + and last four digits. + """ + + +class PaymentMethodCashappPaymentMethodCashapp(BaseModel): + """ + The Cash App-specific details for this payment method, including cashtag and buyer ID. + """ + + buyer_id: Optional[str] = None + """The unique and immutable identifier assigned by Cash App to the buyer. + + Null if not available. + """ + + cashtag: Optional[str] = None + """The public cashtag handle of the buyer on Cash App. Null if not available.""" + + +class PaymentMethodCashappPaymentMethod(BaseModel): + """ + A saved Cash App payment method, including the buyer's cashtag and unique identifier. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + cashapp: PaymentMethodCashappPaymentMethodCashapp + """ + The Cash App-specific details for this payment method, including cashtag and + buyer ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["CashappPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodIdealPaymentMethodIdeal(BaseModel): + """ + The iDEAL-specific details for this payment method, including bank name and BIC. + """ + + bank: Optional[str] = None + """The name of the customer's bank used for the iDEAL transaction. + + Null if not available. + """ + + bic: Optional[str] = None + """The Bank Identifier Code (BIC/SWIFT) of the customer's bank. + + Null if not available. + """ + + +class PaymentMethodIdealPaymentMethod(BaseModel): + """A saved iDEAL payment method, including the customer's bank name and BIC code.""" + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + ideal: PaymentMethodIdealPaymentMethodIdeal + """ + The iDEAL-specific details for this payment method, including bank name and BIC. + """ + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["IdealPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodSepaDebitPaymentMethodSepaDebit(BaseModel): + """ + The SEPA Direct Debit-specific details for this payment method, including bank code and last four IBAN digits. + """ + + bank_code: Optional[str] = None + """The bank code of the financial institution associated with this SEPA account. + + Null if not available. + """ + + branch_code: Optional[str] = None + """The branch code of the financial institution associated with this SEPA account. + + Null if not available. + """ + + country: Optional[str] = None + """The two-letter ISO country code where the bank account is located. + + Null if not available. + """ + + last4: Optional[str] = None + """The last four digits of the IBAN associated with this SEPA account. + + Null if not available. + """ + + +class PaymentMethodSepaDebitPaymentMethod(BaseModel): + """ + A saved SEPA Direct Debit payment method, including the bank code, country, and last four IBAN digits. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + sepa_debit: PaymentMethodSepaDebitPaymentMethodSepaDebit + """ + The SEPA Direct Debit-specific details for this payment method, including bank + code and last four IBAN digits. + """ + + typename: Literal["SepaDebitPaymentMethod"] + """The typename of this object""" + + +PaymentMethod: TypeAlias = Annotated[ + Union[ + Optional[PaymentMethodBasePaymentMethod], + Optional[PaymentMethodCardPaymentMethod], + Optional[PaymentMethodUsBankAccountPaymentMethod], + Optional[PaymentMethodCashappPaymentMethod], + Optional[PaymentMethodIdealPaymentMethod], + Optional[PaymentMethodSepaDebitPaymentMethod], + ], + PropertyInfo(discriminator="typename"), +] + + +class Product(BaseModel): + """The access pass being promoted. + + Null for campaigns that don't target a specific product. + """ + + id: str + """The unique identifier for the product.""" + + route: str + """ + The URL slug used in the product's public link (e.g., 'my-product' in + whop.com/company/my-product). + """ + + title: str + """ + The display name of the product shown to customers on the product page and in + search results. + """ + + +class AdCampaignCreateResponse(BaseModel): + """An advertising campaign running on an external platform or within Whop.""" + + id: str + """The unique identifier for the ad campaign.""" + + available_budget: float + """ + Available budget in dollars, capped at daily budget minus today's spend for + daily campaigns + """ + + billing_ledger_account: Optional[BillingLedgerAccount] = None + """ + The ledger account being charged for platform balance billing (null if using + card) + """ + + clicks_count: int + """Number of clicks""" + + config: Optional[Config] = None + """Meta campaign configuration (objective, budget, bidding, etc.). + + Null for non-Meta campaigns — use `tiktokConfig` for TikTok. + """ + + created_at: datetime + """The datetime the ad campaign was created.""" + + created_by_user: CreatedByUser + """The user who created the campaign""" + + daily_budget: Optional[float] = None + """ + Effective daily budget in dollars — sum of ad group budgets when set, otherwise + campaign-level daily budget + """ + + impressions_count: int + """Number of impressions (views)""" + + paused_until: Optional[datetime] = None + """If temporarily paused, the timestamp when the campaign will auto-resume""" + + payment_method: PaymentMethod + """The payment method used for daily billing (null if using platform balance)""" + + platform: Optional[Literal["meta", "tiktok"]] = None + """The platforms where an ad campaign can run.""" + + product: Optional[Product] = None + """The access pass being promoted. + + Null for campaigns that don't target a specific product. + """ + + purchases_count: int + """Number of purchases""" + + remaining_balance: float + """Remaining balance in dollars""" + + return_on_ad_spend: float + """Return on Ad Spend (ROAS) percentage - revenue generated divided by ad spend""" + + revenue: float + """Total revenue generated from users who converted through this campaign""" + + status: Literal[ + "active", "paused", "inactive", "stale", "pending_refund", "payment_failed", "draft", "in_review", "flagged" + ] + """Current status of the campaign (active, paused, or inactive)""" + + target_country_codes: List[str] + """Array of ISO3166 country codes for territory targeting""" + + title: str + """The title of the ad campaign""" + + todays_spend: float + """Amount spent today in dollars""" + + total_credits: float + """Total credits added to the campaign in dollars""" + + total_spend: float + """Total amount spent on conversions in dollars""" + + updated_at: datetime + """The datetime the ad campaign was last updated.""" diff --git a/src/whop_sdk/types/ad_campaign_list_params.py b/src/whop_sdk/types/ad_campaign_list_params.py new file mode 100644 index 0000000..85585e1 --- /dev/null +++ b/src/whop_sdk/types/ad_campaign_list_params.py @@ -0,0 +1,44 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from datetime import datetime +from typing_extensions import Literal, Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["AdCampaignListParams"] + + +class AdCampaignListParams(TypedDict, total=False): + company_id: Required[str] + """The unique identifier of the company to list ad campaigns for.""" + + after: Optional[str] + """Returns the elements in the list that come after the specified cursor.""" + + before: Optional[str] + """Returns the elements in the list that come before the specified cursor.""" + + created_after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Only return ad campaigns created after this timestamp.""" + + created_before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Only return ad campaigns created before this timestamp.""" + + first: Optional[int] + """Returns the first _n_ elements from the list.""" + + last: Optional[int] + """Returns the last _n_ elements from the list.""" + + query: Optional[str] + """Case-insensitive substring match against the campaign title.""" + + status: Optional[ + Literal[ + "active", "paused", "inactive", "stale", "pending_refund", "payment_failed", "draft", "in_review", "flagged" + ] + ] + """The status of an ad campaign.""" diff --git a/src/whop_sdk/types/ad_campaign_list_response.py b/src/whop_sdk/types/ad_campaign_list_response.py new file mode 100644 index 0000000..5d62357 --- /dev/null +++ b/src/whop_sdk/types/ad_campaign_list_response.py @@ -0,0 +1,106 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["AdCampaignListResponse", "Product"] + + +class Product(BaseModel): + """The access pass being promoted. + + Null for campaigns that don't target a specific product. + """ + + id: str + """The unique identifier for the product.""" + + route: str + """ + The URL slug used in the product's public link (e.g., 'my-product' in + whop.com/company/my-product). + """ + + title: str + """ + The display name of the product shown to customers on the product page and in + search results. + """ + + +class AdCampaignListResponse(BaseModel): + """An advertising campaign running on an external platform or within Whop.""" + + id: str + """The unique identifier for the ad campaign.""" + + available_budget: float + """ + Available budget in dollars, capped at daily budget minus today's spend for + daily campaigns + """ + + clicks_count: int + """Number of clicks""" + + created_at: datetime + """The datetime the ad campaign was created.""" + + daily_budget: Optional[float] = None + """ + Effective daily budget in dollars — sum of ad group budgets when set, otherwise + campaign-level daily budget + """ + + impressions_count: int + """Number of impressions (views)""" + + paused_until: Optional[datetime] = None + """If temporarily paused, the timestamp when the campaign will auto-resume""" + + platform: Optional[Literal["meta", "tiktok"]] = None + """The platforms where an ad campaign can run.""" + + product: Optional[Product] = None + """The access pass being promoted. + + Null for campaigns that don't target a specific product. + """ + + purchases_count: int + """Number of purchases""" + + remaining_balance: float + """Remaining balance in dollars""" + + return_on_ad_spend: float + """Return on Ad Spend (ROAS) percentage - revenue generated divided by ad spend""" + + revenue: float + """Total revenue generated from users who converted through this campaign""" + + status: Literal[ + "active", "paused", "inactive", "stale", "pending_refund", "payment_failed", "draft", "in_review", "flagged" + ] + """Current status of the campaign (active, paused, or inactive)""" + + target_country_codes: List[str] + """Array of ISO3166 country codes for territory targeting""" + + title: str + """The title of the ad campaign""" + + todays_spend: float + """Amount spent today in dollars""" + + total_credits: float + """Total credits added to the campaign in dollars""" + + total_spend: float + """Total amount spent on conversions in dollars""" + + updated_at: datetime + """The datetime the ad campaign was last updated.""" diff --git a/src/whop_sdk/types/ad_campaign_pause_response.py b/src/whop_sdk/types/ad_campaign_pause_response.py new file mode 100644 index 0000000..116715e --- /dev/null +++ b/src/whop_sdk/types/ad_campaign_pause_response.py @@ -0,0 +1,509 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel +from .card_brands import CardBrands +from .payment_method_types import PaymentMethodTypes + +__all__ = [ + "AdCampaignPauseResponse", + "BillingLedgerAccount", + "Config", + "CreatedByUser", + "PaymentMethod", + "PaymentMethodBasePaymentMethod", + "PaymentMethodCardPaymentMethod", + "PaymentMethodCardPaymentMethodCard", + "PaymentMethodUsBankAccountPaymentMethod", + "PaymentMethodUsBankAccountPaymentMethodUsBankAccount", + "PaymentMethodCashappPaymentMethod", + "PaymentMethodCashappPaymentMethodCashapp", + "PaymentMethodIdealPaymentMethod", + "PaymentMethodIdealPaymentMethodIdeal", + "PaymentMethodSepaDebitPaymentMethod", + "PaymentMethodSepaDebitPaymentMethodSepaDebit", + "Product", +] + + +class BillingLedgerAccount(BaseModel): + """ + The ledger account being charged for platform balance billing (null if using card) + """ + + id: str + """The unique identifier for the ledger account.""" + + +class Config(BaseModel): + """Meta campaign configuration (objective, budget, bidding, etc.). + + Null for non-Meta campaigns — use `tiktokConfig` for TikTok. + """ + + bid_amount: Optional[int] = None + """Bid cap amount in cents. Only used when bid_strategy is bid_cap.""" + + bid_strategy: Optional[Literal["lowest_cost", "bid_cap", "cost_cap"]] = None + """The bidding strategy used to optimize spend for this campaign.""" + + budget_optimization: Optional[bool] = None + """ + Whether campaign budget optimization (CBO) is enabled, allowing the platform to + distribute budget across ad groups. + """ + + end_time: Optional[str] = None + """The scheduled end time of the campaign (ISO8601).""" + + objective: Optional[Literal["awareness", "traffic", "engagement", "leads", "sales"]] = None + """The campaign objective that determines how Meta optimizes delivery.""" + + special_categories: Optional[List[str]] = None + """ + Special ad categories required by the platform (e.g., housing, employment, + credit). + """ + + start_time: Optional[str] = None + """The scheduled start time of the campaign (ISO8601).""" + + status: Optional[Literal["active", "paused"]] = None + """The campaign status as set by the advertiser (active or paused).""" + + +class CreatedByUser(BaseModel): + """The user who created the campaign""" + + id: str + """The unique identifier for the user.""" + + name: Optional[str] = None + """The user's display name shown on their public profile.""" + + username: str + """The user's unique username shown on their public profile.""" + + +class PaymentMethodBasePaymentMethod(BaseModel): + """A saved payment method with no type-specific details available.""" + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["BasePaymentMethod"] + """The typename of this object""" + + +class PaymentMethodCardPaymentMethodCard(BaseModel): + """ + The card-specific details for this payment method, including brand, last four digits, and expiration. + """ + + brand: Optional[CardBrands] = None + """Possible card brands that a payment token can have""" + + exp_month: Optional[int] = None + """The two-digit expiration month of the card (1-12). Null if not available.""" + + exp_year: Optional[int] = None + """The two-digit expiration year of the card (e.g., 27 for 2027). + + Null if not available. + """ + + last4: Optional[str] = None + """The last four digits of the card number. Null if not available.""" + + +class PaymentMethodCardPaymentMethod(BaseModel): + """ + A saved card payment method, including brand, last four digits, and expiration details. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + card: PaymentMethodCardPaymentMethodCard + """ + The card-specific details for this payment method, including brand, last four + digits, and expiration. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["CardPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodUsBankAccountPaymentMethodUsBankAccount(BaseModel): + """ + The bank account-specific details for this payment method, including bank name and last four digits. + """ + + account_type: str + """The type of bank account (e.g., checking, savings).""" + + bank_name: str + """The name of the financial institution holding the account.""" + + last4: str + """The last four digits of the bank account number.""" + + +class PaymentMethodUsBankAccountPaymentMethod(BaseModel): + """ + A saved US bank account payment method, including bank name, last four digits, and account type. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["UsBankAccountPaymentMethod"] + """The typename of this object""" + + us_bank_account: PaymentMethodUsBankAccountPaymentMethodUsBankAccount + """ + The bank account-specific details for this payment method, including bank name + and last four digits. + """ + + +class PaymentMethodCashappPaymentMethodCashapp(BaseModel): + """ + The Cash App-specific details for this payment method, including cashtag and buyer ID. + """ + + buyer_id: Optional[str] = None + """The unique and immutable identifier assigned by Cash App to the buyer. + + Null if not available. + """ + + cashtag: Optional[str] = None + """The public cashtag handle of the buyer on Cash App. Null if not available.""" + + +class PaymentMethodCashappPaymentMethod(BaseModel): + """ + A saved Cash App payment method, including the buyer's cashtag and unique identifier. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + cashapp: PaymentMethodCashappPaymentMethodCashapp + """ + The Cash App-specific details for this payment method, including cashtag and + buyer ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["CashappPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodIdealPaymentMethodIdeal(BaseModel): + """ + The iDEAL-specific details for this payment method, including bank name and BIC. + """ + + bank: Optional[str] = None + """The name of the customer's bank used for the iDEAL transaction. + + Null if not available. + """ + + bic: Optional[str] = None + """The Bank Identifier Code (BIC/SWIFT) of the customer's bank. + + Null if not available. + """ + + +class PaymentMethodIdealPaymentMethod(BaseModel): + """A saved iDEAL payment method, including the customer's bank name and BIC code.""" + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + ideal: PaymentMethodIdealPaymentMethodIdeal + """ + The iDEAL-specific details for this payment method, including bank name and BIC. + """ + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["IdealPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodSepaDebitPaymentMethodSepaDebit(BaseModel): + """ + The SEPA Direct Debit-specific details for this payment method, including bank code and last four IBAN digits. + """ + + bank_code: Optional[str] = None + """The bank code of the financial institution associated with this SEPA account. + + Null if not available. + """ + + branch_code: Optional[str] = None + """The branch code of the financial institution associated with this SEPA account. + + Null if not available. + """ + + country: Optional[str] = None + """The two-letter ISO country code where the bank account is located. + + Null if not available. + """ + + last4: Optional[str] = None + """The last four digits of the IBAN associated with this SEPA account. + + Null if not available. + """ + + +class PaymentMethodSepaDebitPaymentMethod(BaseModel): + """ + A saved SEPA Direct Debit payment method, including the bank code, country, and last four IBAN digits. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + sepa_debit: PaymentMethodSepaDebitPaymentMethodSepaDebit + """ + The SEPA Direct Debit-specific details for this payment method, including bank + code and last four IBAN digits. + """ + + typename: Literal["SepaDebitPaymentMethod"] + """The typename of this object""" + + +PaymentMethod: TypeAlias = Annotated[ + Union[ + Optional[PaymentMethodBasePaymentMethod], + Optional[PaymentMethodCardPaymentMethod], + Optional[PaymentMethodUsBankAccountPaymentMethod], + Optional[PaymentMethodCashappPaymentMethod], + Optional[PaymentMethodIdealPaymentMethod], + Optional[PaymentMethodSepaDebitPaymentMethod], + ], + PropertyInfo(discriminator="typename"), +] + + +class Product(BaseModel): + """The access pass being promoted. + + Null for campaigns that don't target a specific product. + """ + + id: str + """The unique identifier for the product.""" + + route: str + """ + The URL slug used in the product's public link (e.g., 'my-product' in + whop.com/company/my-product). + """ + + title: str + """ + The display name of the product shown to customers on the product page and in + search results. + """ + + +class AdCampaignPauseResponse(BaseModel): + """An advertising campaign running on an external platform or within Whop.""" + + id: str + """The unique identifier for the ad campaign.""" + + available_budget: float + """ + Available budget in dollars, capped at daily budget minus today's spend for + daily campaigns + """ + + billing_ledger_account: Optional[BillingLedgerAccount] = None + """ + The ledger account being charged for platform balance billing (null if using + card) + """ + + clicks_count: int + """Number of clicks""" + + config: Optional[Config] = None + """Meta campaign configuration (objective, budget, bidding, etc.). + + Null for non-Meta campaigns — use `tiktokConfig` for TikTok. + """ + + created_at: datetime + """The datetime the ad campaign was created.""" + + created_by_user: CreatedByUser + """The user who created the campaign""" + + daily_budget: Optional[float] = None + """ + Effective daily budget in dollars — sum of ad group budgets when set, otherwise + campaign-level daily budget + """ + + impressions_count: int + """Number of impressions (views)""" + + paused_until: Optional[datetime] = None + """If temporarily paused, the timestamp when the campaign will auto-resume""" + + payment_method: PaymentMethod + """The payment method used for daily billing (null if using platform balance)""" + + platform: Optional[Literal["meta", "tiktok"]] = None + """The platforms where an ad campaign can run.""" + + product: Optional[Product] = None + """The access pass being promoted. + + Null for campaigns that don't target a specific product. + """ + + purchases_count: int + """Number of purchases""" + + remaining_balance: float + """Remaining balance in dollars""" + + return_on_ad_spend: float + """Return on Ad Spend (ROAS) percentage - revenue generated divided by ad spend""" + + revenue: float + """Total revenue generated from users who converted through this campaign""" + + status: Literal[ + "active", "paused", "inactive", "stale", "pending_refund", "payment_failed", "draft", "in_review", "flagged" + ] + """Current status of the campaign (active, paused, or inactive)""" + + target_country_codes: List[str] + """Array of ISO3166 country codes for territory targeting""" + + title: str + """The title of the ad campaign""" + + todays_spend: float + """Amount spent today in dollars""" + + total_credits: float + """Total credits added to the campaign in dollars""" + + total_spend: float + """Total amount spent on conversions in dollars""" + + updated_at: datetime + """The datetime the ad campaign was last updated.""" diff --git a/src/whop_sdk/types/ad_campaign_retrieve_response.py b/src/whop_sdk/types/ad_campaign_retrieve_response.py new file mode 100644 index 0000000..5276938 --- /dev/null +++ b/src/whop_sdk/types/ad_campaign_retrieve_response.py @@ -0,0 +1,509 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel +from .card_brands import CardBrands +from .payment_method_types import PaymentMethodTypes + +__all__ = [ + "AdCampaignRetrieveResponse", + "BillingLedgerAccount", + "Config", + "CreatedByUser", + "PaymentMethod", + "PaymentMethodBasePaymentMethod", + "PaymentMethodCardPaymentMethod", + "PaymentMethodCardPaymentMethodCard", + "PaymentMethodUsBankAccountPaymentMethod", + "PaymentMethodUsBankAccountPaymentMethodUsBankAccount", + "PaymentMethodCashappPaymentMethod", + "PaymentMethodCashappPaymentMethodCashapp", + "PaymentMethodIdealPaymentMethod", + "PaymentMethodIdealPaymentMethodIdeal", + "PaymentMethodSepaDebitPaymentMethod", + "PaymentMethodSepaDebitPaymentMethodSepaDebit", + "Product", +] + + +class BillingLedgerAccount(BaseModel): + """ + The ledger account being charged for platform balance billing (null if using card) + """ + + id: str + """The unique identifier for the ledger account.""" + + +class Config(BaseModel): + """Meta campaign configuration (objective, budget, bidding, etc.). + + Null for non-Meta campaigns — use `tiktokConfig` for TikTok. + """ + + bid_amount: Optional[int] = None + """Bid cap amount in cents. Only used when bid_strategy is bid_cap.""" + + bid_strategy: Optional[Literal["lowest_cost", "bid_cap", "cost_cap"]] = None + """The bidding strategy used to optimize spend for this campaign.""" + + budget_optimization: Optional[bool] = None + """ + Whether campaign budget optimization (CBO) is enabled, allowing the platform to + distribute budget across ad groups. + """ + + end_time: Optional[str] = None + """The scheduled end time of the campaign (ISO8601).""" + + objective: Optional[Literal["awareness", "traffic", "engagement", "leads", "sales"]] = None + """The campaign objective that determines how Meta optimizes delivery.""" + + special_categories: Optional[List[str]] = None + """ + Special ad categories required by the platform (e.g., housing, employment, + credit). + """ + + start_time: Optional[str] = None + """The scheduled start time of the campaign (ISO8601).""" + + status: Optional[Literal["active", "paused"]] = None + """The campaign status as set by the advertiser (active or paused).""" + + +class CreatedByUser(BaseModel): + """The user who created the campaign""" + + id: str + """The unique identifier for the user.""" + + name: Optional[str] = None + """The user's display name shown on their public profile.""" + + username: str + """The user's unique username shown on their public profile.""" + + +class PaymentMethodBasePaymentMethod(BaseModel): + """A saved payment method with no type-specific details available.""" + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["BasePaymentMethod"] + """The typename of this object""" + + +class PaymentMethodCardPaymentMethodCard(BaseModel): + """ + The card-specific details for this payment method, including brand, last four digits, and expiration. + """ + + brand: Optional[CardBrands] = None + """Possible card brands that a payment token can have""" + + exp_month: Optional[int] = None + """The two-digit expiration month of the card (1-12). Null if not available.""" + + exp_year: Optional[int] = None + """The two-digit expiration year of the card (e.g., 27 for 2027). + + Null if not available. + """ + + last4: Optional[str] = None + """The last four digits of the card number. Null if not available.""" + + +class PaymentMethodCardPaymentMethod(BaseModel): + """ + A saved card payment method, including brand, last four digits, and expiration details. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + card: PaymentMethodCardPaymentMethodCard + """ + The card-specific details for this payment method, including brand, last four + digits, and expiration. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["CardPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodUsBankAccountPaymentMethodUsBankAccount(BaseModel): + """ + The bank account-specific details for this payment method, including bank name and last four digits. + """ + + account_type: str + """The type of bank account (e.g., checking, savings).""" + + bank_name: str + """The name of the financial institution holding the account.""" + + last4: str + """The last four digits of the bank account number.""" + + +class PaymentMethodUsBankAccountPaymentMethod(BaseModel): + """ + A saved US bank account payment method, including bank name, last four digits, and account type. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["UsBankAccountPaymentMethod"] + """The typename of this object""" + + us_bank_account: PaymentMethodUsBankAccountPaymentMethodUsBankAccount + """ + The bank account-specific details for this payment method, including bank name + and last four digits. + """ + + +class PaymentMethodCashappPaymentMethodCashapp(BaseModel): + """ + The Cash App-specific details for this payment method, including cashtag and buyer ID. + """ + + buyer_id: Optional[str] = None + """The unique and immutable identifier assigned by Cash App to the buyer. + + Null if not available. + """ + + cashtag: Optional[str] = None + """The public cashtag handle of the buyer on Cash App. Null if not available.""" + + +class PaymentMethodCashappPaymentMethod(BaseModel): + """ + A saved Cash App payment method, including the buyer's cashtag and unique identifier. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + cashapp: PaymentMethodCashappPaymentMethodCashapp + """ + The Cash App-specific details for this payment method, including cashtag and + buyer ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["CashappPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodIdealPaymentMethodIdeal(BaseModel): + """ + The iDEAL-specific details for this payment method, including bank name and BIC. + """ + + bank: Optional[str] = None + """The name of the customer's bank used for the iDEAL transaction. + + Null if not available. + """ + + bic: Optional[str] = None + """The Bank Identifier Code (BIC/SWIFT) of the customer's bank. + + Null if not available. + """ + + +class PaymentMethodIdealPaymentMethod(BaseModel): + """A saved iDEAL payment method, including the customer's bank name and BIC code.""" + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + ideal: PaymentMethodIdealPaymentMethodIdeal + """ + The iDEAL-specific details for this payment method, including bank name and BIC. + """ + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["IdealPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodSepaDebitPaymentMethodSepaDebit(BaseModel): + """ + The SEPA Direct Debit-specific details for this payment method, including bank code and last four IBAN digits. + """ + + bank_code: Optional[str] = None + """The bank code of the financial institution associated with this SEPA account. + + Null if not available. + """ + + branch_code: Optional[str] = None + """The branch code of the financial institution associated with this SEPA account. + + Null if not available. + """ + + country: Optional[str] = None + """The two-letter ISO country code where the bank account is located. + + Null if not available. + """ + + last4: Optional[str] = None + """The last four digits of the IBAN associated with this SEPA account. + + Null if not available. + """ + + +class PaymentMethodSepaDebitPaymentMethod(BaseModel): + """ + A saved SEPA Direct Debit payment method, including the bank code, country, and last four IBAN digits. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + sepa_debit: PaymentMethodSepaDebitPaymentMethodSepaDebit + """ + The SEPA Direct Debit-specific details for this payment method, including bank + code and last four IBAN digits. + """ + + typename: Literal["SepaDebitPaymentMethod"] + """The typename of this object""" + + +PaymentMethod: TypeAlias = Annotated[ + Union[ + Optional[PaymentMethodBasePaymentMethod], + Optional[PaymentMethodCardPaymentMethod], + Optional[PaymentMethodUsBankAccountPaymentMethod], + Optional[PaymentMethodCashappPaymentMethod], + Optional[PaymentMethodIdealPaymentMethod], + Optional[PaymentMethodSepaDebitPaymentMethod], + ], + PropertyInfo(discriminator="typename"), +] + + +class Product(BaseModel): + """The access pass being promoted. + + Null for campaigns that don't target a specific product. + """ + + id: str + """The unique identifier for the product.""" + + route: str + """ + The URL slug used in the product's public link (e.g., 'my-product' in + whop.com/company/my-product). + """ + + title: str + """ + The display name of the product shown to customers on the product page and in + search results. + """ + + +class AdCampaignRetrieveResponse(BaseModel): + """An advertising campaign running on an external platform or within Whop.""" + + id: str + """The unique identifier for the ad campaign.""" + + available_budget: float + """ + Available budget in dollars, capped at daily budget minus today's spend for + daily campaigns + """ + + billing_ledger_account: Optional[BillingLedgerAccount] = None + """ + The ledger account being charged for platform balance billing (null if using + card) + """ + + clicks_count: int + """Number of clicks""" + + config: Optional[Config] = None + """Meta campaign configuration (objective, budget, bidding, etc.). + + Null for non-Meta campaigns — use `tiktokConfig` for TikTok. + """ + + created_at: datetime + """The datetime the ad campaign was created.""" + + created_by_user: CreatedByUser + """The user who created the campaign""" + + daily_budget: Optional[float] = None + """ + Effective daily budget in dollars — sum of ad group budgets when set, otherwise + campaign-level daily budget + """ + + impressions_count: int + """Number of impressions (views)""" + + paused_until: Optional[datetime] = None + """If temporarily paused, the timestamp when the campaign will auto-resume""" + + payment_method: PaymentMethod + """The payment method used for daily billing (null if using platform balance)""" + + platform: Optional[Literal["meta", "tiktok"]] = None + """The platforms where an ad campaign can run.""" + + product: Optional[Product] = None + """The access pass being promoted. + + Null for campaigns that don't target a specific product. + """ + + purchases_count: int + """Number of purchases""" + + remaining_balance: float + """Remaining balance in dollars""" + + return_on_ad_spend: float + """Return on Ad Spend (ROAS) percentage - revenue generated divided by ad spend""" + + revenue: float + """Total revenue generated from users who converted through this campaign""" + + status: Literal[ + "active", "paused", "inactive", "stale", "pending_refund", "payment_failed", "draft", "in_review", "flagged" + ] + """Current status of the campaign (active, paused, or inactive)""" + + target_country_codes: List[str] + """Array of ISO3166 country codes for territory targeting""" + + title: str + """The title of the ad campaign""" + + todays_spend: float + """Amount spent today in dollars""" + + total_credits: float + """Total credits added to the campaign in dollars""" + + total_spend: float + """Total amount spent on conversions in dollars""" + + updated_at: datetime + """The datetime the ad campaign was last updated.""" diff --git a/src/whop_sdk/types/ad_campaign_unpause_response.py b/src/whop_sdk/types/ad_campaign_unpause_response.py new file mode 100644 index 0000000..99328ae --- /dev/null +++ b/src/whop_sdk/types/ad_campaign_unpause_response.py @@ -0,0 +1,509 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel +from .card_brands import CardBrands +from .payment_method_types import PaymentMethodTypes + +__all__ = [ + "AdCampaignUnpauseResponse", + "BillingLedgerAccount", + "Config", + "CreatedByUser", + "PaymentMethod", + "PaymentMethodBasePaymentMethod", + "PaymentMethodCardPaymentMethod", + "PaymentMethodCardPaymentMethodCard", + "PaymentMethodUsBankAccountPaymentMethod", + "PaymentMethodUsBankAccountPaymentMethodUsBankAccount", + "PaymentMethodCashappPaymentMethod", + "PaymentMethodCashappPaymentMethodCashapp", + "PaymentMethodIdealPaymentMethod", + "PaymentMethodIdealPaymentMethodIdeal", + "PaymentMethodSepaDebitPaymentMethod", + "PaymentMethodSepaDebitPaymentMethodSepaDebit", + "Product", +] + + +class BillingLedgerAccount(BaseModel): + """ + The ledger account being charged for platform balance billing (null if using card) + """ + + id: str + """The unique identifier for the ledger account.""" + + +class Config(BaseModel): + """Meta campaign configuration (objective, budget, bidding, etc.). + + Null for non-Meta campaigns — use `tiktokConfig` for TikTok. + """ + + bid_amount: Optional[int] = None + """Bid cap amount in cents. Only used when bid_strategy is bid_cap.""" + + bid_strategy: Optional[Literal["lowest_cost", "bid_cap", "cost_cap"]] = None + """The bidding strategy used to optimize spend for this campaign.""" + + budget_optimization: Optional[bool] = None + """ + Whether campaign budget optimization (CBO) is enabled, allowing the platform to + distribute budget across ad groups. + """ + + end_time: Optional[str] = None + """The scheduled end time of the campaign (ISO8601).""" + + objective: Optional[Literal["awareness", "traffic", "engagement", "leads", "sales"]] = None + """The campaign objective that determines how Meta optimizes delivery.""" + + special_categories: Optional[List[str]] = None + """ + Special ad categories required by the platform (e.g., housing, employment, + credit). + """ + + start_time: Optional[str] = None + """The scheduled start time of the campaign (ISO8601).""" + + status: Optional[Literal["active", "paused"]] = None + """The campaign status as set by the advertiser (active or paused).""" + + +class CreatedByUser(BaseModel): + """The user who created the campaign""" + + id: str + """The unique identifier for the user.""" + + name: Optional[str] = None + """The user's display name shown on their public profile.""" + + username: str + """The user's unique username shown on their public profile.""" + + +class PaymentMethodBasePaymentMethod(BaseModel): + """A saved payment method with no type-specific details available.""" + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["BasePaymentMethod"] + """The typename of this object""" + + +class PaymentMethodCardPaymentMethodCard(BaseModel): + """ + The card-specific details for this payment method, including brand, last four digits, and expiration. + """ + + brand: Optional[CardBrands] = None + """Possible card brands that a payment token can have""" + + exp_month: Optional[int] = None + """The two-digit expiration month of the card (1-12). Null if not available.""" + + exp_year: Optional[int] = None + """The two-digit expiration year of the card (e.g., 27 for 2027). + + Null if not available. + """ + + last4: Optional[str] = None + """The last four digits of the card number. Null if not available.""" + + +class PaymentMethodCardPaymentMethod(BaseModel): + """ + A saved card payment method, including brand, last four digits, and expiration details. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + card: PaymentMethodCardPaymentMethodCard + """ + The card-specific details for this payment method, including brand, last four + digits, and expiration. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["CardPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodUsBankAccountPaymentMethodUsBankAccount(BaseModel): + """ + The bank account-specific details for this payment method, including bank name and last four digits. + """ + + account_type: str + """The type of bank account (e.g., checking, savings).""" + + bank_name: str + """The name of the financial institution holding the account.""" + + last4: str + """The last four digits of the bank account number.""" + + +class PaymentMethodUsBankAccountPaymentMethod(BaseModel): + """ + A saved US bank account payment method, including bank name, last four digits, and account type. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["UsBankAccountPaymentMethod"] + """The typename of this object""" + + us_bank_account: PaymentMethodUsBankAccountPaymentMethodUsBankAccount + """ + The bank account-specific details for this payment method, including bank name + and last four digits. + """ + + +class PaymentMethodCashappPaymentMethodCashapp(BaseModel): + """ + The Cash App-specific details for this payment method, including cashtag and buyer ID. + """ + + buyer_id: Optional[str] = None + """The unique and immutable identifier assigned by Cash App to the buyer. + + Null if not available. + """ + + cashtag: Optional[str] = None + """The public cashtag handle of the buyer on Cash App. Null if not available.""" + + +class PaymentMethodCashappPaymentMethod(BaseModel): + """ + A saved Cash App payment method, including the buyer's cashtag and unique identifier. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + cashapp: PaymentMethodCashappPaymentMethodCashapp + """ + The Cash App-specific details for this payment method, including cashtag and + buyer ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["CashappPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodIdealPaymentMethodIdeal(BaseModel): + """ + The iDEAL-specific details for this payment method, including bank name and BIC. + """ + + bank: Optional[str] = None + """The name of the customer's bank used for the iDEAL transaction. + + Null if not available. + """ + + bic: Optional[str] = None + """The Bank Identifier Code (BIC/SWIFT) of the customer's bank. + + Null if not available. + """ + + +class PaymentMethodIdealPaymentMethod(BaseModel): + """A saved iDEAL payment method, including the customer's bank name and BIC code.""" + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + ideal: PaymentMethodIdealPaymentMethodIdeal + """ + The iDEAL-specific details for this payment method, including bank name and BIC. + """ + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["IdealPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodSepaDebitPaymentMethodSepaDebit(BaseModel): + """ + The SEPA Direct Debit-specific details for this payment method, including bank code and last four IBAN digits. + """ + + bank_code: Optional[str] = None + """The bank code of the financial institution associated with this SEPA account. + + Null if not available. + """ + + branch_code: Optional[str] = None + """The branch code of the financial institution associated with this SEPA account. + + Null if not available. + """ + + country: Optional[str] = None + """The two-letter ISO country code where the bank account is located. + + Null if not available. + """ + + last4: Optional[str] = None + """The last four digits of the IBAN associated with this SEPA account. + + Null if not available. + """ + + +class PaymentMethodSepaDebitPaymentMethod(BaseModel): + """ + A saved SEPA Direct Debit payment method, including the bank code, country, and last four IBAN digits. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + sepa_debit: PaymentMethodSepaDebitPaymentMethodSepaDebit + """ + The SEPA Direct Debit-specific details for this payment method, including bank + code and last four IBAN digits. + """ + + typename: Literal["SepaDebitPaymentMethod"] + """The typename of this object""" + + +PaymentMethod: TypeAlias = Annotated[ + Union[ + Optional[PaymentMethodBasePaymentMethod], + Optional[PaymentMethodCardPaymentMethod], + Optional[PaymentMethodUsBankAccountPaymentMethod], + Optional[PaymentMethodCashappPaymentMethod], + Optional[PaymentMethodIdealPaymentMethod], + Optional[PaymentMethodSepaDebitPaymentMethod], + ], + PropertyInfo(discriminator="typename"), +] + + +class Product(BaseModel): + """The access pass being promoted. + + Null for campaigns that don't target a specific product. + """ + + id: str + """The unique identifier for the product.""" + + route: str + """ + The URL slug used in the product's public link (e.g., 'my-product' in + whop.com/company/my-product). + """ + + title: str + """ + The display name of the product shown to customers on the product page and in + search results. + """ + + +class AdCampaignUnpauseResponse(BaseModel): + """An advertising campaign running on an external platform or within Whop.""" + + id: str + """The unique identifier for the ad campaign.""" + + available_budget: float + """ + Available budget in dollars, capped at daily budget minus today's spend for + daily campaigns + """ + + billing_ledger_account: Optional[BillingLedgerAccount] = None + """ + The ledger account being charged for platform balance billing (null if using + card) + """ + + clicks_count: int + """Number of clicks""" + + config: Optional[Config] = None + """Meta campaign configuration (objective, budget, bidding, etc.). + + Null for non-Meta campaigns — use `tiktokConfig` for TikTok. + """ + + created_at: datetime + """The datetime the ad campaign was created.""" + + created_by_user: CreatedByUser + """The user who created the campaign""" + + daily_budget: Optional[float] = None + """ + Effective daily budget in dollars — sum of ad group budgets when set, otherwise + campaign-level daily budget + """ + + impressions_count: int + """Number of impressions (views)""" + + paused_until: Optional[datetime] = None + """If temporarily paused, the timestamp when the campaign will auto-resume""" + + payment_method: PaymentMethod + """The payment method used for daily billing (null if using platform balance)""" + + platform: Optional[Literal["meta", "tiktok"]] = None + """The platforms where an ad campaign can run.""" + + product: Optional[Product] = None + """The access pass being promoted. + + Null for campaigns that don't target a specific product. + """ + + purchases_count: int + """Number of purchases""" + + remaining_balance: float + """Remaining balance in dollars""" + + return_on_ad_spend: float + """Return on Ad Spend (ROAS) percentage - revenue generated divided by ad spend""" + + revenue: float + """Total revenue generated from users who converted through this campaign""" + + status: Literal[ + "active", "paused", "inactive", "stale", "pending_refund", "payment_failed", "draft", "in_review", "flagged" + ] + """Current status of the campaign (active, paused, or inactive)""" + + target_country_codes: List[str] + """Array of ISO3166 country codes for territory targeting""" + + title: str + """The title of the ad campaign""" + + todays_spend: float + """Amount spent today in dollars""" + + total_credits: float + """Total credits added to the campaign in dollars""" + + total_spend: float + """Total amount spent on conversions in dollars""" + + updated_at: datetime + """The datetime the ad campaign was last updated.""" diff --git a/src/whop_sdk/types/ad_campaign_update_params.py b/src/whop_sdk/types/ad_campaign_update_params.py new file mode 100644 index 0000000..fd8872d --- /dev/null +++ b/src/whop_sdk/types/ad_campaign_update_params.py @@ -0,0 +1,76 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, TypedDict + +from .._types import SequenceNotStr + +__all__ = ["AdCampaignUpdateParams", "Config"] + + +class AdCampaignUpdateParams(TypedDict, total=False): + ad_creative_set_ids: Optional[SequenceNotStr[str]] + """Array of creative set IDs to link to this campaign.""" + + budget: Optional[float] + """Budget amount in dollars.""" + + budget_type: Optional[Literal["daily", "lifetime"]] + """The budget type for an ad campaign or ad group.""" + + config: Optional[Config] + """Unified campaign configuration (conversion goal, budget, bidding, etc.).""" + + daily_budget: Optional[float] + """Daily budget in dollars (minimum $5).""" + + product_id: Optional[str] + """The unique identifier of the product (access pass) to promote.""" + + target_country_codes: Optional[SequenceNotStr[str]] + """Array of ISO3166 country codes for territory targeting.""" + + title: Optional[str] + """The title of the ad campaign. Must be max 100 characters.""" + + +class Config(TypedDict, total=False): + """Unified campaign configuration (conversion goal, budget, bidding, etc.).""" + + bid_amount: Optional[int] + """Bid cap amount in cents. Only used when bid_strategy is bid_cap.""" + + bid_strategy: Optional[Literal["lowest_cost", "bid_cap", "cost_cap"]] + """The bidding strategy used to optimize spend for this campaign.""" + + budget_optimization: Optional[bool] + """ + Whether campaign budget optimization (CBO) is enabled, allowing the platform to + distribute budget across ad groups. + """ + + end_time: Optional[str] + """The scheduled end time of the campaign (ISO8601).""" + + lifetime_budget: Optional[int] + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + objective: Optional[Literal["awareness", "traffic", "engagement", "leads", "sales"]] + """The campaign objective that determines how Meta optimizes delivery.""" + + special_categories: Optional[SequenceNotStr[str]] + """ + Special ad categories required by the platform (e.g., housing, employment, + credit). + """ + + start_time: Optional[str] + """The scheduled start time of the campaign (ISO8601).""" + + status: Optional[Literal["active", "paused"]] + """The campaign status as set by the advertiser (active or paused).""" diff --git a/src/whop_sdk/types/ad_campaign_update_response.py b/src/whop_sdk/types/ad_campaign_update_response.py new file mode 100644 index 0000000..44ea781 --- /dev/null +++ b/src/whop_sdk/types/ad_campaign_update_response.py @@ -0,0 +1,509 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel +from .card_brands import CardBrands +from .payment_method_types import PaymentMethodTypes + +__all__ = [ + "AdCampaignUpdateResponse", + "BillingLedgerAccount", + "Config", + "CreatedByUser", + "PaymentMethod", + "PaymentMethodBasePaymentMethod", + "PaymentMethodCardPaymentMethod", + "PaymentMethodCardPaymentMethodCard", + "PaymentMethodUsBankAccountPaymentMethod", + "PaymentMethodUsBankAccountPaymentMethodUsBankAccount", + "PaymentMethodCashappPaymentMethod", + "PaymentMethodCashappPaymentMethodCashapp", + "PaymentMethodIdealPaymentMethod", + "PaymentMethodIdealPaymentMethodIdeal", + "PaymentMethodSepaDebitPaymentMethod", + "PaymentMethodSepaDebitPaymentMethodSepaDebit", + "Product", +] + + +class BillingLedgerAccount(BaseModel): + """ + The ledger account being charged for platform balance billing (null if using card) + """ + + id: str + """The unique identifier for the ledger account.""" + + +class Config(BaseModel): + """Meta campaign configuration (objective, budget, bidding, etc.). + + Null for non-Meta campaigns — use `tiktokConfig` for TikTok. + """ + + bid_amount: Optional[int] = None + """Bid cap amount in cents. Only used when bid_strategy is bid_cap.""" + + bid_strategy: Optional[Literal["lowest_cost", "bid_cap", "cost_cap"]] = None + """The bidding strategy used to optimize spend for this campaign.""" + + budget_optimization: Optional[bool] = None + """ + Whether campaign budget optimization (CBO) is enabled, allowing the platform to + distribute budget across ad groups. + """ + + end_time: Optional[str] = None + """The scheduled end time of the campaign (ISO8601).""" + + objective: Optional[Literal["awareness", "traffic", "engagement", "leads", "sales"]] = None + """The campaign objective that determines how Meta optimizes delivery.""" + + special_categories: Optional[List[str]] = None + """ + Special ad categories required by the platform (e.g., housing, employment, + credit). + """ + + start_time: Optional[str] = None + """The scheduled start time of the campaign (ISO8601).""" + + status: Optional[Literal["active", "paused"]] = None + """The campaign status as set by the advertiser (active or paused).""" + + +class CreatedByUser(BaseModel): + """The user who created the campaign""" + + id: str + """The unique identifier for the user.""" + + name: Optional[str] = None + """The user's display name shown on their public profile.""" + + username: str + """The user's unique username shown on their public profile.""" + + +class PaymentMethodBasePaymentMethod(BaseModel): + """A saved payment method with no type-specific details available.""" + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["BasePaymentMethod"] + """The typename of this object""" + + +class PaymentMethodCardPaymentMethodCard(BaseModel): + """ + The card-specific details for this payment method, including brand, last four digits, and expiration. + """ + + brand: Optional[CardBrands] = None + """Possible card brands that a payment token can have""" + + exp_month: Optional[int] = None + """The two-digit expiration month of the card (1-12). Null if not available.""" + + exp_year: Optional[int] = None + """The two-digit expiration year of the card (e.g., 27 for 2027). + + Null if not available. + """ + + last4: Optional[str] = None + """The last four digits of the card number. Null if not available.""" + + +class PaymentMethodCardPaymentMethod(BaseModel): + """ + A saved card payment method, including brand, last four digits, and expiration details. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + card: PaymentMethodCardPaymentMethodCard + """ + The card-specific details for this payment method, including brand, last four + digits, and expiration. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["CardPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodUsBankAccountPaymentMethodUsBankAccount(BaseModel): + """ + The bank account-specific details for this payment method, including bank name and last four digits. + """ + + account_type: str + """The type of bank account (e.g., checking, savings).""" + + bank_name: str + """The name of the financial institution holding the account.""" + + last4: str + """The last four digits of the bank account number.""" + + +class PaymentMethodUsBankAccountPaymentMethod(BaseModel): + """ + A saved US bank account payment method, including bank name, last four digits, and account type. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["UsBankAccountPaymentMethod"] + """The typename of this object""" + + us_bank_account: PaymentMethodUsBankAccountPaymentMethodUsBankAccount + """ + The bank account-specific details for this payment method, including bank name + and last four digits. + """ + + +class PaymentMethodCashappPaymentMethodCashapp(BaseModel): + """ + The Cash App-specific details for this payment method, including cashtag and buyer ID. + """ + + buyer_id: Optional[str] = None + """The unique and immutable identifier assigned by Cash App to the buyer. + + Null if not available. + """ + + cashtag: Optional[str] = None + """The public cashtag handle of the buyer on Cash App. Null if not available.""" + + +class PaymentMethodCashappPaymentMethod(BaseModel): + """ + A saved Cash App payment method, including the buyer's cashtag and unique identifier. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + cashapp: PaymentMethodCashappPaymentMethodCashapp + """ + The Cash App-specific details for this payment method, including cashtag and + buyer ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["CashappPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodIdealPaymentMethodIdeal(BaseModel): + """ + The iDEAL-specific details for this payment method, including bank name and BIC. + """ + + bank: Optional[str] = None + """The name of the customer's bank used for the iDEAL transaction. + + Null if not available. + """ + + bic: Optional[str] = None + """The Bank Identifier Code (BIC/SWIFT) of the customer's bank. + + Null if not available. + """ + + +class PaymentMethodIdealPaymentMethod(BaseModel): + """A saved iDEAL payment method, including the customer's bank name and BIC code.""" + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + ideal: PaymentMethodIdealPaymentMethodIdeal + """ + The iDEAL-specific details for this payment method, including bank name and BIC. + """ + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + typename: Literal["IdealPaymentMethod"] + """The typename of this object""" + + +class PaymentMethodSepaDebitPaymentMethodSepaDebit(BaseModel): + """ + The SEPA Direct Debit-specific details for this payment method, including bank code and last four IBAN digits. + """ + + bank_code: Optional[str] = None + """The bank code of the financial institution associated with this SEPA account. + + Null if not available. + """ + + branch_code: Optional[str] = None + """The branch code of the financial institution associated with this SEPA account. + + Null if not available. + """ + + country: Optional[str] = None + """The two-letter ISO country code where the bank account is located. + + Null if not available. + """ + + last4: Optional[str] = None + """The last four digits of the IBAN associated with this SEPA account. + + Null if not available. + """ + + +class PaymentMethodSepaDebitPaymentMethod(BaseModel): + """ + A saved SEPA Direct Debit payment method, including the bank code, country, and last four IBAN digits. + """ + + id: str + """Represents a unique identifier that is Base64 obfuscated. + + It is often used to refetch an object or as key for a cache. The ID type appears + in a JSON response as a String; however, it is not intended to be + human-readable. When expected as an input type, any string (such as + `"VXNlci0xMA=="`) or integer (such as `4`) input value will be accepted as an + ID. + """ + + created_at: datetime + """The time of the event in ISO 8601 UTC format with millisecond precision""" + + payment_method_type: PaymentMethodTypes + """ + The type of payment instrument stored on file (e.g., card, us_bank_account, + cashapp, ideal, sepa_debit). + """ + + sepa_debit: PaymentMethodSepaDebitPaymentMethodSepaDebit + """ + The SEPA Direct Debit-specific details for this payment method, including bank + code and last four IBAN digits. + """ + + typename: Literal["SepaDebitPaymentMethod"] + """The typename of this object""" + + +PaymentMethod: TypeAlias = Annotated[ + Union[ + Optional[PaymentMethodBasePaymentMethod], + Optional[PaymentMethodCardPaymentMethod], + Optional[PaymentMethodUsBankAccountPaymentMethod], + Optional[PaymentMethodCashappPaymentMethod], + Optional[PaymentMethodIdealPaymentMethod], + Optional[PaymentMethodSepaDebitPaymentMethod], + ], + PropertyInfo(discriminator="typename"), +] + + +class Product(BaseModel): + """The access pass being promoted. + + Null for campaigns that don't target a specific product. + """ + + id: str + """The unique identifier for the product.""" + + route: str + """ + The URL slug used in the product's public link (e.g., 'my-product' in + whop.com/company/my-product). + """ + + title: str + """ + The display name of the product shown to customers on the product page and in + search results. + """ + + +class AdCampaignUpdateResponse(BaseModel): + """An advertising campaign running on an external platform or within Whop.""" + + id: str + """The unique identifier for the ad campaign.""" + + available_budget: float + """ + Available budget in dollars, capped at daily budget minus today's spend for + daily campaigns + """ + + billing_ledger_account: Optional[BillingLedgerAccount] = None + """ + The ledger account being charged for platform balance billing (null if using + card) + """ + + clicks_count: int + """Number of clicks""" + + config: Optional[Config] = None + """Meta campaign configuration (objective, budget, bidding, etc.). + + Null for non-Meta campaigns — use `tiktokConfig` for TikTok. + """ + + created_at: datetime + """The datetime the ad campaign was created.""" + + created_by_user: CreatedByUser + """The user who created the campaign""" + + daily_budget: Optional[float] = None + """ + Effective daily budget in dollars — sum of ad group budgets when set, otherwise + campaign-level daily budget + """ + + impressions_count: int + """Number of impressions (views)""" + + paused_until: Optional[datetime] = None + """If temporarily paused, the timestamp when the campaign will auto-resume""" + + payment_method: PaymentMethod + """The payment method used for daily billing (null if using platform balance)""" + + platform: Optional[Literal["meta", "tiktok"]] = None + """The platforms where an ad campaign can run.""" + + product: Optional[Product] = None + """The access pass being promoted. + + Null for campaigns that don't target a specific product. + """ + + purchases_count: int + """Number of purchases""" + + remaining_balance: float + """Remaining balance in dollars""" + + return_on_ad_spend: float + """Return on Ad Spend (ROAS) percentage - revenue generated divided by ad spend""" + + revenue: float + """Total revenue generated from users who converted through this campaign""" + + status: Literal[ + "active", "paused", "inactive", "stale", "pending_refund", "payment_failed", "draft", "in_review", "flagged" + ] + """Current status of the campaign (active, paused, or inactive)""" + + target_country_codes: List[str] + """Array of ISO3166 country codes for territory targeting""" + + title: str + """The title of the ad campaign""" + + todays_spend: float + """Amount spent today in dollars""" + + total_credits: float + """Total credits added to the campaign in dollars""" + + total_spend: float + """Total amount spent on conversions in dollars""" + + updated_at: datetime + """The datetime the ad campaign was last updated.""" diff --git a/src/whop_sdk/types/ad_create_params.py b/src/whop_sdk/types/ad_create_params.py new file mode 100644 index 0000000..26dc747 --- /dev/null +++ b/src/whop_sdk/types/ad_create_params.py @@ -0,0 +1,473 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Iterable, Optional +from typing_extensions import Literal, Required, TypedDict + +from .._types import SequenceNotStr + +__all__ = [ + "AdCreateParams", + "PlatformConfig", + "PlatformConfigMeta", + "PlatformConfigMetaCarouselCard", + "PlatformConfigTiktok", +] + + +class AdCreateParams(TypedDict, total=False): + ad_group_id: Required[str] + """The unique identifier of the ad group to create this ad in.""" + + creative_set_id: Optional[str] + """The unique identifier of the creative set to use.""" + + existing_instagram_media_id: Optional[str] + """ + ID of an existing Instagram media item to use as the ad creative (instead of a + creative set or Facebook post). + """ + + existing_post_id: Optional[str] + """ + ID of an existing Facebook post to use as the ad creative (instead of a creative + set). + """ + + platform_config: Optional[PlatformConfig] + """Platform-specific configuration. Must match the campaign platform.""" + + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] + """The status of an external ad.""" + + +class PlatformConfigMetaCarouselCard(TypedDict, total=False): + """Per-card configuration for a carousel ad.""" + + call_to_action_type: Optional[str] + """CTA button type (e.g., SHOP_NOW, LEARN_MORE).""" + + description: Optional[str] + """Card description (max 30 chars recommended).""" + + link: Optional[str] + """Destination URL for this card (defaults to ad destination).""" + + name: Optional[str] + """Card title (max 35 chars recommended).""" + + +class PlatformConfigMeta(TypedDict, total=False): + """Configuration for Meta (Facebook/Instagram) ads.""" + + call_to_action_type: Optional[ + Literal[ + "LEARN_MORE", + "SHOP_NOW", + "SIGN_UP", + "SUBSCRIBE", + "GET_STARTED", + "BOOK_NOW", + "APPLY_NOW", + "CONTACT_US", + "DOWNLOAD", + "ORDER_NOW", + "BUY_NOW", + "GET_QUOTE", + "MESSAGE_PAGE", + "WHATSAPP_MESSAGE", + "INSTAGRAM_MESSAGE", + "CALL_NOW", + "GET_DIRECTIONS", + "SEND_UPDATES", + "GET_OFFER", + "WATCH_MORE", + "LISTEN_NOW", + "PLAY_GAME", + "OPEN_LINK", + "NO_BUTTON", + "GET_OFFER_VIEW", + "GET_EVENT_TICKETS", + "SEE_MENU", + "REQUEST_TIME", + "EVENT_RSVP", + ] + ] + """Call-to-action button type.""" + + carousel_cards: Optional[Iterable[PlatformConfigMetaCarouselCard]] + """Per-card carousel config.""" + + description: Optional[str] + """Description of the ad creative (legacy single-value).""" + + descriptions: Optional[SequenceNotStr[str]] + """Up to 5 description variants, rendered via Meta asset_feed_spec.""" + + existing_instagram_media_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + existing_post_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + headline: Optional[str] + """Headline of the ad creative (legacy single-value).""" + + headlines: Optional[SequenceNotStr[str]] + """Up to 5 headline variants, rendered via Meta asset_feed_spec.""" + + instagram_actor_id: Optional[str] + """Unique identifier of the Instagram account.""" + + lead_form_config: Optional[Dict[str, object]] + """Lead generation form configuration (JSON).""" + + link_url: Optional[str] + """Destination URL.""" + + multi_advertiser_enrollment: Optional[Literal["OPT_IN", "OPT_OUT"]] + + name: Optional[str] + """Ad name.""" + + page_id: Optional[str] + """Unique identifier of the Facebook Page.""" + + page_welcome_message: Optional[Dict[str, object]] + """Messenger welcome message / ice-breaker template (JSON).""" + + primary_text: Optional[str] + """Primary text of the ad creative (legacy single-value).""" + + primary_texts: Optional[SequenceNotStr[str]] + """Up to 5 primary-text variants, rendered via Meta asset_feed_spec.""" + + url_tags: Optional[str] + """URL query parameters appended to the destination link.""" + + +class PlatformConfigTiktok(TypedDict, total=False): + """Configuration for TikTok ads.""" + + access_pass_tag: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + ad_format: Optional[Literal["SINGLE_IMAGE", "SINGLE_VIDEO", "CAROUSEL_ADS", "CATALOG_CAROUSEL", "LIVE_CONTENT"]] + """ + Ad format (SINGLE_IMAGE, SINGLE_VIDEO, CAROUSEL_ADS, CATALOG_CAROUSEL, + LIVE_CONTENT). + """ + + ad_name: Optional[str] + """Ad name.""" + + ad_text: Optional[str] + """Ad copy (single variant).""" + + ad_texts: Optional[SequenceNotStr[str]] + """Ad copy variants for search ads (up to 5).""" + + aigc_disclosure_type: Optional[Literal["UNSET", "CONTAINS_AIGC", "IS_AIGC", "NOT_AIGC"]] + """Whether the ad creative is AI-generated content. + + See docs/tiktok_api/ad.md § aigc_disclosure_type. + """ + + auto_disclaimer_types: Optional[SequenceNotStr[str]] + """Automatic disclaimer categories (e.g., FINANCE, ALCOHOL).""" + + automate_creative_enabled: Optional[bool] + """Represents `true` or `false` values.""" + + brand_safety_postbid_partner: Optional[Literal["UNSET", "IAS", "DOUBLE_VERIFY", "OPEN_SLATE", "ZEFR"]] + """Post-bid brand-safety vendor. + + See docs/tiktok_api/ad.md § brand_safety_postbid_partner. + """ + + brand_safety_vast_url: Optional[str] + """VAST URL for brand safety measurement.""" + + call_to_action: Optional[ + Literal[ + "LEARN_MORE", + "DOWNLOAD", + "SHOP_NOW", + "SIGN_UP", + "CONTACT_US", + "APPLY_NOW", + "BOOK_NOW", + "PLAY_GAME", + "WATCH_NOW", + "READ_MORE", + "VIEW_NOW", + "GET_QUOTE", + "ORDER_NOW", + "INSTALL_NOW", + "GET_SHOWTIMES", + "LISTEN_NOW", + "INTERESTED", + "SUBSCRIBE", + "GET_TICKETS_NOW", + "EXPERIENCE_NOW", + "PRE_ORDER_NOW", + "VISIT_STORE", + ] + ] + """TikTok call-to-action button text. See docs/tiktok_api/ad.md § call_to_action.""" + + call_to_action_enabled: Optional[bool] + """Represents `true` or `false` values.""" + + call_to_action_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + call_to_action_mode: Optional[Literal["STANDARD", "DYNAMIC"]] + """How the call-to-action text is chosen. + + STANDARD uses a single fixed CTA; DYNAMIC lets TikTok rotate through a set of + CTAs to maximize performance. + """ + + card_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + carousel_image_index: Optional[int] + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + catalog_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + click_tracking_url: Optional[str] + """Third-party click tracker URL.""" + + cpp_url: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + creative_authorized: Optional[bool] + """ + Whether the creator has authorized the use of this creative for paid promotion + (Spark Ads). + """ + + creative_auto_enhancement_strategy_list: Optional[SequenceNotStr[str]] + + dark_post_status: Optional[Literal["ON", "OFF"]] + + deeplink: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + deeplink_format_type: Optional[Literal["UNSET", "DEEPLINK", "DEFERRED_DEEPLINK"]] + """How the ad's deeplink is resolved. + + See docs/tiktok_api/ad.md § deeplink_format_type. + """ + + deeplink_type: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + deeplink_utm_params: Optional[Iterable[Dict[str, object]]] + """UTM params appended to the deeplink.""" + + disclaimer_clickable_texts: Optional[Iterable[Dict[str, object]]] + """Clickable disclaimer segments (text + url).""" + + disclaimer_text: Optional[str] + """Plain text shown when disclaimer_type is DISCLAIMER_TEXT / DISCLAIMER_WITH_URL.""" + + disclaimer_type: Optional[Literal["NONE", "DISCLAIMER_TEXT", "DISCLAIMER_WITH_URL"]] + """Ad disclaimer mode. See docs/tiktok_api/ad.md § disclaimer_type.""" + + dynamic_destination: Optional[str] + """Dynamic destination strategy for shopping ads.""" + + dynamic_format: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + end_card_cta: Optional[str] + """End-card CTA text for video ads.""" + + fallback_type: Optional[Literal["UNSET", "APP_STORE", "LANDING_PAGE"]] + """Destination fallback when a deferred deeplink cannot open the app. + + See docs/tiktok_api/ad.md § fallback_type. + """ + + identity_authorized_bc_id: Optional[str] + """Business Center ID (required when identity_type is BC_AUTH_TT).""" + + identity_id: Optional[str] + """Unique identifier of the identity.""" + + identity_type: Optional[Literal["CUSTOMIZED_USER", "AUTH_CODE", "TT_USER", "BC_AUTH_TT"]] + """Identity type.""" + + image_ids: Optional[SequenceNotStr[str]] + """Unique identifiers of the images.""" + + impression_tracking_url: Optional[str] + """Third-party impression tracker URL.""" + + item_duet_status: Optional[Literal["ENABLE", "DISABLE"]] + + item_group_ids: Optional[SequenceNotStr[str]] + + item_stitch_status: Optional[Literal["ENABLE", "DISABLE"]] + + landing_page_url: Optional[str] + """Landing page URL.""" + + link_url: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + music_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + page_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + product_display_field_list: Optional[SequenceNotStr[str]] + """Fields displayed on dynamic product cards.""" + + product_set_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + product_specific_type: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + promotional_music_disabled: Optional[bool] + """Represents `true` or `false` values.""" + + shopping_ads_fallback_type: Optional[Literal["UNSET", "LANDING_PAGE", "STORE"]] + """Fallback destination for shopping ads when the primary target is unavailable. + + See docs/tiktok_api/ad.md § shopping_ads_fallback_type. + """ + + shopping_ads_video_package_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + showcase_products: Optional[Iterable[Dict[str, object]]] + + sku_ids: Optional[SequenceNotStr[str]] + + tiktok_item_id: Optional[str] + """TikTok item ID for Spark Ads (promotes an organic post).""" + + tracking_app_id: Optional[str] + """TikTok MMP-tracked app ID.""" + + tracking_message_event_set_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + tracking_offline_event_set_ids: Optional[SequenceNotStr[str]] + """Offline event set IDs for attribution.""" + + tracking_pixel_id: Optional[str] + """TikTok pixel ID used for conversion tracking on this ad.""" + + utm_params: Optional[Iterable[Dict[str, object]]] + + vertical_video_strategy: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + video_id: Optional[str] + """Unique identifier of the video.""" + + video_view_tracking_url: Optional[str] + """Third-party video-view tracker URL.""" + + viewability_postbid_partner: Optional[Literal["UNSET", "IAS", "DOUBLE_VERIFY", "MOAT"]] + """Post-bid viewability measurement partner. + + See docs/tiktok_api/ad.md § viewability_postbid_partner. + """ + + viewability_vast_url: Optional[str] + """VAST URL for viewability measurement.""" + + +class PlatformConfig(TypedDict, total=False): + """Platform-specific configuration. Must match the campaign platform.""" + + meta: Optional[PlatformConfigMeta] + """Configuration for Meta (Facebook/Instagram) ads.""" + + tiktok: Optional[PlatformConfigTiktok] + """Configuration for TikTok ads.""" diff --git a/src/whop_sdk/types/ad_create_response.py b/src/whop_sdk/types/ad_create_response.py new file mode 100644 index 0000000..de11a1b --- /dev/null +++ b/src/whop_sdk/types/ad_create_response.py @@ -0,0 +1,249 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel + +__all__ = [ + "AdCreateResponse", + "ExternalAdCreativeSet", + "ExternalAdGroup", + "PlatformConfig", + "PlatformConfigMetaAdPlatformConfigType", + "PlatformConfigTiktokAdPlatformConfigType", +] + + +class ExternalAdCreativeSet(BaseModel): + """The creative set used by this ad.""" + + id: str + """The unique identifier for the external ad creative set.""" + + +class ExternalAdGroup(BaseModel): + """The parent ad group.""" + + id: str + """The unique identifier for the external ad group.""" + + name: Optional[str] = None + """Human-readable ad group name""" + + status: Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"] + """Current operational status of the ad group""" + + +class PlatformConfigMetaAdPlatformConfigType(BaseModel): + """Meta (Facebook/Instagram) ad configuration.""" + + call_to_action_type: Optional[ + Literal[ + "LEARN_MORE", + "SHOP_NOW", + "SIGN_UP", + "SUBSCRIBE", + "GET_STARTED", + "BOOK_NOW", + "APPLY_NOW", + "CONTACT_US", + "DOWNLOAD", + "ORDER_NOW", + "BUY_NOW", + "GET_QUOTE", + "MESSAGE_PAGE", + "WHATSAPP_MESSAGE", + "INSTAGRAM_MESSAGE", + "CALL_NOW", + "GET_DIRECTIONS", + "SEND_UPDATES", + "GET_OFFER", + "WATCH_MORE", + "LISTEN_NOW", + "PLAY_GAME", + "OPEN_LINK", + "NO_BUTTON", + "GET_OFFER_VIEW", + "GET_EVENT_TICKETS", + "SEE_MENU", + "REQUEST_TIME", + "EVENT_RSVP", + ] + ] = None + + headline: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + instagram_actor_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + link_url: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + name: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + page_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + platform: Literal["meta", "tiktok"] + """The ad platform.""" + + primary_text: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + typename: Literal["MetaAdPlatformConfigType"] + """The typename of this object""" + + url_tags: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + +class PlatformConfigTiktokAdPlatformConfigType(BaseModel): + """TikTok ad configuration.""" + + ad_format: Optional[Literal["SINGLE_IMAGE", "SINGLE_VIDEO", "CAROUSEL_ADS", "CATALOG_CAROUSEL", "LIVE_CONTENT"]] = ( + None + ) + + ad_name: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + ad_text: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + call_to_action: Optional[ + Literal[ + "LEARN_MORE", + "DOWNLOAD", + "SHOP_NOW", + "SIGN_UP", + "CONTACT_US", + "APPLY_NOW", + "BOOK_NOW", + "PLAY_GAME", + "WATCH_NOW", + "READ_MORE", + "VIEW_NOW", + "GET_QUOTE", + "ORDER_NOW", + "INSTALL_NOW", + "GET_SHOWTIMES", + "LISTEN_NOW", + "INTERESTED", + "SUBSCRIBE", + "GET_TICKETS_NOW", + "EXPERIENCE_NOW", + "PRE_ORDER_NOW", + "VISIT_STORE", + ] + ] = None + """TikTok call-to-action button text. See docs/tiktok_api/ad.md § call_to_action.""" + + identity_authorized_bc_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + identity_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + identity_type: Optional[Literal["CUSTOMIZED_USER", "AUTH_CODE", "TT_USER", "BC_AUTH_TT"]] = None + + image_ids: Optional[List[str]] = None + + landing_page_url: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + platform: Literal["meta", "tiktok"] + """The ad platform.""" + + typename: Literal["TiktokAdPlatformConfigType"] + """The typename of this object""" + + video_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + +PlatformConfig: TypeAlias = Annotated[ + Union[Optional[PlatformConfigMetaAdPlatformConfigType], Optional[PlatformConfigTiktokAdPlatformConfigType]], + PropertyInfo(discriminator="typename"), +] + + +class AdCreateResponse(BaseModel): + """An ad belonging to an ad group""" + + id: str + """Unique identifier for the ad.""" + + created_at: datetime + """When the ad was created.""" + + external_ad_creative_set: Optional[ExternalAdCreativeSet] = None + """The creative set used by this ad.""" + + external_ad_group: ExternalAdGroup + """The parent ad group.""" + + platform_config: PlatformConfig + """Typed platform-specific configuration.""" + + status: Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"] + """Current status of the ad.""" + + updated_at: datetime + """When the ad was last updated.""" diff --git a/src/whop_sdk/types/ad_group_create_params.py b/src/whop_sdk/types/ad_group_create_params.py new file mode 100644 index 0000000..7d4dc6b --- /dev/null +++ b/src/whop_sdk/types/ad_group_create_params.py @@ -0,0 +1,1219 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, List, Iterable, Optional +from typing_extensions import Literal, Required, TypedDict + +from .._types import SequenceNotStr + +__all__ = [ + "AdGroupCreateParams", + "Config", + "ConfigTargeting", + "PlatformConfig", + "PlatformConfigMeta", + "PlatformConfigMetaAttributionSpec", + "PlatformConfigMetaExcludedGeoLocations", + "PlatformConfigMetaExcludedGeoLocationsCity", + "PlatformConfigMetaExcludedGeoLocationsRegion", + "PlatformConfigMetaExcludedGeoLocationsZip", + "PlatformConfigMetaGeoCity", + "PlatformConfigMetaGeoLocations", + "PlatformConfigMetaGeoLocationsCity", + "PlatformConfigMetaGeoLocationsRegion", + "PlatformConfigMetaGeoLocationsZip", + "PlatformConfigMetaGeoRegion", + "PlatformConfigMetaLeadFormConfig", + "PlatformConfigMetaLeadFormConfigQuestion", + "PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestion", + "PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestionOption", + "PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestionOptionLogic", + "PlatformConfigMetaLeadFormConfigQuestionOption", + "PlatformConfigMetaLeadFormConfigQuestionOptionLogic", + "PlatformConfigMetaLeadFormConfigCustomDisclaimerCheckbox", + "PlatformConfigMetaLeadFormConfigThankYouPage", + "PlatformConfigMetaPromotedObject", + "PlatformConfigMetaTargetingAutomation", + "PlatformConfigTiktok", + "PlatformConfigTiktokAction", + "PlatformConfigTiktokInstantFormConfig", + "PlatformConfigTiktokInstantFormConfigQuestion", +] + + +class AdGroupCreateParams(TypedDict, total=False): + campaign_id: Required[str] + """The ad campaign to create this ad group within.""" + + budget: Optional[float] + """Budget amount in dollars.""" + + budget_type: Optional[Literal["daily", "lifetime"]] + """The budget type for an ad campaign or ad group.""" + + config: Optional[Config] + """Unified ad group configuration (bidding, optimization, targeting).""" + + daily_budget: Optional[float] + """Daily budget in dollars.""" + + name: Optional[str] + """Human-readable ad group name.""" + + platform_config: Optional[PlatformConfig] + """Platform-specific ad group configuration.""" + + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] + """The status of an external ad group.""" + + +class ConfigTargeting(TypedDict, total=False): + """Audience targeting settings (demographics, geo, interests, audiences, devices).""" + + age_max: Optional[int] + """Maximum age for demographic targeting.""" + + age_min: Optional[int] + """Minimum age for demographic targeting.""" + + countries: Optional[SequenceNotStr[str]] + """ISO 3166-1 alpha-2 country codes to target.""" + + device_platforms: Optional[List[Literal["mobile", "desktop"]]] + """Device platforms to target.""" + + exclude_audience_ids: Optional[SequenceNotStr[str]] + """Platform audience IDs to exclude.""" + + genders: Optional[List[Literal["male", "female", "all"]]] + """Genders to target.""" + + include_audience_ids: Optional[SequenceNotStr[str]] + """Platform audience IDs to include.""" + + interest_ids: Optional[SequenceNotStr[str]] + """Platform-specific interest IDs to target.""" + + languages: Optional[SequenceNotStr[str]] + """Language codes to target.""" + + placement_type: Optional[Literal["automatic", "manual"]] + """Placement strategy for ad delivery.""" + + +class Config(TypedDict, total=False): + """Unified ad group configuration (bidding, optimization, targeting).""" + + bid_amount: Optional[int] + """Bid cap amount in cents. Used when bid_strategy is bid_cap or cost_cap.""" + + bid_strategy: Optional[Literal["lowest_cost", "bid_cap", "cost_cap"]] + """Bid strategy: lowest_cost, bid_cap, or cost_cap.""" + + billing_event: Optional[Literal["impressions", "clicks", "optimized_cpm", "video_views"]] + """How you are billed (e.g., impressions, clicks).""" + + end_time: Optional[str] + """Scheduled end time (ISO8601). Required for lifetime budgets.""" + + frequency_cap: Optional[int] + """Maximum number of times to show ads to each person in the frequency interval.""" + + frequency_cap_interval_days: Optional[int] + """Number of days for the frequency cap interval.""" + + optimization_goal: Optional[ + Literal[ + "conversions", + "link_clicks", + "landing_page_views", + "reach", + "impressions", + "app_installs", + "video_views", + "lead_generation", + "value", + "page_likes", + "conversations", + "ad_recall_lift", + "two_second_continuous_video_views", + "post_engagement", + "event_responses", + "reminders_set", + "quality_lead", + ] + ] + """What the ad group optimizes for (e.g., conversions, link_clicks, reach).""" + + pacing: Optional[Literal["standard", "accelerated"]] + """Budget pacing: standard (even) or accelerated (fast).""" + + start_time: Optional[str] + """Scheduled start time (ISO8601).""" + + targeting: Optional[ConfigTargeting] + """Audience targeting settings (demographics, geo, interests, audiences, devices).""" + + +class PlatformConfigMetaAttributionSpec(TypedDict, total=False): + """Meta conversion attribution window.""" + + event_type: Required[str] + """Attribution event type (e.g., CLICK_THROUGH, VIEW_THROUGH).""" + + window_days: Required[int] + """Attribution window in days (1, 7, 28).""" + + +class PlatformConfigMetaExcludedGeoLocationsCity(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaExcludedGeoLocationsRegion(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaExcludedGeoLocationsZip(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaExcludedGeoLocations(TypedDict, total=False): + """Geo locations to exclude.""" + + cities: Optional[Iterable[PlatformConfigMetaExcludedGeoLocationsCity]] + """City targets.""" + + countries: Optional[SequenceNotStr[str]] + """ISO 3166-1 alpha-2 country codes.""" + + location_types: Optional[SequenceNotStr[str]] + """Location types (home, recent, travel_in).""" + + regions: Optional[Iterable[PlatformConfigMetaExcludedGeoLocationsRegion]] + """Region/state targets.""" + + zips: Optional[Iterable[PlatformConfigMetaExcludedGeoLocationsZip]] + """Zip/postal code targets.""" + + +class PlatformConfigMetaGeoCity(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaGeoLocationsCity(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaGeoLocationsRegion(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaGeoLocationsZip(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaGeoLocations(TypedDict, total=False): + """Geo targeting (countries, regions, cities, zips).""" + + cities: Optional[Iterable[PlatformConfigMetaGeoLocationsCity]] + """City targets.""" + + countries: Optional[SequenceNotStr[str]] + """ISO 3166-1 alpha-2 country codes.""" + + location_types: Optional[SequenceNotStr[str]] + """Location types (home, recent, travel_in).""" + + regions: Optional[Iterable[PlatformConfigMetaGeoLocationsRegion]] + """Region/state targets.""" + + zips: Optional[Iterable[PlatformConfigMetaGeoLocationsZip]] + """Zip/postal code targets.""" + + +class PlatformConfigMetaGeoRegion(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestionOptionLogic(TypedDict, total=False): + """Conditional logic routing for this answer option.""" + + type: Required[str] + """Logic type: go_to_question, submit_form, or close_form.""" + + target_end_page_index: Optional[int] + """Index of the end page to route to (for submit_form type).""" + + target_question_index: Optional[int] + """Index of the question to route to (for go_to_question type).""" + + +class PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestionOption(TypedDict, total=False): + """An answer option for a multiple choice lead form question.""" + + key: Required[str] + """Unique key for this option.""" + + value: Required[str] + """Display text for this option.""" + + logic: Optional[PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestionOptionLogic] + """Conditional logic routing for this answer option.""" + + +class PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestion(TypedDict, total=False): + """A dependent conditional question (non-recursive to avoid schema recursion).""" + + type: Required[str] + """Question type (EMAIL, FULL_NAME, PHONE, CUSTOM, DATE_TIME, etc.).""" + + inline_context: Optional[str] + """Helper text shown below the question.""" + + key: Optional[str] + """Unique key for this question.""" + + label: Optional[str] + """Custom label for CUSTOM questions.""" + + options: Optional[Iterable[PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestionOption]] + """Answer options for multiple choice questions.""" + + +class PlatformConfigMetaLeadFormConfigQuestionOptionLogic(TypedDict, total=False): + """Conditional logic routing for this answer option.""" + + type: Required[str] + """Logic type: go_to_question, submit_form, or close_form.""" + + target_end_page_index: Optional[int] + """Index of the end page to route to (for submit_form type).""" + + target_question_index: Optional[int] + """Index of the question to route to (for go_to_question type).""" + + +class PlatformConfigMetaLeadFormConfigQuestionOption(TypedDict, total=False): + """An answer option for a multiple choice lead form question.""" + + key: Required[str] + """Unique key for this option.""" + + value: Required[str] + """Display text for this option.""" + + logic: Optional[PlatformConfigMetaLeadFormConfigQuestionOptionLogic] + """Conditional logic routing for this answer option.""" + + +class PlatformConfigMetaLeadFormConfigQuestion(TypedDict, total=False): + """A question on a Meta lead gen form.""" + + type: Required[str] + """Question type (EMAIL, FULL_NAME, PHONE, CUSTOM, DATE_TIME, etc.).""" + + conditional_questions_group_id: Optional[str] + """Group ID for conditional question routing.""" + + dependent_conditional_questions: Optional[ + Iterable[PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestion] + ] + """Questions shown conditionally based on this question's answer.""" + + inline_context: Optional[str] + """Helper text shown below the question.""" + + key: Optional[str] + """Unique key for this question.""" + + label: Optional[str] + """Custom label for CUSTOM questions.""" + + options: Optional[Iterable[PlatformConfigMetaLeadFormConfigQuestionOption]] + """Answer options for multiple choice CUSTOM questions.""" + + question_format: Optional[str] + """UI hint: short_answer, multiple_choice, or appointment.""" + + +class PlatformConfigMetaLeadFormConfigCustomDisclaimerCheckbox(TypedDict, total=False): + """A consent checkbox for the custom disclaimer section.""" + + key: Required[str] + """Unique key for this checkbox.""" + + text: Required[str] + """Label text for the checkbox.""" + + is_checked_by_default: Optional[bool] + """Whether the checkbox is checked by default.""" + + is_required: Optional[bool] + """Whether the checkbox must be checked to submit.""" + + +class PlatformConfigMetaLeadFormConfigThankYouPage(TypedDict, total=False): + """A thank-you / ending page for a Meta lead gen form.""" + + body: Optional[str] + """Body text for this ending page.""" + + business_phone: Optional[str] + """Business phone number for call CTA.""" + + button_text: Optional[str] + """Custom button text.""" + + button_type: Optional[str] + """CTA button type: VIEW_WEBSITE, CALL_BUSINESS, DOWNLOAD.""" + + conditional_question_group_id: Optional[str] + """Question group ID for conditional routing to this page.""" + + enable_messenger: Optional[bool] + """Enable Messenger follow-up.""" + + gated_file_url: Optional[str] + """Uploaded file URL for gated content download.""" + + link: Optional[str] + """URL the button links to.""" + + name: Optional[str] + """Internal name for this ending page.""" + + title: Optional[str] + """Headline for this ending page.""" + + +class PlatformConfigMetaLeadFormConfig(TypedDict, total=False): + """Configuration for a Meta lead gen instant form.""" + + name: Required[str] + """Name of the lead form.""" + + privacy_policy_url: Required[str] + """URL to your privacy policy. Required by Meta.""" + + questions: Required[Iterable[PlatformConfigMetaLeadFormConfigQuestion]] + """Questions to ask on the form.""" + + background_image_source: Optional[str] + """Background image source: from_ad or custom.""" + + background_image_url: Optional[str] + """URL of custom background image.""" + + conditional_logic_enabled: Optional[bool] + """Whether conditional logic is enabled for questions.""" + + context_card_button_text: Optional[str] + """CTA button text on the greeting card.""" + + context_card_content: Optional[SequenceNotStr[str]] + """Optional greeting card bullet points.""" + + context_card_style: Optional[str] + """Greeting layout: PARAGRAPH_STYLE or LIST_STYLE.""" + + context_card_title: Optional[str] + """Optional greeting card title.""" + + custom_disclaimer_body: Optional[str] + """Custom disclaimer body text.""" + + custom_disclaimer_checkboxes: Optional[Iterable[PlatformConfigMetaLeadFormConfigCustomDisclaimerCheckbox]] + """Consent checkboxes for the custom disclaimer.""" + + custom_disclaimer_title: Optional[str] + """Custom disclaimer section title.""" + + form_type: Optional[str] + """Form type: more_volume, higher_intent, or rich_creative.""" + + messenger_enabled: Optional[bool] + """Enable Messenger follow-up after form submission.""" + + phone_verification_enabled: Optional[bool] + """Require phone number verification via OTP (higher_intent only).""" + + privacy_policy_link_text: Optional[str] + """Custom link text for privacy policy (max 70 chars).""" + + question_page_custom_headline: Optional[str] + """Custom headline for the questions page.""" + + rich_creative_headline: Optional[str] + """Headline for rich creative form intro.""" + + rich_creative_overview: Optional[str] + """Overview description for rich creative form intro.""" + + rich_creative_url: Optional[str] + """Uploaded image URL for rich creative form type.""" + + thank_you_pages: Optional[Iterable[PlatformConfigMetaLeadFormConfigThankYouPage]] + """Thank you / ending pages (supports multiple for conditional routing).""" + + +class PlatformConfigMetaPromotedObject(TypedDict, total=False): + """The object this ad set promotes (pixel, page, etc.).""" + + custom_conversion_id: Optional[str] + """Custom conversion rule ID (numeric, from Meta Events Manager).""" + + custom_event_str: Optional[str] + """Pixel event name, used when custom_event_type is OTHER.""" + + custom_event_type: Optional[str] + """Custom event type (e.g., PURCHASE, COMPLETE_REGISTRATION, OTHER).""" + + page_id: Optional[str] + """Facebook Page ID.""" + + pixel_id: Optional[str] + """Meta Pixel ID for conversion tracking.""" + + whatsapp_phone_number: Optional[str] + """WhatsApp phone number for messaging campaigns.""" + + +class PlatformConfigMetaTargetingAutomation(TypedDict, total=False): + """Advantage+ audience expansion settings.""" + + advantage_audience: Optional[int] + """0 = off (use exact targeting), 1 = on (let Meta expand audience).""" + + +class PlatformConfigMeta(TypedDict, total=False): + """Meta (Facebook/Instagram) ad set configuration.""" + + android_devices: Optional[SequenceNotStr[str]] + + attribution_setting: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + attribution_spec: Optional[Iterable[PlatformConfigMetaAttributionSpec]] + """Conversion attribution windows.""" + + audience_network_positions: Optional[SequenceNotStr[str]] + + audience_type: Optional[str] + """Audience type for retargeting.""" + + bid_amount: Optional[int] + """Bid amount in cents.""" + + bid_strategy: Optional[ + Literal["LOWEST_COST_WITHOUT_CAP", "LOWEST_COST_WITH_BID_CAP", "COST_CAP", "LOWEST_COST_WITH_MIN_ROAS"] + ] + """Meta bid strategy.""" + + billing_event: Optional[ + Literal[ + "APP_INSTALLS", + "CLICKS", + "IMPRESSIONS", + "LINK_CLICKS", + "NONE", + "OFFER_CLAIMS", + "PAGE_LIKES", + "POST_ENGAGEMENT", + "THRUPLAY", + "PURCHASE", + "LISTING_INTERACTION", + ] + ] + """How you are billed on Meta.""" + + brand_safety_content_filter_levels: Optional[SequenceNotStr[str]] + + budget_remaining: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + cost_per_result_goal: Optional[float] + """ + Represents signed double-precision fractional values as specified by + [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point). + """ + + created_time: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + daily_budget: Optional[int] + """Daily budget in cents.""" + + daily_min_spend_target: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + daily_spend_cap: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + destination_type: Optional[ + Literal[ + "UNDEFINED", + "WEBSITE", + "APP", + "FACEBOOK", + "MESSENGER", + "WHATSAPP", + "INSTAGRAM_DIRECT", + "INSTAGRAM_PROFILE", + "PHONE_CALL", + "SHOP_AUTOMATIC", + "APPLINKS_AUTOMATIC", + "ON_AD", + "ON_POST", + "ON_VIDEO", + "ON_PAGE", + "ON_EVENT", + "MESSAGING_MESSENGER_WHATSAPP", + "MESSAGING_INSTAGRAM_DIRECT_MESSENGER", + "MESSAGING_INSTAGRAM_DIRECT_WHATSAPP", + "MESSAGING_INSTAGRAM_DIRECT_MESSENGER_WHATSAPP", + "INSTAGRAM_PROFILE_AND_FACEBOOK_PAGE", + "FACEBOOK_PAGE", + "INSTAGRAM_LIVE", + "FACEBOOK_LIVE", + "IMAGINE", + "LEAD_FROM_IG_DIRECT", + "LEAD_FROM_MESSENGER", + "WEBSITE_AND_LEAD_FORM", + "WEBSITE_AND_PHONE_CALL", + "BROADCAST_CHANNEL", + ] + ] + """Where ads in this ad set direct people.""" + + dsa_beneficiary: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + dsa_payor: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + end_time: Optional[str] + """End time (ISO8601). Required for lifetime budgets.""" + + excluded_geo_locations: Optional[PlatformConfigMetaExcludedGeoLocations] + """Geo locations to exclude.""" + + facebook_positions: Optional[SequenceNotStr[str]] + """Facebook ad placements (feed, reels, stories, etc.).""" + + frequency_control_count: Optional[int] + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + frequency_control_days: Optional[int] + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + frequency_control_type: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + geo_cities: Optional[Iterable[PlatformConfigMetaGeoCity]] + + geo_locations: Optional[PlatformConfigMetaGeoLocations] + """Geo targeting (countries, regions, cities, zips).""" + + geo_regions: Optional[Iterable[PlatformConfigMetaGeoRegion]] + + geo_zips: Optional[SequenceNotStr[str]] + + instagram_actor_id: Optional[str] + """Instagram account ID for this ad set.""" + + instagram_positions: Optional[SequenceNotStr[str]] + """Instagram ad placements (stream, story, reels, etc.).""" + + ios_devices: Optional[SequenceNotStr[str]] + + is_dynamic_creative: Optional[bool] + """Represents `true` or `false` values.""" + + lead_conversion_location: Optional[Literal["website", "instant_forms", "messenger", "instagram", "calls", "app"]] + + lead_form_config: Optional[PlatformConfigMetaLeadFormConfig] + """Configuration for a Meta lead gen instant form.""" + + lead_gen_form_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + lifetime_budget: Optional[int] + """Lifetime budget in cents.""" + + lifetime_min_spend_target: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + lifetime_spend_cap: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + location_types: Optional[SequenceNotStr[str]] + + messenger_positions: Optional[SequenceNotStr[str]] + + optimization_goal: Optional[ + Literal[ + "NONE", + "APP_INSTALLS", + "AD_RECALL_LIFT", + "ENGAGED_USERS", + "EVENT_RESPONSES", + "IMPRESSIONS", + "LEAD_GENERATION", + "QUALITY_LEAD", + "LINK_CLICKS", + "OFFSITE_CONVERSIONS", + "PAGE_LIKES", + "POST_ENGAGEMENT", + "QUALITY_CALL", + "REACH", + "LANDING_PAGE_VIEWS", + "VISIT_INSTAGRAM_PROFILE", + "VALUE", + "THRUPLAY", + "DERIVED_EVENTS", + "APP_INSTALLS_AND_OFFSITE_CONVERSIONS", + "CONVERSATIONS", + "IN_APP_VALUE", + "MESSAGING_PURCHASE_CONVERSION", + "SUBSCRIBERS", + "REMINDERS_SET", + "MEANINGFUL_CALL_ATTEMPT", + "PROFILE_VISIT", + "PROFILE_AND_PAGE_ENGAGEMENT", + "TWO_SECOND_CONTINUOUS_VIDEO_VIEWS", + "ENGAGED_REACH", + "ENGAGED_PAGE_VIEWS", + "MESSAGING_DEEP_CONVERSATION_AND_FOLLOW", + "ADVERTISER_SILOED_VALUE", + "AUTOMATIC_OBJECTIVE", + "MESSAGING_APPOINTMENT_CONVERSION", + ] + ] + """What this ad set optimizes for on Meta.""" + + page_id: Optional[str] + """Facebook Page ID for this ad set.""" + + pixel_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + promoted_object: Optional[PlatformConfigMetaPromotedObject] + """The object this ad set promotes (pixel, page, etc.).""" + + publisher_platforms: Optional[SequenceNotStr[str]] + """Platforms to publish on (facebook, instagram, messenger, audience_network).""" + + source_adset_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + start_time: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + status: Optional[Literal["ACTIVE", "PAUSED"]] + + targeting_automation: Optional[PlatformConfigMetaTargetingAutomation] + """Advantage+ audience expansion settings.""" + + threads_positions: Optional[SequenceNotStr[str]] + + updated_time: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + user_device: Optional[SequenceNotStr[str]] + + user_os: Optional[SequenceNotStr[str]] + + whatsapp_phone_number: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + whatsapp_positions: Optional[SequenceNotStr[str]] + + +class PlatformConfigTiktokAction(TypedDict, total=False): + """A single TikTok behavioral targeting entry. + + One category of past user behavior (what they did, over what window, on which kind of content). See docs/tiktok_api/ad_group.md § actions. + """ + + action_category_ids: Optional[SequenceNotStr[str]] + """Behavioral category IDs. Use /tool/action_category/ to list them.""" + + action_period: Optional[int] + """Lookback window in days. TikTok accepts 7, 15, 30, 60, 90, or 180.""" + + action_scene: Optional[Literal["VIDEO_RELATED", "CREATOR_RELATED", "HASHTAG_RELATED", "LIVE_RELATED"]] + """The category of TikTok content a behavioral targeting rule applies to. + + See docs/tiktok_api/ad_group.md § actions. + """ + + video_user_actions: Optional[ + List[Literal["WATCHED_TO_END", "LIKED", "COMMENTED", "SHARED", "FOLLOWED", "PROFILE_VISITED"]] + ] + """ + Specific video interactions (WATCHED_TO_END, LIKED, COMMENTED, SHARED, FOLLOWED, + PROFILE_VISITED). + """ + + +class PlatformConfigTiktokInstantFormConfigQuestion(TypedDict, total=False): + """A question for a TikTok instant form.""" + + field_type: Required[str] + """Question type (EMAIL, PHONE_NUMBER, NAME, CUSTOM).""" + + label: Optional[str] + """Custom label for the question.""" + + +class PlatformConfigTiktokInstantFormConfig(TypedDict, total=False): + """Instant form configuration for lead generation campaigns.""" + + privacy_policy_url: Required[str] + """URL to your privacy policy.""" + + questions: Required[Iterable[PlatformConfigTiktokInstantFormConfigQuestion]] + """Form questions (at least one required).""" + + button_text: Optional[str] + """Submit button text.""" + + greeting: Optional[str] + """Greeting text shown at the top of the form.""" + + name: Optional[str] + """Form name. Auto-generated if omitted.""" + + +class PlatformConfigTiktok(TypedDict, total=False): + """TikTok ad group configuration.""" + + actions: Optional[Iterable[PlatformConfigTiktokAction]] + + age_groups: Optional[List[Literal["AGE_13_17", "AGE_18_24", "AGE_25_34", "AGE_35_44", "AGE_45_54", "AGE_55_100"]]] + + app_id: Optional[str] + """App ID for app promotion campaigns.""" + + attribution_event_count: Optional[Literal["UNSET", "EVERY", "ONCE"]] + + audience_ids: Optional[SequenceNotStr[str]] + + audience_rule: Optional[Dict[str, object]] + """Represents untyped JSON""" + + audience_type: Optional[Literal["NORMAL", "SMART_INTERESTS_BEHAVIORS"]] + + bid_price: Optional[float] + """Bid price (cost per result for Cost Cap).""" + + bid_type: Optional[Literal["BID_TYPE_NO_BID", "BID_TYPE_CUSTOM"]] + """Bidding strategy (BID_TYPE_NO_BID, BID_TYPE_CUSTOM).""" + + billing_event: Optional[Literal["CPC", "CPM", "OCPM", "CPV"]] + """How you are billed on TikTok (CPC, CPM, OCPM, CPV).""" + + brand_safety_type: Optional[ + Literal["NO_BRAND_SAFETY", "STANDARD_INVENTORY", "LIMITED_INVENTORY", "FULL_INVENTORY", "EXPANDED_INVENTORY"] + ] + + budget_mode: Optional[Literal["BUDGET_MODE_DAY", "BUDGET_MODE_TOTAL", "BUDGET_MODE_DYNAMIC_DAILY_BUDGET"]] + """ + Budget mode (BUDGET_MODE_DAY, BUDGET_MODE_TOTAL, + BUDGET_MODE_DYNAMIC_DAILY_BUDGET). + """ + + carrier_ids: Optional[SequenceNotStr[str]] + + category_exclusion_ids: Optional[SequenceNotStr[str]] + + click_attribution_window: Optional[Literal["OFF", "ONE_DAY", "SEVEN_DAYS", "FOURTEEN_DAYS", "TWENTY_EIGHT_DAYS"]] + + comment_disabled: Optional[bool] + """Represents `true` or `false` values.""" + + contextual_tag_ids: Optional[SequenceNotStr[str]] + + conversion_bid_price: Optional[float] + """Target cost per conversion for oCPM.""" + + creative_material_mode: Optional[str] + """Creative delivery strategy.""" + + dayparting: Optional[str] + """Ad delivery schedule (48x7 character string).""" + + deep_funnel_event_source: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + deep_funnel_event_source_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + deep_funnel_optimization_status: Optional[Literal["ON", "OFF"]] + + device_model_ids: Optional[SequenceNotStr[str]] + + device_price_ranges: Optional[SequenceNotStr[str]] + + engaged_view_attribution_window: Optional[Literal["OFF", "ONE_DAY", "SEVEN_DAYS"]] + + excluded_audience_ids: Optional[SequenceNotStr[str]] + + excluded_location_ids: Optional[SequenceNotStr[str]] + """TikTok location/region IDs to exclude.""" + + frequency: Optional[int] + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + frequency_schedule: Optional[int] + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + gender: Optional[Literal["GENDER_UNLIMITED", "GENDER_MALE", "GENDER_FEMALE"]] + + identity_authorized_bc_id: Optional[str] + """Business Center ID for BC_AUTH_TT identity.""" + + identity_id: Optional[str] + """TikTok identity ID for the ad group.""" + + identity_type: Optional[str] + """Identity type (AUTH_CODE, TT_USER, BC_AUTH_TT).""" + + instant_form_config: Optional[PlatformConfigTiktokInstantFormConfig] + """Instant form configuration for lead generation campaigns.""" + + instant_form_id: Optional[str] + """ + TikTok instant form ID (set automatically when instant_form_config is provided). + """ + + interest_category_ids: Optional[SequenceNotStr[str]] + + interest_keyword_ids: Optional[SequenceNotStr[str]] + + inventory_filter_enabled: Optional[bool] + """Represents `true` or `false` values.""" + + ios14_targeting: Optional[Literal["UNSET", "IOS14_MINUS", "IOS14_PLUS", "ALL"]] + + isp_ids: Optional[SequenceNotStr[str]] + + languages: Optional[SequenceNotStr[str]] + + location_ids: Optional[SequenceNotStr[str]] + """TikTok location/region IDs for geo targeting.""" + + min_android_version: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + min_ios_version: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + network_types: Optional[SequenceNotStr[str]] + + operating_systems: Optional[List[Literal["ANDROID", "IOS"]]] + + operation_status: Optional[Literal["ENABLE", "DISABLE"]] + """Initial status (ENABLE, DISABLE).""" + + optimization_event: Optional[str] + """Conversion event (e.g., COMPLETE_PAYMENT).""" + + optimization_goal: Optional[ + Literal[ + "CLICK", + "CONVERT", + "INSTALL", + "IN_APP_EVENT", + "REACH", + "SHOW", + "VIDEO_VIEW", + "ENGAGED_VIEW", + "ENGAGED_VIEW_FIFTEEN", + "LEAD_GENERATION", + "PREFERRED_LEAD", + "CONVERSATION", + "FOLLOWERS", + "PROFILE_VIEWS", + "PAGE_VISIT", + "VALUE", + "AUTOMATIC_VALUE_OPTIMIZATION", + "TRAFFIC_LANDING_PAGE_VIEW", + "DESTINATION_VISIT", + "MT_LIVE_ROOM", + "PRODUCT_CLICK_IN_LIVE", + ] + ] + """What this ad group optimizes for on TikTok.""" + + pacing: Optional[Literal["PACING_MODE_SMOOTH", "PACING_MODE_FAST"]] + """Budget pacing (PACING_MODE_SMOOTH, PACING_MODE_FAST).""" + + pangle_audience_package_exclude_ids: Optional[SequenceNotStr[str]] + + pangle_audience_package_include_ids: Optional[SequenceNotStr[str]] + + pangle_block_app_ids: Optional[SequenceNotStr[str]] + + pixel_id: Optional[str] + """TikTok Pixel ID for conversion tracking.""" + + placement_type: Optional[Literal["PLACEMENT_TYPE_AUTOMATIC", "PLACEMENT_TYPE_NORMAL"]] + """Placement strategy (PLACEMENT_TYPE_AUTOMATIC, PLACEMENT_TYPE_NORMAL).""" + + placements: Optional[SequenceNotStr[str]] + """Placements (PLACEMENT_TIKTOK, PLACEMENT_PANGLE, etc.).""" + + product_set_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + product_source: Optional[Literal["CATALOG", "STORE", "SHOWCASE"]] + + promotion_type: Optional[str] + """Promotion type (optimization location).""" + + schedule_end_time: Optional[str] + """Schedule end time (UTC, YYYY-MM-DD HH:MM:SS).""" + + schedule_start_time: Optional[str] + """Schedule start time (UTC, YYYY-MM-DD HH:MM:SS).""" + + schedule_type: Optional[Literal["SCHEDULE_START_END", "SCHEDULE_FROM_NOW"]] + """Schedule type (SCHEDULE_START_END, SCHEDULE_FROM_NOW).""" + + secondary_optimization_event: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + shopping_ads_retargeting_actions_days: Optional[int] + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + shopping_ads_retargeting_type: Optional[Literal["OFF", "LAB1", "LAB2", "LAB3", "LAB4", "LAB5"]] + + spending_power: Optional[Literal["ALL", "HIGH"]] + + tiktok_subplacements: Optional[SequenceNotStr[str]] + """TikTok subplacements (IN_FEED, SEARCH_FEED, etc.).""" + + vertical_sensitivity_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + video_download_disabled: Optional[bool] + """Represents `true` or `false` values.""" + + video_user_actions: Optional[SequenceNotStr[str]] + + view_attribution_window: Optional[Literal["OFF", "ONE_DAY", "SEVEN_DAYS"]] + + +class PlatformConfig(TypedDict, total=False): + """Platform-specific ad group configuration.""" + + meta: Optional[PlatformConfigMeta] + """Meta (Facebook/Instagram) ad set configuration.""" + + tiktok: Optional[PlatformConfigTiktok] + """TikTok ad group configuration.""" diff --git a/src/whop_sdk/types/ad_group_create_response.py b/src/whop_sdk/types/ad_group_create_response.py new file mode 100644 index 0000000..6c17368 --- /dev/null +++ b/src/whop_sdk/types/ad_group_create_response.py @@ -0,0 +1,438 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel + +__all__ = [ + "AdGroupCreateResponse", + "AdCampaign", + "Config", + "ConfigTargeting", + "PlatformConfig", + "PlatformConfigMetaAdGroupPlatformConfigType", + "PlatformConfigTiktokAdGroupPlatformConfigType", +] + + +class AdCampaign(BaseModel): + """The parent ad campaign""" + + id: str + """The unique identifier for the ad campaign.""" + + platform: Optional[Literal["meta", "tiktok"]] = None + """The platforms where an ad campaign can run.""" + + status: Literal[ + "active", "paused", "inactive", "stale", "pending_refund", "payment_failed", "draft", "in_review", "flagged" + ] + """Current status of the campaign (active, paused, or inactive)""" + + title: str + """The title of the ad campaign""" + + +class ConfigTargeting(BaseModel): + """Audience targeting settings (demographics, geo, interests, audiences, devices).""" + + age_max: Optional[int] = None + """Maximum age for demographic targeting.""" + + age_min: Optional[int] = None + """Minimum age for demographic targeting.""" + + countries: Optional[List[str]] = None + """ISO 3166-1 alpha-2 country codes targeted.""" + + device_platforms: Optional[List[Literal["mobile", "desktop"]]] = None + """Device platforms targeted.""" + + exclude_audience_ids: Optional[List[str]] = None + """Platform audience IDs excluded.""" + + genders: Optional[List[Literal["male", "female", "all"]]] = None + """Genders targeted.""" + + include_audience_ids: Optional[List[str]] = None + """Platform audience IDs included.""" + + interest_ids: Optional[List[str]] = None + """Platform-specific interest IDs targeted.""" + + languages: Optional[List[str]] = None + """Language codes targeted.""" + + placement_type: Optional[Literal["automatic", "manual"]] = None + """Placement strategy for ad delivery.""" + + +class Config(BaseModel): + """Unified ad group configuration (platform-agnostic)""" + + bid_amount: Optional[int] = None + """Bid cap amount in cents. Used when bid_strategy is bid_cap or cost_cap.""" + + bid_strategy: Optional[Literal["lowest_cost", "bid_cap", "cost_cap"]] = None + """Bid strategy: lowest_cost, bid_cap, or cost_cap.""" + + billing_event: Optional[Literal["impressions", "clicks", "optimized_cpm", "video_views"]] = None + """How you are billed (e.g., impressions, clicks).""" + + end_time: Optional[str] = None + """Scheduled end time (ISO8601). Required for lifetime budgets.""" + + frequency_cap: Optional[int] = None + """Maximum number of times to show ads to each person in the frequency interval.""" + + frequency_cap_interval_days: Optional[int] = None + """Number of days for the frequency cap interval.""" + + optimization_goal: Optional[ + Literal[ + "conversions", + "link_clicks", + "landing_page_views", + "reach", + "impressions", + "app_installs", + "video_views", + "lead_generation", + "value", + "page_likes", + "conversations", + "ad_recall_lift", + "two_second_continuous_video_views", + "post_engagement", + "event_responses", + "reminders_set", + "quality_lead", + ] + ] = None + """What the ad group optimizes for (e.g., conversions, link_clicks, reach).""" + + pacing: Optional[Literal["standard", "accelerated"]] = None + """Budget pacing: standard (even) or accelerated (fast).""" + + start_time: Optional[str] = None + """Scheduled start time (ISO8601).""" + + targeting: Optional[ConfigTargeting] = None + """Audience targeting settings (demographics, geo, interests, audiences, devices).""" + + +class PlatformConfigMetaAdGroupPlatformConfigType(BaseModel): + """Meta (Facebook/Instagram) ad set configuration.""" + + attribution_spec: Optional[List[Dict[str, object]]] = None + + bid_amount: Optional[int] = None + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + bid_strategy: Optional[ + Literal["LOWEST_COST_WITHOUT_CAP", "LOWEST_COST_WITH_BID_CAP", "COST_CAP", "LOWEST_COST_WITH_MIN_ROAS"] + ] = None + + billing_event: Optional[ + Literal[ + "APP_INSTALLS", + "CLICKS", + "IMPRESSIONS", + "LINK_CLICKS", + "NONE", + "OFFER_CLAIMS", + "PAGE_LIKES", + "POST_ENGAGEMENT", + "THRUPLAY", + "PURCHASE", + "LISTING_INTERACTION", + ] + ] = None + + daily_budget: Optional[int] = None + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + destination_type: Optional[ + Literal[ + "UNDEFINED", + "WEBSITE", + "APP", + "FACEBOOK", + "MESSENGER", + "WHATSAPP", + "INSTAGRAM_DIRECT", + "INSTAGRAM_PROFILE", + "PHONE_CALL", + "SHOP_AUTOMATIC", + "APPLINKS_AUTOMATIC", + "ON_AD", + "ON_POST", + "ON_VIDEO", + "ON_PAGE", + "ON_EVENT", + "MESSAGING_MESSENGER_WHATSAPP", + "MESSAGING_INSTAGRAM_DIRECT_MESSENGER", + "MESSAGING_INSTAGRAM_DIRECT_WHATSAPP", + "MESSAGING_INSTAGRAM_DIRECT_MESSENGER_WHATSAPP", + "INSTAGRAM_PROFILE_AND_FACEBOOK_PAGE", + "FACEBOOK_PAGE", + "INSTAGRAM_LIVE", + "FACEBOOK_LIVE", + "IMAGINE", + "LEAD_FROM_IG_DIRECT", + "LEAD_FROM_MESSENGER", + "WEBSITE_AND_LEAD_FORM", + "WEBSITE_AND_PHONE_CALL", + "BROADCAST_CHANNEL", + ] + ] = None + + end_time: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + excluded_geo_locations: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + facebook_positions: Optional[List[str]] = None + + geo_locations: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + instagram_actor_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + instagram_positions: Optional[List[str]] = None + + lifetime_budget: Optional[int] = None + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + optimization_goal: Optional[ + Literal[ + "NONE", + "APP_INSTALLS", + "AD_RECALL_LIFT", + "ENGAGED_USERS", + "EVENT_RESPONSES", + "IMPRESSIONS", + "LEAD_GENERATION", + "QUALITY_LEAD", + "LINK_CLICKS", + "OFFSITE_CONVERSIONS", + "PAGE_LIKES", + "POST_ENGAGEMENT", + "QUALITY_CALL", + "REACH", + "LANDING_PAGE_VIEWS", + "VISIT_INSTAGRAM_PROFILE", + "VALUE", + "THRUPLAY", + "DERIVED_EVENTS", + "APP_INSTALLS_AND_OFFSITE_CONVERSIONS", + "CONVERSATIONS", + "IN_APP_VALUE", + "MESSAGING_PURCHASE_CONVERSION", + "SUBSCRIBERS", + "REMINDERS_SET", + "MEANINGFUL_CALL_ATTEMPT", + "PROFILE_VISIT", + "PROFILE_AND_PAGE_ENGAGEMENT", + "TWO_SECOND_CONTINUOUS_VIDEO_VIEWS", + "ENGAGED_REACH", + "ENGAGED_PAGE_VIEWS", + "MESSAGING_DEEP_CONVERSATION_AND_FOLLOW", + "ADVERTISER_SILOED_VALUE", + "AUTOMATIC_OBJECTIVE", + "MESSAGING_APPOINTMENT_CONVERSION", + ] + ] = None + + page_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + platform: Literal["meta", "tiktok"] + """The ad platform (meta, tiktok).""" + + promoted_object: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + publisher_platforms: Optional[List[str]] = None + + status: Optional[Literal["ACTIVE", "PAUSED"]] = None + + targeting_automation: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + typename: Literal["MetaAdGroupPlatformConfigType"] + """The typename of this object""" + + +class PlatformConfigTiktokAdGroupPlatformConfigType(BaseModel): + """TikTok ad group configuration.""" + + bid_price: Optional[float] = None + """ + Represents signed double-precision fractional values as specified by + [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point). + """ + + bid_type: Optional[Literal["BID_TYPE_NO_BID", "BID_TYPE_CUSTOM"]] = None + + billing_event: Optional[Literal["CPC", "CPM", "OCPM", "CPV"]] = None + + budget_mode: Optional[Literal["BUDGET_MODE_DAY", "BUDGET_MODE_TOTAL", "BUDGET_MODE_DYNAMIC_DAILY_BUDGET"]] = None + + conversion_bid_price: Optional[float] = None + """ + Represents signed double-precision fractional values as specified by + [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point). + """ + + identity_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + identity_type: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + operation_status: Optional[Literal["ENABLE", "DISABLE"]] = None + + optimization_event: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + optimization_goal: Optional[ + Literal[ + "CLICK", + "CONVERT", + "INSTALL", + "IN_APP_EVENT", + "REACH", + "SHOW", + "VIDEO_VIEW", + "ENGAGED_VIEW", + "ENGAGED_VIEW_FIFTEEN", + "LEAD_GENERATION", + "PREFERRED_LEAD", + "CONVERSATION", + "FOLLOWERS", + "PROFILE_VIEWS", + "PAGE_VISIT", + "VALUE", + "AUTOMATIC_VALUE_OPTIMIZATION", + "TRAFFIC_LANDING_PAGE_VIEW", + "DESTINATION_VISIT", + "MT_LIVE_ROOM", + "PRODUCT_CLICK_IN_LIVE", + ] + ] = None + + pacing: Optional[Literal["PACING_MODE_SMOOTH", "PACING_MODE_FAST"]] = None + + pixel_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + placement_type: Optional[Literal["PLACEMENT_TYPE_AUTOMATIC", "PLACEMENT_TYPE_NORMAL"]] = None + + placements: Optional[List[str]] = None + + platform: Literal["meta", "tiktok"] + """The ad platform (meta, tiktok).""" + + schedule_end_time: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + schedule_start_time: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + schedule_type: Optional[Literal["SCHEDULE_START_END", "SCHEDULE_FROM_NOW"]] = None + + typename: Literal["TiktokAdGroupPlatformConfigType"] + """The typename of this object""" + + +PlatformConfig: TypeAlias = Annotated[ + Union[ + Optional[PlatformConfigMetaAdGroupPlatformConfigType], Optional[PlatformConfigTiktokAdGroupPlatformConfigType] + ], + PropertyInfo(discriminator="typename"), +] + + +class AdGroupCreateResponse(BaseModel): + """An external ad group (ad set) belonging to an ad campaign""" + + id: str + """The unique identifier for the external ad group.""" + + ad_campaign: AdCampaign + """The parent ad campaign""" + + config: Optional[Config] = None + """Unified ad group configuration (platform-agnostic)""" + + created_at: datetime + """The datetime the external ad group was created.""" + + daily_budget: Optional[float] = None + """Daily budget in dollars (nil for lifetime budgets)""" + + name: Optional[str] = None + """Human-readable ad group name""" + + platform_config: PlatformConfig + """Typed platform-specific configuration. + + Use inline fragments (... on MetaAdGroupPlatformConfigType). + """ + + status: Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"] + """Current operational status of the ad group""" + + updated_at: datetime + """The datetime the external ad group was last updated.""" diff --git a/src/whop_sdk/types/ad_group_delete_response.py b/src/whop_sdk/types/ad_group_delete_response.py new file mode 100644 index 0000000..c7869a5 --- /dev/null +++ b/src/whop_sdk/types/ad_group_delete_response.py @@ -0,0 +1,7 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import TypeAlias + +__all__ = ["AdGroupDeleteResponse"] + +AdGroupDeleteResponse: TypeAlias = bool diff --git a/src/whop_sdk/types/ad_group_list_params.py b/src/whop_sdk/types/ad_group_list_params.py new file mode 100644 index 0000000..cde96fb --- /dev/null +++ b/src/whop_sdk/types/ad_group_list_params.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["AdGroupListParams"] + + +class AdGroupListParams(TypedDict, total=False): + after: Optional[str] + """Returns the elements in the list that come after the specified cursor.""" + + before: Optional[str] + """Returns the elements in the list that come before the specified cursor.""" + + campaign_id: Optional[str] + """Filter by campaign. Provide exactly one of campaign_id or company_id.""" + + company_id: Optional[str] + """Filter by company. Provide exactly one of campaign_id or company_id.""" + + created_after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Only return ad groups created after this timestamp.""" + + created_before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Only return ad groups created before this timestamp.""" + + first: Optional[int] + """Returns the first _n_ elements from the list.""" + + last: Optional[int] + """Returns the last _n_ elements from the list.""" + + query: Optional[str] + """Case-insensitive substring match against the ad group name.""" + + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] + """The status of an external ad group.""" diff --git a/src/whop_sdk/types/ad_group_list_response.py b/src/whop_sdk/types/ad_group_list_response.py new file mode 100644 index 0000000..26e3998 --- /dev/null +++ b/src/whop_sdk/types/ad_group_list_response.py @@ -0,0 +1,416 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel + +__all__ = [ + "AdGroupListResponse", + "Config", + "ConfigTargeting", + "PlatformConfig", + "PlatformConfigMetaAdGroupPlatformConfigType", + "PlatformConfigTiktokAdGroupPlatformConfigType", +] + + +class ConfigTargeting(BaseModel): + """Audience targeting settings (demographics, geo, interests, audiences, devices).""" + + age_max: Optional[int] = None + """Maximum age for demographic targeting.""" + + age_min: Optional[int] = None + """Minimum age for demographic targeting.""" + + countries: Optional[List[str]] = None + """ISO 3166-1 alpha-2 country codes targeted.""" + + device_platforms: Optional[List[Literal["mobile", "desktop"]]] = None + """Device platforms targeted.""" + + exclude_audience_ids: Optional[List[str]] = None + """Platform audience IDs excluded.""" + + genders: Optional[List[Literal["male", "female", "all"]]] = None + """Genders targeted.""" + + include_audience_ids: Optional[List[str]] = None + """Platform audience IDs included.""" + + interest_ids: Optional[List[str]] = None + """Platform-specific interest IDs targeted.""" + + languages: Optional[List[str]] = None + """Language codes targeted.""" + + placement_type: Optional[Literal["automatic", "manual"]] = None + """Placement strategy for ad delivery.""" + + +class Config(BaseModel): + """Unified ad group configuration (platform-agnostic)""" + + bid_amount: Optional[int] = None + """Bid cap amount in cents. Used when bid_strategy is bid_cap or cost_cap.""" + + bid_strategy: Optional[Literal["lowest_cost", "bid_cap", "cost_cap"]] = None + """Bid strategy: lowest_cost, bid_cap, or cost_cap.""" + + billing_event: Optional[Literal["impressions", "clicks", "optimized_cpm", "video_views"]] = None + """How you are billed (e.g., impressions, clicks).""" + + end_time: Optional[str] = None + """Scheduled end time (ISO8601). Required for lifetime budgets.""" + + frequency_cap: Optional[int] = None + """Maximum number of times to show ads to each person in the frequency interval.""" + + frequency_cap_interval_days: Optional[int] = None + """Number of days for the frequency cap interval.""" + + optimization_goal: Optional[ + Literal[ + "conversions", + "link_clicks", + "landing_page_views", + "reach", + "impressions", + "app_installs", + "video_views", + "lead_generation", + "value", + "page_likes", + "conversations", + "ad_recall_lift", + "two_second_continuous_video_views", + "post_engagement", + "event_responses", + "reminders_set", + "quality_lead", + ] + ] = None + """What the ad group optimizes for (e.g., conversions, link_clicks, reach).""" + + pacing: Optional[Literal["standard", "accelerated"]] = None + """Budget pacing: standard (even) or accelerated (fast).""" + + start_time: Optional[str] = None + """Scheduled start time (ISO8601).""" + + targeting: Optional[ConfigTargeting] = None + """Audience targeting settings (demographics, geo, interests, audiences, devices).""" + + +class PlatformConfigMetaAdGroupPlatformConfigType(BaseModel): + """Meta (Facebook/Instagram) ad set configuration.""" + + attribution_spec: Optional[List[Dict[str, object]]] = None + + bid_amount: Optional[int] = None + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + bid_strategy: Optional[ + Literal["LOWEST_COST_WITHOUT_CAP", "LOWEST_COST_WITH_BID_CAP", "COST_CAP", "LOWEST_COST_WITH_MIN_ROAS"] + ] = None + + billing_event: Optional[ + Literal[ + "APP_INSTALLS", + "CLICKS", + "IMPRESSIONS", + "LINK_CLICKS", + "NONE", + "OFFER_CLAIMS", + "PAGE_LIKES", + "POST_ENGAGEMENT", + "THRUPLAY", + "PURCHASE", + "LISTING_INTERACTION", + ] + ] = None + + daily_budget: Optional[int] = None + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + destination_type: Optional[ + Literal[ + "UNDEFINED", + "WEBSITE", + "APP", + "FACEBOOK", + "MESSENGER", + "WHATSAPP", + "INSTAGRAM_DIRECT", + "INSTAGRAM_PROFILE", + "PHONE_CALL", + "SHOP_AUTOMATIC", + "APPLINKS_AUTOMATIC", + "ON_AD", + "ON_POST", + "ON_VIDEO", + "ON_PAGE", + "ON_EVENT", + "MESSAGING_MESSENGER_WHATSAPP", + "MESSAGING_INSTAGRAM_DIRECT_MESSENGER", + "MESSAGING_INSTAGRAM_DIRECT_WHATSAPP", + "MESSAGING_INSTAGRAM_DIRECT_MESSENGER_WHATSAPP", + "INSTAGRAM_PROFILE_AND_FACEBOOK_PAGE", + "FACEBOOK_PAGE", + "INSTAGRAM_LIVE", + "FACEBOOK_LIVE", + "IMAGINE", + "LEAD_FROM_IG_DIRECT", + "LEAD_FROM_MESSENGER", + "WEBSITE_AND_LEAD_FORM", + "WEBSITE_AND_PHONE_CALL", + "BROADCAST_CHANNEL", + ] + ] = None + + end_time: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + excluded_geo_locations: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + facebook_positions: Optional[List[str]] = None + + geo_locations: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + instagram_actor_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + instagram_positions: Optional[List[str]] = None + + lifetime_budget: Optional[int] = None + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + optimization_goal: Optional[ + Literal[ + "NONE", + "APP_INSTALLS", + "AD_RECALL_LIFT", + "ENGAGED_USERS", + "EVENT_RESPONSES", + "IMPRESSIONS", + "LEAD_GENERATION", + "QUALITY_LEAD", + "LINK_CLICKS", + "OFFSITE_CONVERSIONS", + "PAGE_LIKES", + "POST_ENGAGEMENT", + "QUALITY_CALL", + "REACH", + "LANDING_PAGE_VIEWS", + "VISIT_INSTAGRAM_PROFILE", + "VALUE", + "THRUPLAY", + "DERIVED_EVENTS", + "APP_INSTALLS_AND_OFFSITE_CONVERSIONS", + "CONVERSATIONS", + "IN_APP_VALUE", + "MESSAGING_PURCHASE_CONVERSION", + "SUBSCRIBERS", + "REMINDERS_SET", + "MEANINGFUL_CALL_ATTEMPT", + "PROFILE_VISIT", + "PROFILE_AND_PAGE_ENGAGEMENT", + "TWO_SECOND_CONTINUOUS_VIDEO_VIEWS", + "ENGAGED_REACH", + "ENGAGED_PAGE_VIEWS", + "MESSAGING_DEEP_CONVERSATION_AND_FOLLOW", + "ADVERTISER_SILOED_VALUE", + "AUTOMATIC_OBJECTIVE", + "MESSAGING_APPOINTMENT_CONVERSION", + ] + ] = None + + page_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + platform: Literal["meta", "tiktok"] + """The ad platform (meta, tiktok).""" + + promoted_object: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + publisher_platforms: Optional[List[str]] = None + + status: Optional[Literal["ACTIVE", "PAUSED"]] = None + + targeting_automation: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + typename: Literal["MetaAdGroupPlatformConfigType"] + """The typename of this object""" + + +class PlatformConfigTiktokAdGroupPlatformConfigType(BaseModel): + """TikTok ad group configuration.""" + + bid_price: Optional[float] = None + """ + Represents signed double-precision fractional values as specified by + [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point). + """ + + bid_type: Optional[Literal["BID_TYPE_NO_BID", "BID_TYPE_CUSTOM"]] = None + + billing_event: Optional[Literal["CPC", "CPM", "OCPM", "CPV"]] = None + + budget_mode: Optional[Literal["BUDGET_MODE_DAY", "BUDGET_MODE_TOTAL", "BUDGET_MODE_DYNAMIC_DAILY_BUDGET"]] = None + + conversion_bid_price: Optional[float] = None + """ + Represents signed double-precision fractional values as specified by + [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point). + """ + + identity_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + identity_type: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + operation_status: Optional[Literal["ENABLE", "DISABLE"]] = None + + optimization_event: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + optimization_goal: Optional[ + Literal[ + "CLICK", + "CONVERT", + "INSTALL", + "IN_APP_EVENT", + "REACH", + "SHOW", + "VIDEO_VIEW", + "ENGAGED_VIEW", + "ENGAGED_VIEW_FIFTEEN", + "LEAD_GENERATION", + "PREFERRED_LEAD", + "CONVERSATION", + "FOLLOWERS", + "PROFILE_VIEWS", + "PAGE_VISIT", + "VALUE", + "AUTOMATIC_VALUE_OPTIMIZATION", + "TRAFFIC_LANDING_PAGE_VIEW", + "DESTINATION_VISIT", + "MT_LIVE_ROOM", + "PRODUCT_CLICK_IN_LIVE", + ] + ] = None + + pacing: Optional[Literal["PACING_MODE_SMOOTH", "PACING_MODE_FAST"]] = None + + pixel_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + placement_type: Optional[Literal["PLACEMENT_TYPE_AUTOMATIC", "PLACEMENT_TYPE_NORMAL"]] = None + + placements: Optional[List[str]] = None + + platform: Literal["meta", "tiktok"] + """The ad platform (meta, tiktok).""" + + schedule_end_time: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + schedule_start_time: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + schedule_type: Optional[Literal["SCHEDULE_START_END", "SCHEDULE_FROM_NOW"]] = None + + typename: Literal["TiktokAdGroupPlatformConfigType"] + """The typename of this object""" + + +PlatformConfig: TypeAlias = Annotated[ + Union[ + Optional[PlatformConfigMetaAdGroupPlatformConfigType], Optional[PlatformConfigTiktokAdGroupPlatformConfigType] + ], + PropertyInfo(discriminator="typename"), +] + + +class AdGroupListResponse(BaseModel): + """An external ad group (ad set) belonging to an ad campaign""" + + id: str + """The unique identifier for the external ad group.""" + + config: Optional[Config] = None + """Unified ad group configuration (platform-agnostic)""" + + created_at: datetime + """The datetime the external ad group was created.""" + + daily_budget: Optional[float] = None + """Daily budget in dollars (nil for lifetime budgets)""" + + name: Optional[str] = None + """Human-readable ad group name""" + + platform_config: PlatformConfig + """Typed platform-specific configuration. + + Use inline fragments (... on MetaAdGroupPlatformConfigType). + """ + + status: Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"] + """Current operational status of the ad group""" + + updated_at: datetime + """The datetime the external ad group was last updated.""" diff --git a/src/whop_sdk/types/ad_group_retrieve_response.py b/src/whop_sdk/types/ad_group_retrieve_response.py new file mode 100644 index 0000000..9a36fe0 --- /dev/null +++ b/src/whop_sdk/types/ad_group_retrieve_response.py @@ -0,0 +1,438 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel + +__all__ = [ + "AdGroupRetrieveResponse", + "AdCampaign", + "Config", + "ConfigTargeting", + "PlatformConfig", + "PlatformConfigMetaAdGroupPlatformConfigType", + "PlatformConfigTiktokAdGroupPlatformConfigType", +] + + +class AdCampaign(BaseModel): + """The parent ad campaign""" + + id: str + """The unique identifier for the ad campaign.""" + + platform: Optional[Literal["meta", "tiktok"]] = None + """The platforms where an ad campaign can run.""" + + status: Literal[ + "active", "paused", "inactive", "stale", "pending_refund", "payment_failed", "draft", "in_review", "flagged" + ] + """Current status of the campaign (active, paused, or inactive)""" + + title: str + """The title of the ad campaign""" + + +class ConfigTargeting(BaseModel): + """Audience targeting settings (demographics, geo, interests, audiences, devices).""" + + age_max: Optional[int] = None + """Maximum age for demographic targeting.""" + + age_min: Optional[int] = None + """Minimum age for demographic targeting.""" + + countries: Optional[List[str]] = None + """ISO 3166-1 alpha-2 country codes targeted.""" + + device_platforms: Optional[List[Literal["mobile", "desktop"]]] = None + """Device platforms targeted.""" + + exclude_audience_ids: Optional[List[str]] = None + """Platform audience IDs excluded.""" + + genders: Optional[List[Literal["male", "female", "all"]]] = None + """Genders targeted.""" + + include_audience_ids: Optional[List[str]] = None + """Platform audience IDs included.""" + + interest_ids: Optional[List[str]] = None + """Platform-specific interest IDs targeted.""" + + languages: Optional[List[str]] = None + """Language codes targeted.""" + + placement_type: Optional[Literal["automatic", "manual"]] = None + """Placement strategy for ad delivery.""" + + +class Config(BaseModel): + """Unified ad group configuration (platform-agnostic)""" + + bid_amount: Optional[int] = None + """Bid cap amount in cents. Used when bid_strategy is bid_cap or cost_cap.""" + + bid_strategy: Optional[Literal["lowest_cost", "bid_cap", "cost_cap"]] = None + """Bid strategy: lowest_cost, bid_cap, or cost_cap.""" + + billing_event: Optional[Literal["impressions", "clicks", "optimized_cpm", "video_views"]] = None + """How you are billed (e.g., impressions, clicks).""" + + end_time: Optional[str] = None + """Scheduled end time (ISO8601). Required for lifetime budgets.""" + + frequency_cap: Optional[int] = None + """Maximum number of times to show ads to each person in the frequency interval.""" + + frequency_cap_interval_days: Optional[int] = None + """Number of days for the frequency cap interval.""" + + optimization_goal: Optional[ + Literal[ + "conversions", + "link_clicks", + "landing_page_views", + "reach", + "impressions", + "app_installs", + "video_views", + "lead_generation", + "value", + "page_likes", + "conversations", + "ad_recall_lift", + "two_second_continuous_video_views", + "post_engagement", + "event_responses", + "reminders_set", + "quality_lead", + ] + ] = None + """What the ad group optimizes for (e.g., conversions, link_clicks, reach).""" + + pacing: Optional[Literal["standard", "accelerated"]] = None + """Budget pacing: standard (even) or accelerated (fast).""" + + start_time: Optional[str] = None + """Scheduled start time (ISO8601).""" + + targeting: Optional[ConfigTargeting] = None + """Audience targeting settings (demographics, geo, interests, audiences, devices).""" + + +class PlatformConfigMetaAdGroupPlatformConfigType(BaseModel): + """Meta (Facebook/Instagram) ad set configuration.""" + + attribution_spec: Optional[List[Dict[str, object]]] = None + + bid_amount: Optional[int] = None + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + bid_strategy: Optional[ + Literal["LOWEST_COST_WITHOUT_CAP", "LOWEST_COST_WITH_BID_CAP", "COST_CAP", "LOWEST_COST_WITH_MIN_ROAS"] + ] = None + + billing_event: Optional[ + Literal[ + "APP_INSTALLS", + "CLICKS", + "IMPRESSIONS", + "LINK_CLICKS", + "NONE", + "OFFER_CLAIMS", + "PAGE_LIKES", + "POST_ENGAGEMENT", + "THRUPLAY", + "PURCHASE", + "LISTING_INTERACTION", + ] + ] = None + + daily_budget: Optional[int] = None + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + destination_type: Optional[ + Literal[ + "UNDEFINED", + "WEBSITE", + "APP", + "FACEBOOK", + "MESSENGER", + "WHATSAPP", + "INSTAGRAM_DIRECT", + "INSTAGRAM_PROFILE", + "PHONE_CALL", + "SHOP_AUTOMATIC", + "APPLINKS_AUTOMATIC", + "ON_AD", + "ON_POST", + "ON_VIDEO", + "ON_PAGE", + "ON_EVENT", + "MESSAGING_MESSENGER_WHATSAPP", + "MESSAGING_INSTAGRAM_DIRECT_MESSENGER", + "MESSAGING_INSTAGRAM_DIRECT_WHATSAPP", + "MESSAGING_INSTAGRAM_DIRECT_MESSENGER_WHATSAPP", + "INSTAGRAM_PROFILE_AND_FACEBOOK_PAGE", + "FACEBOOK_PAGE", + "INSTAGRAM_LIVE", + "FACEBOOK_LIVE", + "IMAGINE", + "LEAD_FROM_IG_DIRECT", + "LEAD_FROM_MESSENGER", + "WEBSITE_AND_LEAD_FORM", + "WEBSITE_AND_PHONE_CALL", + "BROADCAST_CHANNEL", + ] + ] = None + + end_time: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + excluded_geo_locations: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + facebook_positions: Optional[List[str]] = None + + geo_locations: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + instagram_actor_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + instagram_positions: Optional[List[str]] = None + + lifetime_budget: Optional[int] = None + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + optimization_goal: Optional[ + Literal[ + "NONE", + "APP_INSTALLS", + "AD_RECALL_LIFT", + "ENGAGED_USERS", + "EVENT_RESPONSES", + "IMPRESSIONS", + "LEAD_GENERATION", + "QUALITY_LEAD", + "LINK_CLICKS", + "OFFSITE_CONVERSIONS", + "PAGE_LIKES", + "POST_ENGAGEMENT", + "QUALITY_CALL", + "REACH", + "LANDING_PAGE_VIEWS", + "VISIT_INSTAGRAM_PROFILE", + "VALUE", + "THRUPLAY", + "DERIVED_EVENTS", + "APP_INSTALLS_AND_OFFSITE_CONVERSIONS", + "CONVERSATIONS", + "IN_APP_VALUE", + "MESSAGING_PURCHASE_CONVERSION", + "SUBSCRIBERS", + "REMINDERS_SET", + "MEANINGFUL_CALL_ATTEMPT", + "PROFILE_VISIT", + "PROFILE_AND_PAGE_ENGAGEMENT", + "TWO_SECOND_CONTINUOUS_VIDEO_VIEWS", + "ENGAGED_REACH", + "ENGAGED_PAGE_VIEWS", + "MESSAGING_DEEP_CONVERSATION_AND_FOLLOW", + "ADVERTISER_SILOED_VALUE", + "AUTOMATIC_OBJECTIVE", + "MESSAGING_APPOINTMENT_CONVERSION", + ] + ] = None + + page_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + platform: Literal["meta", "tiktok"] + """The ad platform (meta, tiktok).""" + + promoted_object: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + publisher_platforms: Optional[List[str]] = None + + status: Optional[Literal["ACTIVE", "PAUSED"]] = None + + targeting_automation: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + typename: Literal["MetaAdGroupPlatformConfigType"] + """The typename of this object""" + + +class PlatformConfigTiktokAdGroupPlatformConfigType(BaseModel): + """TikTok ad group configuration.""" + + bid_price: Optional[float] = None + """ + Represents signed double-precision fractional values as specified by + [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point). + """ + + bid_type: Optional[Literal["BID_TYPE_NO_BID", "BID_TYPE_CUSTOM"]] = None + + billing_event: Optional[Literal["CPC", "CPM", "OCPM", "CPV"]] = None + + budget_mode: Optional[Literal["BUDGET_MODE_DAY", "BUDGET_MODE_TOTAL", "BUDGET_MODE_DYNAMIC_DAILY_BUDGET"]] = None + + conversion_bid_price: Optional[float] = None + """ + Represents signed double-precision fractional values as specified by + [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point). + """ + + identity_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + identity_type: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + operation_status: Optional[Literal["ENABLE", "DISABLE"]] = None + + optimization_event: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + optimization_goal: Optional[ + Literal[ + "CLICK", + "CONVERT", + "INSTALL", + "IN_APP_EVENT", + "REACH", + "SHOW", + "VIDEO_VIEW", + "ENGAGED_VIEW", + "ENGAGED_VIEW_FIFTEEN", + "LEAD_GENERATION", + "PREFERRED_LEAD", + "CONVERSATION", + "FOLLOWERS", + "PROFILE_VIEWS", + "PAGE_VISIT", + "VALUE", + "AUTOMATIC_VALUE_OPTIMIZATION", + "TRAFFIC_LANDING_PAGE_VIEW", + "DESTINATION_VISIT", + "MT_LIVE_ROOM", + "PRODUCT_CLICK_IN_LIVE", + ] + ] = None + + pacing: Optional[Literal["PACING_MODE_SMOOTH", "PACING_MODE_FAST"]] = None + + pixel_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + placement_type: Optional[Literal["PLACEMENT_TYPE_AUTOMATIC", "PLACEMENT_TYPE_NORMAL"]] = None + + placements: Optional[List[str]] = None + + platform: Literal["meta", "tiktok"] + """The ad platform (meta, tiktok).""" + + schedule_end_time: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + schedule_start_time: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + schedule_type: Optional[Literal["SCHEDULE_START_END", "SCHEDULE_FROM_NOW"]] = None + + typename: Literal["TiktokAdGroupPlatformConfigType"] + """The typename of this object""" + + +PlatformConfig: TypeAlias = Annotated[ + Union[ + Optional[PlatformConfigMetaAdGroupPlatformConfigType], Optional[PlatformConfigTiktokAdGroupPlatformConfigType] + ], + PropertyInfo(discriminator="typename"), +] + + +class AdGroupRetrieveResponse(BaseModel): + """An external ad group (ad set) belonging to an ad campaign""" + + id: str + """The unique identifier for the external ad group.""" + + ad_campaign: AdCampaign + """The parent ad campaign""" + + config: Optional[Config] = None + """Unified ad group configuration (platform-agnostic)""" + + created_at: datetime + """The datetime the external ad group was created.""" + + daily_budget: Optional[float] = None + """Daily budget in dollars (nil for lifetime budgets)""" + + name: Optional[str] = None + """Human-readable ad group name""" + + platform_config: PlatformConfig + """Typed platform-specific configuration. + + Use inline fragments (... on MetaAdGroupPlatformConfigType). + """ + + status: Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"] + """Current operational status of the ad group""" + + updated_at: datetime + """The datetime the external ad group was last updated.""" diff --git a/src/whop_sdk/types/ad_group_update_params.py b/src/whop_sdk/types/ad_group_update_params.py new file mode 100644 index 0000000..bb7e726 --- /dev/null +++ b/src/whop_sdk/types/ad_group_update_params.py @@ -0,0 +1,1216 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, List, Iterable, Optional +from typing_extensions import Literal, Required, TypedDict + +from .._types import SequenceNotStr + +__all__ = [ + "AdGroupUpdateParams", + "Config", + "ConfigTargeting", + "PlatformConfig", + "PlatformConfigMeta", + "PlatformConfigMetaAttributionSpec", + "PlatformConfigMetaExcludedGeoLocations", + "PlatformConfigMetaExcludedGeoLocationsCity", + "PlatformConfigMetaExcludedGeoLocationsRegion", + "PlatformConfigMetaExcludedGeoLocationsZip", + "PlatformConfigMetaGeoCity", + "PlatformConfigMetaGeoLocations", + "PlatformConfigMetaGeoLocationsCity", + "PlatformConfigMetaGeoLocationsRegion", + "PlatformConfigMetaGeoLocationsZip", + "PlatformConfigMetaGeoRegion", + "PlatformConfigMetaLeadFormConfig", + "PlatformConfigMetaLeadFormConfigQuestion", + "PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestion", + "PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestionOption", + "PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestionOptionLogic", + "PlatformConfigMetaLeadFormConfigQuestionOption", + "PlatformConfigMetaLeadFormConfigQuestionOptionLogic", + "PlatformConfigMetaLeadFormConfigCustomDisclaimerCheckbox", + "PlatformConfigMetaLeadFormConfigThankYouPage", + "PlatformConfigMetaPromotedObject", + "PlatformConfigMetaTargetingAutomation", + "PlatformConfigTiktok", + "PlatformConfigTiktokAction", + "PlatformConfigTiktokInstantFormConfig", + "PlatformConfigTiktokInstantFormConfigQuestion", +] + + +class AdGroupUpdateParams(TypedDict, total=False): + budget: Optional[float] + """Budget amount in dollars.""" + + budget_type: Optional[Literal["daily", "lifetime"]] + """The budget type for an ad campaign or ad group.""" + + config: Optional[Config] + """Unified ad group configuration (bidding, optimization, targeting).""" + + daily_budget: Optional[float] + """Daily budget in dollars.""" + + name: Optional[str] + """Human-readable ad group name.""" + + platform_config: Optional[PlatformConfig] + """Platform-specific ad group configuration.""" + + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] + """The status of an external ad group.""" + + +class ConfigTargeting(TypedDict, total=False): + """Audience targeting settings (demographics, geo, interests, audiences, devices).""" + + age_max: Optional[int] + """Maximum age for demographic targeting.""" + + age_min: Optional[int] + """Minimum age for demographic targeting.""" + + countries: Optional[SequenceNotStr[str]] + """ISO 3166-1 alpha-2 country codes to target.""" + + device_platforms: Optional[List[Literal["mobile", "desktop"]]] + """Device platforms to target.""" + + exclude_audience_ids: Optional[SequenceNotStr[str]] + """Platform audience IDs to exclude.""" + + genders: Optional[List[Literal["male", "female", "all"]]] + """Genders to target.""" + + include_audience_ids: Optional[SequenceNotStr[str]] + """Platform audience IDs to include.""" + + interest_ids: Optional[SequenceNotStr[str]] + """Platform-specific interest IDs to target.""" + + languages: Optional[SequenceNotStr[str]] + """Language codes to target.""" + + placement_type: Optional[Literal["automatic", "manual"]] + """Placement strategy for ad delivery.""" + + +class Config(TypedDict, total=False): + """Unified ad group configuration (bidding, optimization, targeting).""" + + bid_amount: Optional[int] + """Bid cap amount in cents. Used when bid_strategy is bid_cap or cost_cap.""" + + bid_strategy: Optional[Literal["lowest_cost", "bid_cap", "cost_cap"]] + """Bid strategy: lowest_cost, bid_cap, or cost_cap.""" + + billing_event: Optional[Literal["impressions", "clicks", "optimized_cpm", "video_views"]] + """How you are billed (e.g., impressions, clicks).""" + + end_time: Optional[str] + """Scheduled end time (ISO8601). Required for lifetime budgets.""" + + frequency_cap: Optional[int] + """Maximum number of times to show ads to each person in the frequency interval.""" + + frequency_cap_interval_days: Optional[int] + """Number of days for the frequency cap interval.""" + + optimization_goal: Optional[ + Literal[ + "conversions", + "link_clicks", + "landing_page_views", + "reach", + "impressions", + "app_installs", + "video_views", + "lead_generation", + "value", + "page_likes", + "conversations", + "ad_recall_lift", + "two_second_continuous_video_views", + "post_engagement", + "event_responses", + "reminders_set", + "quality_lead", + ] + ] + """What the ad group optimizes for (e.g., conversions, link_clicks, reach).""" + + pacing: Optional[Literal["standard", "accelerated"]] + """Budget pacing: standard (even) or accelerated (fast).""" + + start_time: Optional[str] + """Scheduled start time (ISO8601).""" + + targeting: Optional[ConfigTargeting] + """Audience targeting settings (demographics, geo, interests, audiences, devices).""" + + +class PlatformConfigMetaAttributionSpec(TypedDict, total=False): + """Meta conversion attribution window.""" + + event_type: Required[str] + """Attribution event type (e.g., CLICK_THROUGH, VIEW_THROUGH).""" + + window_days: Required[int] + """Attribution window in days (1, 7, 28).""" + + +class PlatformConfigMetaExcludedGeoLocationsCity(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaExcludedGeoLocationsRegion(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaExcludedGeoLocationsZip(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaExcludedGeoLocations(TypedDict, total=False): + """Geo locations to exclude.""" + + cities: Optional[Iterable[PlatformConfigMetaExcludedGeoLocationsCity]] + """City targets.""" + + countries: Optional[SequenceNotStr[str]] + """ISO 3166-1 alpha-2 country codes.""" + + location_types: Optional[SequenceNotStr[str]] + """Location types (home, recent, travel_in).""" + + regions: Optional[Iterable[PlatformConfigMetaExcludedGeoLocationsRegion]] + """Region/state targets.""" + + zips: Optional[Iterable[PlatformConfigMetaExcludedGeoLocationsZip]] + """Zip/postal code targets.""" + + +class PlatformConfigMetaGeoCity(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaGeoLocationsCity(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaGeoLocationsRegion(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaGeoLocationsZip(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaGeoLocations(TypedDict, total=False): + """Geo targeting (countries, regions, cities, zips).""" + + cities: Optional[Iterable[PlatformConfigMetaGeoLocationsCity]] + """City targets.""" + + countries: Optional[SequenceNotStr[str]] + """ISO 3166-1 alpha-2 country codes.""" + + location_types: Optional[SequenceNotStr[str]] + """Location types (home, recent, travel_in).""" + + regions: Optional[Iterable[PlatformConfigMetaGeoLocationsRegion]] + """Region/state targets.""" + + zips: Optional[Iterable[PlatformConfigMetaGeoLocationsZip]] + """Zip/postal code targets.""" + + +class PlatformConfigMetaGeoRegion(TypedDict, total=False): + """A Meta geo target entry (region, city, or zip).""" + + key: Required[str] + """Meta geo target key/ID.""" + + country: Optional[str] + """Country code for this entry.""" + + name: Optional[str] + """Display name.""" + + radius: Optional[int] + """Radius in miles (cities only).""" + + +class PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestionOptionLogic(TypedDict, total=False): + """Conditional logic routing for this answer option.""" + + type: Required[str] + """Logic type: go_to_question, submit_form, or close_form.""" + + target_end_page_index: Optional[int] + """Index of the end page to route to (for submit_form type).""" + + target_question_index: Optional[int] + """Index of the question to route to (for go_to_question type).""" + + +class PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestionOption(TypedDict, total=False): + """An answer option for a multiple choice lead form question.""" + + key: Required[str] + """Unique key for this option.""" + + value: Required[str] + """Display text for this option.""" + + logic: Optional[PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestionOptionLogic] + """Conditional logic routing for this answer option.""" + + +class PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestion(TypedDict, total=False): + """A dependent conditional question (non-recursive to avoid schema recursion).""" + + type: Required[str] + """Question type (EMAIL, FULL_NAME, PHONE, CUSTOM, DATE_TIME, etc.).""" + + inline_context: Optional[str] + """Helper text shown below the question.""" + + key: Optional[str] + """Unique key for this question.""" + + label: Optional[str] + """Custom label for CUSTOM questions.""" + + options: Optional[Iterable[PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestionOption]] + """Answer options for multiple choice questions.""" + + +class PlatformConfigMetaLeadFormConfigQuestionOptionLogic(TypedDict, total=False): + """Conditional logic routing for this answer option.""" + + type: Required[str] + """Logic type: go_to_question, submit_form, or close_form.""" + + target_end_page_index: Optional[int] + """Index of the end page to route to (for submit_form type).""" + + target_question_index: Optional[int] + """Index of the question to route to (for go_to_question type).""" + + +class PlatformConfigMetaLeadFormConfigQuestionOption(TypedDict, total=False): + """An answer option for a multiple choice lead form question.""" + + key: Required[str] + """Unique key for this option.""" + + value: Required[str] + """Display text for this option.""" + + logic: Optional[PlatformConfigMetaLeadFormConfigQuestionOptionLogic] + """Conditional logic routing for this answer option.""" + + +class PlatformConfigMetaLeadFormConfigQuestion(TypedDict, total=False): + """A question on a Meta lead gen form.""" + + type: Required[str] + """Question type (EMAIL, FULL_NAME, PHONE, CUSTOM, DATE_TIME, etc.).""" + + conditional_questions_group_id: Optional[str] + """Group ID for conditional question routing.""" + + dependent_conditional_questions: Optional[ + Iterable[PlatformConfigMetaLeadFormConfigQuestionDependentConditionalQuestion] + ] + """Questions shown conditionally based on this question's answer.""" + + inline_context: Optional[str] + """Helper text shown below the question.""" + + key: Optional[str] + """Unique key for this question.""" + + label: Optional[str] + """Custom label for CUSTOM questions.""" + + options: Optional[Iterable[PlatformConfigMetaLeadFormConfigQuestionOption]] + """Answer options for multiple choice CUSTOM questions.""" + + question_format: Optional[str] + """UI hint: short_answer, multiple_choice, or appointment.""" + + +class PlatformConfigMetaLeadFormConfigCustomDisclaimerCheckbox(TypedDict, total=False): + """A consent checkbox for the custom disclaimer section.""" + + key: Required[str] + """Unique key for this checkbox.""" + + text: Required[str] + """Label text for the checkbox.""" + + is_checked_by_default: Optional[bool] + """Whether the checkbox is checked by default.""" + + is_required: Optional[bool] + """Whether the checkbox must be checked to submit.""" + + +class PlatformConfigMetaLeadFormConfigThankYouPage(TypedDict, total=False): + """A thank-you / ending page for a Meta lead gen form.""" + + body: Optional[str] + """Body text for this ending page.""" + + business_phone: Optional[str] + """Business phone number for call CTA.""" + + button_text: Optional[str] + """Custom button text.""" + + button_type: Optional[str] + """CTA button type: VIEW_WEBSITE, CALL_BUSINESS, DOWNLOAD.""" + + conditional_question_group_id: Optional[str] + """Question group ID for conditional routing to this page.""" + + enable_messenger: Optional[bool] + """Enable Messenger follow-up.""" + + gated_file_url: Optional[str] + """Uploaded file URL for gated content download.""" + + link: Optional[str] + """URL the button links to.""" + + name: Optional[str] + """Internal name for this ending page.""" + + title: Optional[str] + """Headline for this ending page.""" + + +class PlatformConfigMetaLeadFormConfig(TypedDict, total=False): + """Configuration for a Meta lead gen instant form.""" + + name: Required[str] + """Name of the lead form.""" + + privacy_policy_url: Required[str] + """URL to your privacy policy. Required by Meta.""" + + questions: Required[Iterable[PlatformConfigMetaLeadFormConfigQuestion]] + """Questions to ask on the form.""" + + background_image_source: Optional[str] + """Background image source: from_ad or custom.""" + + background_image_url: Optional[str] + """URL of custom background image.""" + + conditional_logic_enabled: Optional[bool] + """Whether conditional logic is enabled for questions.""" + + context_card_button_text: Optional[str] + """CTA button text on the greeting card.""" + + context_card_content: Optional[SequenceNotStr[str]] + """Optional greeting card bullet points.""" + + context_card_style: Optional[str] + """Greeting layout: PARAGRAPH_STYLE or LIST_STYLE.""" + + context_card_title: Optional[str] + """Optional greeting card title.""" + + custom_disclaimer_body: Optional[str] + """Custom disclaimer body text.""" + + custom_disclaimer_checkboxes: Optional[Iterable[PlatformConfigMetaLeadFormConfigCustomDisclaimerCheckbox]] + """Consent checkboxes for the custom disclaimer.""" + + custom_disclaimer_title: Optional[str] + """Custom disclaimer section title.""" + + form_type: Optional[str] + """Form type: more_volume, higher_intent, or rich_creative.""" + + messenger_enabled: Optional[bool] + """Enable Messenger follow-up after form submission.""" + + phone_verification_enabled: Optional[bool] + """Require phone number verification via OTP (higher_intent only).""" + + privacy_policy_link_text: Optional[str] + """Custom link text for privacy policy (max 70 chars).""" + + question_page_custom_headline: Optional[str] + """Custom headline for the questions page.""" + + rich_creative_headline: Optional[str] + """Headline for rich creative form intro.""" + + rich_creative_overview: Optional[str] + """Overview description for rich creative form intro.""" + + rich_creative_url: Optional[str] + """Uploaded image URL for rich creative form type.""" + + thank_you_pages: Optional[Iterable[PlatformConfigMetaLeadFormConfigThankYouPage]] + """Thank you / ending pages (supports multiple for conditional routing).""" + + +class PlatformConfigMetaPromotedObject(TypedDict, total=False): + """The object this ad set promotes (pixel, page, etc.).""" + + custom_conversion_id: Optional[str] + """Custom conversion rule ID (numeric, from Meta Events Manager).""" + + custom_event_str: Optional[str] + """Pixel event name, used when custom_event_type is OTHER.""" + + custom_event_type: Optional[str] + """Custom event type (e.g., PURCHASE, COMPLETE_REGISTRATION, OTHER).""" + + page_id: Optional[str] + """Facebook Page ID.""" + + pixel_id: Optional[str] + """Meta Pixel ID for conversion tracking.""" + + whatsapp_phone_number: Optional[str] + """WhatsApp phone number for messaging campaigns.""" + + +class PlatformConfigMetaTargetingAutomation(TypedDict, total=False): + """Advantage+ audience expansion settings.""" + + advantage_audience: Optional[int] + """0 = off (use exact targeting), 1 = on (let Meta expand audience).""" + + +class PlatformConfigMeta(TypedDict, total=False): + """Meta (Facebook/Instagram) ad set configuration.""" + + android_devices: Optional[SequenceNotStr[str]] + + attribution_setting: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + attribution_spec: Optional[Iterable[PlatformConfigMetaAttributionSpec]] + """Conversion attribution windows.""" + + audience_network_positions: Optional[SequenceNotStr[str]] + + audience_type: Optional[str] + """Audience type for retargeting.""" + + bid_amount: Optional[int] + """Bid amount in cents.""" + + bid_strategy: Optional[ + Literal["LOWEST_COST_WITHOUT_CAP", "LOWEST_COST_WITH_BID_CAP", "COST_CAP", "LOWEST_COST_WITH_MIN_ROAS"] + ] + """Meta bid strategy.""" + + billing_event: Optional[ + Literal[ + "APP_INSTALLS", + "CLICKS", + "IMPRESSIONS", + "LINK_CLICKS", + "NONE", + "OFFER_CLAIMS", + "PAGE_LIKES", + "POST_ENGAGEMENT", + "THRUPLAY", + "PURCHASE", + "LISTING_INTERACTION", + ] + ] + """How you are billed on Meta.""" + + brand_safety_content_filter_levels: Optional[SequenceNotStr[str]] + + budget_remaining: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + cost_per_result_goal: Optional[float] + """ + Represents signed double-precision fractional values as specified by + [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point). + """ + + created_time: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + daily_budget: Optional[int] + """Daily budget in cents.""" + + daily_min_spend_target: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + daily_spend_cap: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + destination_type: Optional[ + Literal[ + "UNDEFINED", + "WEBSITE", + "APP", + "FACEBOOK", + "MESSENGER", + "WHATSAPP", + "INSTAGRAM_DIRECT", + "INSTAGRAM_PROFILE", + "PHONE_CALL", + "SHOP_AUTOMATIC", + "APPLINKS_AUTOMATIC", + "ON_AD", + "ON_POST", + "ON_VIDEO", + "ON_PAGE", + "ON_EVENT", + "MESSAGING_MESSENGER_WHATSAPP", + "MESSAGING_INSTAGRAM_DIRECT_MESSENGER", + "MESSAGING_INSTAGRAM_DIRECT_WHATSAPP", + "MESSAGING_INSTAGRAM_DIRECT_MESSENGER_WHATSAPP", + "INSTAGRAM_PROFILE_AND_FACEBOOK_PAGE", + "FACEBOOK_PAGE", + "INSTAGRAM_LIVE", + "FACEBOOK_LIVE", + "IMAGINE", + "LEAD_FROM_IG_DIRECT", + "LEAD_FROM_MESSENGER", + "WEBSITE_AND_LEAD_FORM", + "WEBSITE_AND_PHONE_CALL", + "BROADCAST_CHANNEL", + ] + ] + """Where ads in this ad set direct people.""" + + dsa_beneficiary: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + dsa_payor: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + end_time: Optional[str] + """End time (ISO8601). Required for lifetime budgets.""" + + excluded_geo_locations: Optional[PlatformConfigMetaExcludedGeoLocations] + """Geo locations to exclude.""" + + facebook_positions: Optional[SequenceNotStr[str]] + """Facebook ad placements (feed, reels, stories, etc.).""" + + frequency_control_count: Optional[int] + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + frequency_control_days: Optional[int] + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + frequency_control_type: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + geo_cities: Optional[Iterable[PlatformConfigMetaGeoCity]] + + geo_locations: Optional[PlatformConfigMetaGeoLocations] + """Geo targeting (countries, regions, cities, zips).""" + + geo_regions: Optional[Iterable[PlatformConfigMetaGeoRegion]] + + geo_zips: Optional[SequenceNotStr[str]] + + instagram_actor_id: Optional[str] + """Instagram account ID for this ad set.""" + + instagram_positions: Optional[SequenceNotStr[str]] + """Instagram ad placements (stream, story, reels, etc.).""" + + ios_devices: Optional[SequenceNotStr[str]] + + is_dynamic_creative: Optional[bool] + """Represents `true` or `false` values.""" + + lead_conversion_location: Optional[Literal["website", "instant_forms", "messenger", "instagram", "calls", "app"]] + + lead_form_config: Optional[PlatformConfigMetaLeadFormConfig] + """Configuration for a Meta lead gen instant form.""" + + lead_gen_form_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + lifetime_budget: Optional[int] + """Lifetime budget in cents.""" + + lifetime_min_spend_target: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + lifetime_spend_cap: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + location_types: Optional[SequenceNotStr[str]] + + messenger_positions: Optional[SequenceNotStr[str]] + + optimization_goal: Optional[ + Literal[ + "NONE", + "APP_INSTALLS", + "AD_RECALL_LIFT", + "ENGAGED_USERS", + "EVENT_RESPONSES", + "IMPRESSIONS", + "LEAD_GENERATION", + "QUALITY_LEAD", + "LINK_CLICKS", + "OFFSITE_CONVERSIONS", + "PAGE_LIKES", + "POST_ENGAGEMENT", + "QUALITY_CALL", + "REACH", + "LANDING_PAGE_VIEWS", + "VISIT_INSTAGRAM_PROFILE", + "VALUE", + "THRUPLAY", + "DERIVED_EVENTS", + "APP_INSTALLS_AND_OFFSITE_CONVERSIONS", + "CONVERSATIONS", + "IN_APP_VALUE", + "MESSAGING_PURCHASE_CONVERSION", + "SUBSCRIBERS", + "REMINDERS_SET", + "MEANINGFUL_CALL_ATTEMPT", + "PROFILE_VISIT", + "PROFILE_AND_PAGE_ENGAGEMENT", + "TWO_SECOND_CONTINUOUS_VIDEO_VIEWS", + "ENGAGED_REACH", + "ENGAGED_PAGE_VIEWS", + "MESSAGING_DEEP_CONVERSATION_AND_FOLLOW", + "ADVERTISER_SILOED_VALUE", + "AUTOMATIC_OBJECTIVE", + "MESSAGING_APPOINTMENT_CONVERSION", + ] + ] + """What this ad set optimizes for on Meta.""" + + page_id: Optional[str] + """Facebook Page ID for this ad set.""" + + pixel_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + promoted_object: Optional[PlatformConfigMetaPromotedObject] + """The object this ad set promotes (pixel, page, etc.).""" + + publisher_platforms: Optional[SequenceNotStr[str]] + """Platforms to publish on (facebook, instagram, messenger, audience_network).""" + + source_adset_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + start_time: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + status: Optional[Literal["ACTIVE", "PAUSED"]] + + targeting_automation: Optional[PlatformConfigMetaTargetingAutomation] + """Advantage+ audience expansion settings.""" + + threads_positions: Optional[SequenceNotStr[str]] + + updated_time: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + user_device: Optional[SequenceNotStr[str]] + + user_os: Optional[SequenceNotStr[str]] + + whatsapp_phone_number: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + whatsapp_positions: Optional[SequenceNotStr[str]] + + +class PlatformConfigTiktokAction(TypedDict, total=False): + """A single TikTok behavioral targeting entry. + + One category of past user behavior (what they did, over what window, on which kind of content). See docs/tiktok_api/ad_group.md § actions. + """ + + action_category_ids: Optional[SequenceNotStr[str]] + """Behavioral category IDs. Use /tool/action_category/ to list them.""" + + action_period: Optional[int] + """Lookback window in days. TikTok accepts 7, 15, 30, 60, 90, or 180.""" + + action_scene: Optional[Literal["VIDEO_RELATED", "CREATOR_RELATED", "HASHTAG_RELATED", "LIVE_RELATED"]] + """The category of TikTok content a behavioral targeting rule applies to. + + See docs/tiktok_api/ad_group.md § actions. + """ + + video_user_actions: Optional[ + List[Literal["WATCHED_TO_END", "LIKED", "COMMENTED", "SHARED", "FOLLOWED", "PROFILE_VISITED"]] + ] + """ + Specific video interactions (WATCHED_TO_END, LIKED, COMMENTED, SHARED, FOLLOWED, + PROFILE_VISITED). + """ + + +class PlatformConfigTiktokInstantFormConfigQuestion(TypedDict, total=False): + """A question for a TikTok instant form.""" + + field_type: Required[str] + """Question type (EMAIL, PHONE_NUMBER, NAME, CUSTOM).""" + + label: Optional[str] + """Custom label for the question.""" + + +class PlatformConfigTiktokInstantFormConfig(TypedDict, total=False): + """Instant form configuration for lead generation campaigns.""" + + privacy_policy_url: Required[str] + """URL to your privacy policy.""" + + questions: Required[Iterable[PlatformConfigTiktokInstantFormConfigQuestion]] + """Form questions (at least one required).""" + + button_text: Optional[str] + """Submit button text.""" + + greeting: Optional[str] + """Greeting text shown at the top of the form.""" + + name: Optional[str] + """Form name. Auto-generated if omitted.""" + + +class PlatformConfigTiktok(TypedDict, total=False): + """TikTok ad group configuration.""" + + actions: Optional[Iterable[PlatformConfigTiktokAction]] + + age_groups: Optional[List[Literal["AGE_13_17", "AGE_18_24", "AGE_25_34", "AGE_35_44", "AGE_45_54", "AGE_55_100"]]] + + app_id: Optional[str] + """App ID for app promotion campaigns.""" + + attribution_event_count: Optional[Literal["UNSET", "EVERY", "ONCE"]] + + audience_ids: Optional[SequenceNotStr[str]] + + audience_rule: Optional[Dict[str, object]] + """Represents untyped JSON""" + + audience_type: Optional[Literal["NORMAL", "SMART_INTERESTS_BEHAVIORS"]] + + bid_price: Optional[float] + """Bid price (cost per result for Cost Cap).""" + + bid_type: Optional[Literal["BID_TYPE_NO_BID", "BID_TYPE_CUSTOM"]] + """Bidding strategy (BID_TYPE_NO_BID, BID_TYPE_CUSTOM).""" + + billing_event: Optional[Literal["CPC", "CPM", "OCPM", "CPV"]] + """How you are billed on TikTok (CPC, CPM, OCPM, CPV).""" + + brand_safety_type: Optional[ + Literal["NO_BRAND_SAFETY", "STANDARD_INVENTORY", "LIMITED_INVENTORY", "FULL_INVENTORY", "EXPANDED_INVENTORY"] + ] + + budget_mode: Optional[Literal["BUDGET_MODE_DAY", "BUDGET_MODE_TOTAL", "BUDGET_MODE_DYNAMIC_DAILY_BUDGET"]] + """ + Budget mode (BUDGET_MODE_DAY, BUDGET_MODE_TOTAL, + BUDGET_MODE_DYNAMIC_DAILY_BUDGET). + """ + + carrier_ids: Optional[SequenceNotStr[str]] + + category_exclusion_ids: Optional[SequenceNotStr[str]] + + click_attribution_window: Optional[Literal["OFF", "ONE_DAY", "SEVEN_DAYS", "FOURTEEN_DAYS", "TWENTY_EIGHT_DAYS"]] + + comment_disabled: Optional[bool] + """Represents `true` or `false` values.""" + + contextual_tag_ids: Optional[SequenceNotStr[str]] + + conversion_bid_price: Optional[float] + """Target cost per conversion for oCPM.""" + + creative_material_mode: Optional[str] + """Creative delivery strategy.""" + + dayparting: Optional[str] + """Ad delivery schedule (48x7 character string).""" + + deep_funnel_event_source: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + deep_funnel_event_source_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + deep_funnel_optimization_status: Optional[Literal["ON", "OFF"]] + + device_model_ids: Optional[SequenceNotStr[str]] + + device_price_ranges: Optional[SequenceNotStr[str]] + + engaged_view_attribution_window: Optional[Literal["OFF", "ONE_DAY", "SEVEN_DAYS"]] + + excluded_audience_ids: Optional[SequenceNotStr[str]] + + excluded_location_ids: Optional[SequenceNotStr[str]] + """TikTok location/region IDs to exclude.""" + + frequency: Optional[int] + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + frequency_schedule: Optional[int] + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + gender: Optional[Literal["GENDER_UNLIMITED", "GENDER_MALE", "GENDER_FEMALE"]] + + identity_authorized_bc_id: Optional[str] + """Business Center ID for BC_AUTH_TT identity.""" + + identity_id: Optional[str] + """TikTok identity ID for the ad group.""" + + identity_type: Optional[str] + """Identity type (AUTH_CODE, TT_USER, BC_AUTH_TT).""" + + instant_form_config: Optional[PlatformConfigTiktokInstantFormConfig] + """Instant form configuration for lead generation campaigns.""" + + instant_form_id: Optional[str] + """ + TikTok instant form ID (set automatically when instant_form_config is provided). + """ + + interest_category_ids: Optional[SequenceNotStr[str]] + + interest_keyword_ids: Optional[SequenceNotStr[str]] + + inventory_filter_enabled: Optional[bool] + """Represents `true` or `false` values.""" + + ios14_targeting: Optional[Literal["UNSET", "IOS14_MINUS", "IOS14_PLUS", "ALL"]] + + isp_ids: Optional[SequenceNotStr[str]] + + languages: Optional[SequenceNotStr[str]] + + location_ids: Optional[SequenceNotStr[str]] + """TikTok location/region IDs for geo targeting.""" + + min_android_version: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + min_ios_version: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + network_types: Optional[SequenceNotStr[str]] + + operating_systems: Optional[List[Literal["ANDROID", "IOS"]]] + + operation_status: Optional[Literal["ENABLE", "DISABLE"]] + """Initial status (ENABLE, DISABLE).""" + + optimization_event: Optional[str] + """Conversion event (e.g., COMPLETE_PAYMENT).""" + + optimization_goal: Optional[ + Literal[ + "CLICK", + "CONVERT", + "INSTALL", + "IN_APP_EVENT", + "REACH", + "SHOW", + "VIDEO_VIEW", + "ENGAGED_VIEW", + "ENGAGED_VIEW_FIFTEEN", + "LEAD_GENERATION", + "PREFERRED_LEAD", + "CONVERSATION", + "FOLLOWERS", + "PROFILE_VIEWS", + "PAGE_VISIT", + "VALUE", + "AUTOMATIC_VALUE_OPTIMIZATION", + "TRAFFIC_LANDING_PAGE_VIEW", + "DESTINATION_VISIT", + "MT_LIVE_ROOM", + "PRODUCT_CLICK_IN_LIVE", + ] + ] + """What this ad group optimizes for on TikTok.""" + + pacing: Optional[Literal["PACING_MODE_SMOOTH", "PACING_MODE_FAST"]] + """Budget pacing (PACING_MODE_SMOOTH, PACING_MODE_FAST).""" + + pangle_audience_package_exclude_ids: Optional[SequenceNotStr[str]] + + pangle_audience_package_include_ids: Optional[SequenceNotStr[str]] + + pangle_block_app_ids: Optional[SequenceNotStr[str]] + + pixel_id: Optional[str] + """TikTok Pixel ID for conversion tracking.""" + + placement_type: Optional[Literal["PLACEMENT_TYPE_AUTOMATIC", "PLACEMENT_TYPE_NORMAL"]] + """Placement strategy (PLACEMENT_TYPE_AUTOMATIC, PLACEMENT_TYPE_NORMAL).""" + + placements: Optional[SequenceNotStr[str]] + """Placements (PLACEMENT_TIKTOK, PLACEMENT_PANGLE, etc.).""" + + product_set_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + product_source: Optional[Literal["CATALOG", "STORE", "SHOWCASE"]] + + promotion_type: Optional[str] + """Promotion type (optimization location).""" + + schedule_end_time: Optional[str] + """Schedule end time (UTC, YYYY-MM-DD HH:MM:SS).""" + + schedule_start_time: Optional[str] + """Schedule start time (UTC, YYYY-MM-DD HH:MM:SS).""" + + schedule_type: Optional[Literal["SCHEDULE_START_END", "SCHEDULE_FROM_NOW"]] + """Schedule type (SCHEDULE_START_END, SCHEDULE_FROM_NOW).""" + + secondary_optimization_event: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + shopping_ads_retargeting_actions_days: Optional[int] + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + shopping_ads_retargeting_type: Optional[Literal["OFF", "LAB1", "LAB2", "LAB3", "LAB4", "LAB5"]] + + spending_power: Optional[Literal["ALL", "HIGH"]] + + tiktok_subplacements: Optional[SequenceNotStr[str]] + """TikTok subplacements (IN_FEED, SEARCH_FEED, etc.).""" + + vertical_sensitivity_id: Optional[str] + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + video_download_disabled: Optional[bool] + """Represents `true` or `false` values.""" + + video_user_actions: Optional[SequenceNotStr[str]] + + view_attribution_window: Optional[Literal["OFF", "ONE_DAY", "SEVEN_DAYS"]] + + +class PlatformConfig(TypedDict, total=False): + """Platform-specific ad group configuration.""" + + meta: Optional[PlatformConfigMeta] + """Meta (Facebook/Instagram) ad set configuration.""" + + tiktok: Optional[PlatformConfigTiktok] + """TikTok ad group configuration.""" diff --git a/src/whop_sdk/types/ad_group_update_response.py b/src/whop_sdk/types/ad_group_update_response.py new file mode 100644 index 0000000..78caea6 --- /dev/null +++ b/src/whop_sdk/types/ad_group_update_response.py @@ -0,0 +1,438 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel + +__all__ = [ + "AdGroupUpdateResponse", + "AdCampaign", + "Config", + "ConfigTargeting", + "PlatformConfig", + "PlatformConfigMetaAdGroupPlatformConfigType", + "PlatformConfigTiktokAdGroupPlatformConfigType", +] + + +class AdCampaign(BaseModel): + """The parent ad campaign""" + + id: str + """The unique identifier for the ad campaign.""" + + platform: Optional[Literal["meta", "tiktok"]] = None + """The platforms where an ad campaign can run.""" + + status: Literal[ + "active", "paused", "inactive", "stale", "pending_refund", "payment_failed", "draft", "in_review", "flagged" + ] + """Current status of the campaign (active, paused, or inactive)""" + + title: str + """The title of the ad campaign""" + + +class ConfigTargeting(BaseModel): + """Audience targeting settings (demographics, geo, interests, audiences, devices).""" + + age_max: Optional[int] = None + """Maximum age for demographic targeting.""" + + age_min: Optional[int] = None + """Minimum age for demographic targeting.""" + + countries: Optional[List[str]] = None + """ISO 3166-1 alpha-2 country codes targeted.""" + + device_platforms: Optional[List[Literal["mobile", "desktop"]]] = None + """Device platforms targeted.""" + + exclude_audience_ids: Optional[List[str]] = None + """Platform audience IDs excluded.""" + + genders: Optional[List[Literal["male", "female", "all"]]] = None + """Genders targeted.""" + + include_audience_ids: Optional[List[str]] = None + """Platform audience IDs included.""" + + interest_ids: Optional[List[str]] = None + """Platform-specific interest IDs targeted.""" + + languages: Optional[List[str]] = None + """Language codes targeted.""" + + placement_type: Optional[Literal["automatic", "manual"]] = None + """Placement strategy for ad delivery.""" + + +class Config(BaseModel): + """Unified ad group configuration (platform-agnostic)""" + + bid_amount: Optional[int] = None + """Bid cap amount in cents. Used when bid_strategy is bid_cap or cost_cap.""" + + bid_strategy: Optional[Literal["lowest_cost", "bid_cap", "cost_cap"]] = None + """Bid strategy: lowest_cost, bid_cap, or cost_cap.""" + + billing_event: Optional[Literal["impressions", "clicks", "optimized_cpm", "video_views"]] = None + """How you are billed (e.g., impressions, clicks).""" + + end_time: Optional[str] = None + """Scheduled end time (ISO8601). Required for lifetime budgets.""" + + frequency_cap: Optional[int] = None + """Maximum number of times to show ads to each person in the frequency interval.""" + + frequency_cap_interval_days: Optional[int] = None + """Number of days for the frequency cap interval.""" + + optimization_goal: Optional[ + Literal[ + "conversions", + "link_clicks", + "landing_page_views", + "reach", + "impressions", + "app_installs", + "video_views", + "lead_generation", + "value", + "page_likes", + "conversations", + "ad_recall_lift", + "two_second_continuous_video_views", + "post_engagement", + "event_responses", + "reminders_set", + "quality_lead", + ] + ] = None + """What the ad group optimizes for (e.g., conversions, link_clicks, reach).""" + + pacing: Optional[Literal["standard", "accelerated"]] = None + """Budget pacing: standard (even) or accelerated (fast).""" + + start_time: Optional[str] = None + """Scheduled start time (ISO8601).""" + + targeting: Optional[ConfigTargeting] = None + """Audience targeting settings (demographics, geo, interests, audiences, devices).""" + + +class PlatformConfigMetaAdGroupPlatformConfigType(BaseModel): + """Meta (Facebook/Instagram) ad set configuration.""" + + attribution_spec: Optional[List[Dict[str, object]]] = None + + bid_amount: Optional[int] = None + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + bid_strategy: Optional[ + Literal["LOWEST_COST_WITHOUT_CAP", "LOWEST_COST_WITH_BID_CAP", "COST_CAP", "LOWEST_COST_WITH_MIN_ROAS"] + ] = None + + billing_event: Optional[ + Literal[ + "APP_INSTALLS", + "CLICKS", + "IMPRESSIONS", + "LINK_CLICKS", + "NONE", + "OFFER_CLAIMS", + "PAGE_LIKES", + "POST_ENGAGEMENT", + "THRUPLAY", + "PURCHASE", + "LISTING_INTERACTION", + ] + ] = None + + daily_budget: Optional[int] = None + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + destination_type: Optional[ + Literal[ + "UNDEFINED", + "WEBSITE", + "APP", + "FACEBOOK", + "MESSENGER", + "WHATSAPP", + "INSTAGRAM_DIRECT", + "INSTAGRAM_PROFILE", + "PHONE_CALL", + "SHOP_AUTOMATIC", + "APPLINKS_AUTOMATIC", + "ON_AD", + "ON_POST", + "ON_VIDEO", + "ON_PAGE", + "ON_EVENT", + "MESSAGING_MESSENGER_WHATSAPP", + "MESSAGING_INSTAGRAM_DIRECT_MESSENGER", + "MESSAGING_INSTAGRAM_DIRECT_WHATSAPP", + "MESSAGING_INSTAGRAM_DIRECT_MESSENGER_WHATSAPP", + "INSTAGRAM_PROFILE_AND_FACEBOOK_PAGE", + "FACEBOOK_PAGE", + "INSTAGRAM_LIVE", + "FACEBOOK_LIVE", + "IMAGINE", + "LEAD_FROM_IG_DIRECT", + "LEAD_FROM_MESSENGER", + "WEBSITE_AND_LEAD_FORM", + "WEBSITE_AND_PHONE_CALL", + "BROADCAST_CHANNEL", + ] + ] = None + + end_time: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + excluded_geo_locations: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + facebook_positions: Optional[List[str]] = None + + geo_locations: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + instagram_actor_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + instagram_positions: Optional[List[str]] = None + + lifetime_budget: Optional[int] = None + """Represents non-fractional signed whole numeric values. + + Int can represent values between -(2^31) and 2^31 - 1. + """ + + optimization_goal: Optional[ + Literal[ + "NONE", + "APP_INSTALLS", + "AD_RECALL_LIFT", + "ENGAGED_USERS", + "EVENT_RESPONSES", + "IMPRESSIONS", + "LEAD_GENERATION", + "QUALITY_LEAD", + "LINK_CLICKS", + "OFFSITE_CONVERSIONS", + "PAGE_LIKES", + "POST_ENGAGEMENT", + "QUALITY_CALL", + "REACH", + "LANDING_PAGE_VIEWS", + "VISIT_INSTAGRAM_PROFILE", + "VALUE", + "THRUPLAY", + "DERIVED_EVENTS", + "APP_INSTALLS_AND_OFFSITE_CONVERSIONS", + "CONVERSATIONS", + "IN_APP_VALUE", + "MESSAGING_PURCHASE_CONVERSION", + "SUBSCRIBERS", + "REMINDERS_SET", + "MEANINGFUL_CALL_ATTEMPT", + "PROFILE_VISIT", + "PROFILE_AND_PAGE_ENGAGEMENT", + "TWO_SECOND_CONTINUOUS_VIDEO_VIEWS", + "ENGAGED_REACH", + "ENGAGED_PAGE_VIEWS", + "MESSAGING_DEEP_CONVERSATION_AND_FOLLOW", + "ADVERTISER_SILOED_VALUE", + "AUTOMATIC_OBJECTIVE", + "MESSAGING_APPOINTMENT_CONVERSION", + ] + ] = None + + page_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + platform: Literal["meta", "tiktok"] + """The ad platform (meta, tiktok).""" + + promoted_object: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + publisher_platforms: Optional[List[str]] = None + + status: Optional[Literal["ACTIVE", "PAUSED"]] = None + + targeting_automation: Optional[Dict[str, object]] = None + """Represents untyped JSON""" + + typename: Literal["MetaAdGroupPlatformConfigType"] + """The typename of this object""" + + +class PlatformConfigTiktokAdGroupPlatformConfigType(BaseModel): + """TikTok ad group configuration.""" + + bid_price: Optional[float] = None + """ + Represents signed double-precision fractional values as specified by + [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point). + """ + + bid_type: Optional[Literal["BID_TYPE_NO_BID", "BID_TYPE_CUSTOM"]] = None + + billing_event: Optional[Literal["CPC", "CPM", "OCPM", "CPV"]] = None + + budget_mode: Optional[Literal["BUDGET_MODE_DAY", "BUDGET_MODE_TOTAL", "BUDGET_MODE_DYNAMIC_DAILY_BUDGET"]] = None + + conversion_bid_price: Optional[float] = None + """ + Represents signed double-precision fractional values as specified by + [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point). + """ + + identity_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + identity_type: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + operation_status: Optional[Literal["ENABLE", "DISABLE"]] = None + + optimization_event: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + optimization_goal: Optional[ + Literal[ + "CLICK", + "CONVERT", + "INSTALL", + "IN_APP_EVENT", + "REACH", + "SHOW", + "VIDEO_VIEW", + "ENGAGED_VIEW", + "ENGAGED_VIEW_FIFTEEN", + "LEAD_GENERATION", + "PREFERRED_LEAD", + "CONVERSATION", + "FOLLOWERS", + "PROFILE_VIEWS", + "PAGE_VISIT", + "VALUE", + "AUTOMATIC_VALUE_OPTIMIZATION", + "TRAFFIC_LANDING_PAGE_VIEW", + "DESTINATION_VISIT", + "MT_LIVE_ROOM", + "PRODUCT_CLICK_IN_LIVE", + ] + ] = None + + pacing: Optional[Literal["PACING_MODE_SMOOTH", "PACING_MODE_FAST"]] = None + + pixel_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + placement_type: Optional[Literal["PLACEMENT_TYPE_AUTOMATIC", "PLACEMENT_TYPE_NORMAL"]] = None + + placements: Optional[List[str]] = None + + platform: Literal["meta", "tiktok"] + """The ad platform (meta, tiktok).""" + + schedule_end_time: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + schedule_start_time: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + schedule_type: Optional[Literal["SCHEDULE_START_END", "SCHEDULE_FROM_NOW"]] = None + + typename: Literal["TiktokAdGroupPlatformConfigType"] + """The typename of this object""" + + +PlatformConfig: TypeAlias = Annotated[ + Union[ + Optional[PlatformConfigMetaAdGroupPlatformConfigType], Optional[PlatformConfigTiktokAdGroupPlatformConfigType] + ], + PropertyInfo(discriminator="typename"), +] + + +class AdGroupUpdateResponse(BaseModel): + """An external ad group (ad set) belonging to an ad campaign""" + + id: str + """The unique identifier for the external ad group.""" + + ad_campaign: AdCampaign + """The parent ad campaign""" + + config: Optional[Config] = None + """Unified ad group configuration (platform-agnostic)""" + + created_at: datetime + """The datetime the external ad group was created.""" + + daily_budget: Optional[float] = None + """Daily budget in dollars (nil for lifetime budgets)""" + + name: Optional[str] = None + """Human-readable ad group name""" + + platform_config: PlatformConfig + """Typed platform-specific configuration. + + Use inline fragments (... on MetaAdGroupPlatformConfigType). + """ + + status: Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"] + """Current operational status of the ad group""" + + updated_at: datetime + """The datetime the external ad group was last updated.""" diff --git a/src/whop_sdk/types/ad_list_params.py b/src/whop_sdk/types/ad_list_params.py new file mode 100644 index 0000000..17c36e1 --- /dev/null +++ b/src/whop_sdk/types/ad_list_params.py @@ -0,0 +1,52 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["AdListParams"] + + +class AdListParams(TypedDict, total=False): + ad_group_id: Optional[str] + """Filter by ad group. + + Provide exactly one of ad_group_id, campaign_id, or company_id. + """ + + after: Optional[str] + """Returns the elements in the list that come after the specified cursor.""" + + before: Optional[str] + """Returns the elements in the list that come before the specified cursor.""" + + campaign_id: Optional[str] + """Filter by campaign. + + Provide exactly one of ad_group_id, campaign_id, or company_id. + """ + + company_id: Optional[str] + """Filter by company. + + Provide exactly one of ad_group_id, campaign_id, or company_id. + """ + + created_after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Only return ads created after this timestamp.""" + + created_before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Only return ads created before this timestamp.""" + + first: Optional[int] + """Returns the first _n_ elements from the list.""" + + last: Optional[int] + """Returns the last _n_ elements from the list.""" + + status: Optional[Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"]] + """The status of an external ad.""" diff --git a/src/whop_sdk/types/ad_list_response.py b/src/whop_sdk/types/ad_list_response.py new file mode 100644 index 0000000..c5254c5 --- /dev/null +++ b/src/whop_sdk/types/ad_list_response.py @@ -0,0 +1,221 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel + +__all__ = [ + "AdListResponse", + "PlatformConfig", + "PlatformConfigMetaAdPlatformConfigType", + "PlatformConfigTiktokAdPlatformConfigType", +] + + +class PlatformConfigMetaAdPlatformConfigType(BaseModel): + """Meta (Facebook/Instagram) ad configuration.""" + + call_to_action_type: Optional[ + Literal[ + "LEARN_MORE", + "SHOP_NOW", + "SIGN_UP", + "SUBSCRIBE", + "GET_STARTED", + "BOOK_NOW", + "APPLY_NOW", + "CONTACT_US", + "DOWNLOAD", + "ORDER_NOW", + "BUY_NOW", + "GET_QUOTE", + "MESSAGE_PAGE", + "WHATSAPP_MESSAGE", + "INSTAGRAM_MESSAGE", + "CALL_NOW", + "GET_DIRECTIONS", + "SEND_UPDATES", + "GET_OFFER", + "WATCH_MORE", + "LISTEN_NOW", + "PLAY_GAME", + "OPEN_LINK", + "NO_BUTTON", + "GET_OFFER_VIEW", + "GET_EVENT_TICKETS", + "SEE_MENU", + "REQUEST_TIME", + "EVENT_RSVP", + ] + ] = None + + headline: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + instagram_actor_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + link_url: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + name: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + page_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + platform: Literal["meta", "tiktok"] + """The ad platform.""" + + primary_text: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + typename: Literal["MetaAdPlatformConfigType"] + """The typename of this object""" + + url_tags: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + +class PlatformConfigTiktokAdPlatformConfigType(BaseModel): + """TikTok ad configuration.""" + + ad_format: Optional[Literal["SINGLE_IMAGE", "SINGLE_VIDEO", "CAROUSEL_ADS", "CATALOG_CAROUSEL", "LIVE_CONTENT"]] = ( + None + ) + + ad_name: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + ad_text: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + call_to_action: Optional[ + Literal[ + "LEARN_MORE", + "DOWNLOAD", + "SHOP_NOW", + "SIGN_UP", + "CONTACT_US", + "APPLY_NOW", + "BOOK_NOW", + "PLAY_GAME", + "WATCH_NOW", + "READ_MORE", + "VIEW_NOW", + "GET_QUOTE", + "ORDER_NOW", + "INSTALL_NOW", + "GET_SHOWTIMES", + "LISTEN_NOW", + "INTERESTED", + "SUBSCRIBE", + "GET_TICKETS_NOW", + "EXPERIENCE_NOW", + "PRE_ORDER_NOW", + "VISIT_STORE", + ] + ] = None + """TikTok call-to-action button text. See docs/tiktok_api/ad.md § call_to_action.""" + + identity_authorized_bc_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + identity_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + identity_type: Optional[Literal["CUSTOMIZED_USER", "AUTH_CODE", "TT_USER", "BC_AUTH_TT"]] = None + + image_ids: Optional[List[str]] = None + + landing_page_url: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + platform: Literal["meta", "tiktok"] + """The ad platform.""" + + typename: Literal["TiktokAdPlatformConfigType"] + """The typename of this object""" + + video_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + +PlatformConfig: TypeAlias = Annotated[ + Union[Optional[PlatformConfigMetaAdPlatformConfigType], Optional[PlatformConfigTiktokAdPlatformConfigType]], + PropertyInfo(discriminator="typename"), +] + + +class AdListResponse(BaseModel): + """An ad belonging to an ad group""" + + id: str + """Unique identifier for the ad.""" + + created_at: datetime + """When the ad was created.""" + + platform_config: PlatformConfig + """Typed platform-specific configuration.""" + + status: Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"] + """Current status of the ad.""" + + updated_at: datetime + """When the ad was last updated.""" diff --git a/src/whop_sdk/types/ad_retrieve_response.py b/src/whop_sdk/types/ad_retrieve_response.py new file mode 100644 index 0000000..4b49b9b --- /dev/null +++ b/src/whop_sdk/types/ad_retrieve_response.py @@ -0,0 +1,249 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel + +__all__ = [ + "AdRetrieveResponse", + "ExternalAdCreativeSet", + "ExternalAdGroup", + "PlatformConfig", + "PlatformConfigMetaAdPlatformConfigType", + "PlatformConfigTiktokAdPlatformConfigType", +] + + +class ExternalAdCreativeSet(BaseModel): + """The creative set used by this ad.""" + + id: str + """The unique identifier for the external ad creative set.""" + + +class ExternalAdGroup(BaseModel): + """The parent ad group.""" + + id: str + """The unique identifier for the external ad group.""" + + name: Optional[str] = None + """Human-readable ad group name""" + + status: Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"] + """Current operational status of the ad group""" + + +class PlatformConfigMetaAdPlatformConfigType(BaseModel): + """Meta (Facebook/Instagram) ad configuration.""" + + call_to_action_type: Optional[ + Literal[ + "LEARN_MORE", + "SHOP_NOW", + "SIGN_UP", + "SUBSCRIBE", + "GET_STARTED", + "BOOK_NOW", + "APPLY_NOW", + "CONTACT_US", + "DOWNLOAD", + "ORDER_NOW", + "BUY_NOW", + "GET_QUOTE", + "MESSAGE_PAGE", + "WHATSAPP_MESSAGE", + "INSTAGRAM_MESSAGE", + "CALL_NOW", + "GET_DIRECTIONS", + "SEND_UPDATES", + "GET_OFFER", + "WATCH_MORE", + "LISTEN_NOW", + "PLAY_GAME", + "OPEN_LINK", + "NO_BUTTON", + "GET_OFFER_VIEW", + "GET_EVENT_TICKETS", + "SEE_MENU", + "REQUEST_TIME", + "EVENT_RSVP", + ] + ] = None + + headline: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + instagram_actor_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + link_url: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + name: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + page_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + platform: Literal["meta", "tiktok"] + """The ad platform.""" + + primary_text: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + typename: Literal["MetaAdPlatformConfigType"] + """The typename of this object""" + + url_tags: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + +class PlatformConfigTiktokAdPlatformConfigType(BaseModel): + """TikTok ad configuration.""" + + ad_format: Optional[Literal["SINGLE_IMAGE", "SINGLE_VIDEO", "CAROUSEL_ADS", "CATALOG_CAROUSEL", "LIVE_CONTENT"]] = ( + None + ) + + ad_name: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + ad_text: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + call_to_action: Optional[ + Literal[ + "LEARN_MORE", + "DOWNLOAD", + "SHOP_NOW", + "SIGN_UP", + "CONTACT_US", + "APPLY_NOW", + "BOOK_NOW", + "PLAY_GAME", + "WATCH_NOW", + "READ_MORE", + "VIEW_NOW", + "GET_QUOTE", + "ORDER_NOW", + "INSTALL_NOW", + "GET_SHOWTIMES", + "LISTEN_NOW", + "INTERESTED", + "SUBSCRIBE", + "GET_TICKETS_NOW", + "EXPERIENCE_NOW", + "PRE_ORDER_NOW", + "VISIT_STORE", + ] + ] = None + """TikTok call-to-action button text. See docs/tiktok_api/ad.md § call_to_action.""" + + identity_authorized_bc_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + identity_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + identity_type: Optional[Literal["CUSTOMIZED_USER", "AUTH_CODE", "TT_USER", "BC_AUTH_TT"]] = None + + image_ids: Optional[List[str]] = None + + landing_page_url: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + platform: Literal["meta", "tiktok"] + """The ad platform.""" + + typename: Literal["TiktokAdPlatformConfigType"] + """The typename of this object""" + + video_id: Optional[str] = None + """Represents textual data as UTF-8 character sequences. + + This type is most often used by GraphQL to represent free-form human-readable + text. + """ + + +PlatformConfig: TypeAlias = Annotated[ + Union[Optional[PlatformConfigMetaAdPlatformConfigType], Optional[PlatformConfigTiktokAdPlatformConfigType]], + PropertyInfo(discriminator="typename"), +] + + +class AdRetrieveResponse(BaseModel): + """An ad belonging to an ad group""" + + id: str + """Unique identifier for the ad.""" + + created_at: datetime + """When the ad was created.""" + + external_ad_creative_set: Optional[ExternalAdCreativeSet] = None + """The creative set used by this ad.""" + + external_ad_group: ExternalAdGroup + """The parent ad group.""" + + platform_config: PlatformConfig + """Typed platform-specific configuration.""" + + status: Literal["active", "paused", "inactive", "in_review", "rejected", "flagged"] + """Current status of the ad.""" + + updated_at: datetime + """When the ad was last updated.""" diff --git a/src/whop_sdk/types/bounty_create_params.py b/src/whop_sdk/types/bounty_create_params.py new file mode 100644 index 0000000..0934b79 --- /dev/null +++ b/src/whop_sdk/types/bounty_create_params.py @@ -0,0 +1,50 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +from .._types import SequenceNotStr +from .shared.currency import Currency + +__all__ = ["BountyCreateParams"] + + +class BountyCreateParams(TypedDict, total=False): + base_unit_amount: Required[float] + """The amount paid to each approved submission. + + The total bounty pool funded is this amount times accepted_submissions_limit. + """ + + currency: Required[Currency] + """The currency for the bounty pool funding amount.""" + + description: Required[str] + """The description of the bounty.""" + + title: Required[str] + """The title of the bounty.""" + + accepted_submissions_limit: Optional[int] + """The number of submissions that can be approved before the bounty closes. + + Defaults to 1. + """ + + allowed_country_codes: Optional[SequenceNotStr[str]] + """The ISO3166 country codes where this bounty should be visible. + + Empty means globally visible. + """ + + experience_id: Optional[str] + """An optional experience to scope the bounty to.""" + + origin_account_id: Optional[str] + """The user (user*\\**) or company (biz*\\**) tag whose balance funds this bounty pool. + + Defaults to the requester's personal balance when omitted. The requester must be + the user themself or an owner/admin of the company. + """ diff --git a/src/whop_sdk/types/bounty_create_response.py b/src/whop_sdk/types/bounty_create_response.py new file mode 100644 index 0000000..94c4f5e --- /dev/null +++ b/src/whop_sdk/types/bounty_create_response.py @@ -0,0 +1,46 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel +from .shared.currency import Currency + +__all__ = ["BountyCreateResponse"] + + +class BountyCreateResponse(BaseModel): + """A privately accessible bounty.""" + + id: str + """The unique identifier for the bounty.""" + + bounty_type: Literal["classic", "user_funded", "workforce"] + """The underlying bounty implementation type.""" + + created_at: datetime + """The datetime the bounty was created.""" + + currency: Currency + """The currency used for the bounty funds.""" + + description: str + """The description of the bounty.""" + + status: Literal["published", "archived"] + """The current lifecycle status of the bounty.""" + + title: str + """The title of the bounty.""" + + total_available: float + """The total amount currently funded in the bounty pool for payout.""" + + total_paid: float + """The total amount paid out for this bounty.""" + + updated_at: datetime + """The datetime the bounty was last updated.""" + + vote_threshold: int + """The number of watcher votes required before the submission can resolve.""" diff --git a/src/whop_sdk/types/bounty_list_params.py b/src/whop_sdk/types/bounty_list_params.py new file mode 100644 index 0000000..cb80eab --- /dev/null +++ b/src/whop_sdk/types/bounty_list_params.py @@ -0,0 +1,36 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, TypedDict + +from .shared.direction import Direction + +__all__ = ["BountyListParams"] + + +class BountyListParams(TypedDict, total=False): + after: Optional[str] + """Returns the elements in the list that come after the specified cursor.""" + + before: Optional[str] + """Returns the elements in the list that come before the specified cursor.""" + + direction: Optional[Direction] + """The direction of the sort.""" + + experience_id: Optional[str] + """The experience to list bounties for. + + When omitted, returns bounties with no experience. + """ + + first: Optional[int] + """Returns the first _n_ elements from the list.""" + + last: Optional[int] + """Returns the last _n_ elements from the list.""" + + status: Optional[Literal["published", "archived"]] + """The available bounty statuses to choose from.""" diff --git a/src/whop_sdk/types/bounty_list_response.py b/src/whop_sdk/types/bounty_list_response.py new file mode 100644 index 0000000..0117a1b --- /dev/null +++ b/src/whop_sdk/types/bounty_list_response.py @@ -0,0 +1,46 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel +from .shared.currency import Currency + +__all__ = ["BountyListResponse"] + + +class BountyListResponse(BaseModel): + """A privately accessible bounty.""" + + id: str + """The unique identifier for the bounty.""" + + bounty_type: Literal["classic", "user_funded", "workforce"] + """The underlying bounty implementation type.""" + + created_at: datetime + """The datetime the bounty was created.""" + + currency: Currency + """The currency used for the bounty funds.""" + + description: str + """The description of the bounty.""" + + status: Literal["published", "archived"] + """The current lifecycle status of the bounty.""" + + title: str + """The title of the bounty.""" + + total_available: float + """The total amount currently funded in the bounty pool for payout.""" + + total_paid: float + """The total amount paid out for this bounty.""" + + updated_at: datetime + """The datetime the bounty was last updated.""" + + vote_threshold: int + """The number of watcher votes required before the submission can resolve.""" diff --git a/src/whop_sdk/types/bounty_retrieve_response.py b/src/whop_sdk/types/bounty_retrieve_response.py new file mode 100644 index 0000000..d9df060 --- /dev/null +++ b/src/whop_sdk/types/bounty_retrieve_response.py @@ -0,0 +1,46 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel +from .shared.currency import Currency + +__all__ = ["BountyRetrieveResponse"] + + +class BountyRetrieveResponse(BaseModel): + """A privately accessible bounty.""" + + id: str + """The unique identifier for the bounty.""" + + bounty_type: Literal["classic", "user_funded", "workforce"] + """The underlying bounty implementation type.""" + + created_at: datetime + """The datetime the bounty was created.""" + + currency: Currency + """The currency used for the bounty funds.""" + + description: str + """The description of the bounty.""" + + status: Literal["published", "archived"] + """The current lifecycle status of the bounty.""" + + title: str + """The title of the bounty.""" + + total_available: float + """The total amount currently funded in the bounty pool for payout.""" + + total_paid: float + """The total amount paid out for this bounty.""" + + updated_at: datetime + """The datetime the bounty was last updated.""" + + vote_threshold: int + """The number of watcher votes required before the submission can resolve.""" diff --git a/src/whop_sdk/types/company_create_api_key_params.py b/src/whop_sdk/types/company_create_api_key_params.py new file mode 100644 index 0000000..a908504 --- /dev/null +++ b/src/whop_sdk/types/company_create_api_key_params.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional +from typing_extensions import Literal, Required, TypedDict + +from .._types import SequenceNotStr + +__all__ = ["CompanyCreateAPIKeyParams", "Permission"] + + +class CompanyCreateAPIKeyParams(TypedDict, total=False): + child_company_id: Required[str] + """The unique identifier of the connected account to create the API key for (e.g. + + 'biz_xxx'). + """ + + name: Optional[str] + """A human-readable name for the API key, such as 'Production API Key'.""" + + permissions: Optional[Iterable[Permission]] + """Granular permission statements defining which actions this API key can perform. + + Either permissions or role must be provided. + """ + + role: Optional[Literal["owner", "admin", "moderator", "sales_manager", "advertiser"]] + """The different system roles that can be assigned.""" + + +class Permission(TypedDict, total=False): + """Input for a single permissions statement""" + + actions: Required[SequenceNotStr[str]] + """Actions covered by this statement""" + + grant: Required[bool] + """Whether the actions are granted or denied""" + + resources: Required[SequenceNotStr[str]] + """Resource identifiers. + + Can look like 'biz*xxxx' or 'biz_xxx|pass*_|exp*xxx' or 'biz_xxx|app_xxx' or + 'biz_xxx|pass_xxx|exp_xxx' or 'biz_xxx|pass_xxx' or 'biz_xxx|pass*_' + """ diff --git a/src/whop_sdk/types/company_create_api_key_response.py b/src/whop_sdk/types/company_create_api_key_response.py new file mode 100644 index 0000000..691c6ec --- /dev/null +++ b/src/whop_sdk/types/company_create_api_key_response.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["CompanyCreateAPIKeyResponse"] + + +class CompanyCreateAPIKeyResponse(BaseModel): + """An API key created for a child company, including the one-time secret key.""" + + id: str + """The unique identifier for the authorized api key.""" + + name: Optional[str] = None + """A user set name to identify an API key""" + + secret_key: str + """The secret key used to authenticate requests. Only returned at creation time.""" diff --git a/src/whop_sdk/types/conversion_create_params.py b/src/whop_sdk/types/conversion_create_params.py new file mode 100644 index 0000000..e833dba --- /dev/null +++ b/src/whop_sdk/types/conversion_create_params.py @@ -0,0 +1,183 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from datetime import datetime +from typing_extensions import Literal, Required, Annotated, TypedDict + +from .._utils import PropertyInfo +from .shared.currency import Currency + +__all__ = ["ConversionCreateParams", "Context", "User"] + + +class ConversionCreateParams(TypedDict, total=False): + company_id: Required[str] + """The company to associate with this event.""" + + event_name: Required[ + Literal["lead", "submit_application", "contact", "complete_registration", "schedule", "custom"] + ] + """The type of event.""" + + action_source: Optional[ + Literal[ + "email", + "website", + "app", + "phone_call", + "chat", + "physical_store", + "system_generated", + "business_messaging", + "other", + ] + ] + """The channel where an event originated""" + + context: Optional[Context] + """Tracking and attribution context.""" + + currency: Optional[Currency] + """The available currencies on the platform""" + + custom_name: Optional[str] + """Custom event name when event_name is 'custom'.""" + + event_id: Optional[str] + """Client-provided identifier for deduplication. Generated if omitted.""" + + event_time: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """When the event occurred. Defaults to now.""" + + plan_id: Optional[str] + """The plan associated with the event.""" + + product_id: Optional[str] + """The product associated with the event.""" + + referrer_url: Optional[str] + """The referring URL.""" + + url: Optional[str] + """The URL where the event occurred.""" + + user: Optional[User] + """User identity and profile data.""" + + value: Optional[float] + """Monetary value associated with the event.""" + + +class Context(TypedDict, total=False): + """Tracking and attribution context.""" + + ad_campaign_id: Optional[str] + """Ad campaign ID.""" + + ad_id: Optional[str] + """Ad ID.""" + + ad_set_id: Optional[str] + """Ad set ID.""" + + fbclid: Optional[str] + """Facebook click ID.""" + + fbp: Optional[str] + """Facebook browser pixel ID.""" + + ga: Optional[str] + """Google Analytics client ID.""" + + gclid: Optional[str] + """Google click ID.""" + + ig_sid: Optional[str] + """Instagram session ID.""" + + ip_address: Optional[str] + """IP address.""" + + ttclid: Optional[str] + """TikTok click ID.""" + + ttp: Optional[str] + """TikTok pixel ID.""" + + user_agent: Optional[str] + """Browser user agent string.""" + + utm_campaign: Optional[str] + """UTM campaign parameter.""" + + utm_content: Optional[str] + """UTM content parameter.""" + + utm_id: Optional[str] + """UTM ID parameter.""" + + utm_medium: Optional[str] + """UTM medium parameter.""" + + utm_source: Optional[str] + """UTM source parameter.""" + + utm_term: Optional[str] + """UTM term parameter.""" + + +class User(TypedDict, total=False): + """User identity and profile data.""" + + anonymous_id: Optional[str] + """An anonymous identifier for the user.""" + + birthdate: Optional[str] + """Date of birth (YYYY-MM-DD).""" + + city: Optional[str] + """City.""" + + country: Optional[str] + """Country.""" + + email: Optional[str] + """Email address.""" + + external_id: Optional[str] + """An external identifier for the user.""" + + first_name: Optional[str] + """First name.""" + + gender: Optional[Literal["male", "female"]] + """Gender""" + + last_name: Optional[str] + """Last name.""" + + member_id: Optional[str] + """The Whop member ID.""" + + membership_id: Optional[str] + """The Whop membership ID.""" + + name: Optional[str] + """Full display name.""" + + phone: Optional[str] + """Phone number.""" + + postal_code: Optional[str] + """Postal code.""" + + state: Optional[str] + """State or region.""" + + user_id: Optional[str] + """The Whop user ID.""" + + username: Optional[str] + """Username.""" diff --git a/src/whop_sdk/types/conversion_create_response.py b/src/whop_sdk/types/conversion_create_response.py new file mode 100644 index 0000000..eeef3e8 --- /dev/null +++ b/src/whop_sdk/types/conversion_create_response.py @@ -0,0 +1,12 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["ConversionCreateResponse"] + + +class ConversionCreateResponse(BaseModel): + """A tracked conversion event""" + + id: str + """The unique identifier for the conversion""" diff --git a/src/whop_sdk/types/stat_describe_params.py b/src/whop_sdk/types/stat_describe_params.py new file mode 100644 index 0000000..b97e597 --- /dev/null +++ b/src/whop_sdk/types/stat_describe_params.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import TypedDict + +__all__ = ["StatDescribeParams"] + + +class StatDescribeParams(TypedDict, total=False): + company_id: Optional[str] + """Scope query to a specific company.""" + + resource: Optional[str] + """ + Resource path using : as separator (e.g., 'receipts', 'payments:membership', + 'receipts:gross_revenue'). + """ + + user_id: Optional[str] + """Scope query to a specific user.""" diff --git a/src/whop_sdk/types/stat_describe_response.py b/src/whop_sdk/types/stat_describe_response.py new file mode 100644 index 0000000..029b5b3 --- /dev/null +++ b/src/whop_sdk/types/stat_describe_response.py @@ -0,0 +1,256 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel + +__all__ = [ + "StatDescribeResponse", + "DescribeRoot", + "DescribeRootDebug", + "DescribeRootMetric", + "DescribeNode", + "DescribeNodeAssociation", + "DescribeNodeDebug", + "DescribeNodeMetric", + "DescribeMetric", + "DescribeMetricDebug", + "DescribeView", + "DescribeViewAssociation", + "DescribeViewDebug", + "DescribeViewMetric", +] + + +class DescribeRootDebug(BaseModel): + """Debug information.""" + + request_id: Optional[str] = None + """Unique request identifier for debugging.""" + + +class DescribeRootMetric(BaseModel): + """A metric available for querying.""" + + name: str + """The metric name.""" + + node_path: str + """The node path this metric operates on.""" + + supported_engines: List[str] + """Query engines that support this metric.""" + + +class DescribeRoot(BaseModel): + """Root schema description showing available nodes, views, and metrics.""" + + debug: Optional[DescribeRootDebug] = None + """Debug information.""" + + metrics: List[DescribeRootMetric] + """Available metrics.""" + + nodes: List[str] + """Available root nodes.""" + + typename: Literal["DescribeRoot"] + """The typename of this object""" + + views: List[str] + """Available API resource views.""" + + +class DescribeNodeAssociation(BaseModel): + """An association or child path available for navigation.""" + + event_name: Optional[str] = None + """The event name (for event type).""" + + model: Optional[str] = None + """The associated model class name (for model associations).""" + + name: str + """The association name.""" + + path: Optional[str] = None + """The full path (for event associations).""" + + type: str + """The type (belongs_to, has_many, has_one, event, namespace).""" + + +class DescribeNodeDebug(BaseModel): + """Debug information.""" + + request_id: Optional[str] = None + """Unique request identifier for debugging.""" + + +class DescribeNodeMetric(BaseModel): + """A metric available for querying.""" + + name: str + """The metric name.""" + + node_path: str + """The node path this metric operates on.""" + + supported_engines: List[str] + """Query engines that support this metric.""" + + +class DescribeNode(BaseModel): + """Description of a node (model) including its columns and associations.""" + + associations: List[DescribeNodeAssociation] + """Available associations or child paths.""" + + columns: List[str] + """Available columns.""" + + debug: Optional[DescribeNodeDebug] = None + """Debug information.""" + + engine: str + """The query engine being used.""" + + metrics: List[DescribeNodeMetric] + """Available metrics for this node.""" + + node: str + """The node path being described.""" + + sample: Optional[List[Dict[str, object]]] = None + """Sample data rows.""" + + sortable_columns: List[str] + """Columns that can be used for sorting.""" + + typename: Literal["DescribeNode"] + """The typename of this object""" + + +class DescribeMetricDebug(BaseModel): + """Debug information.""" + + request_id: Optional[str] = None + """Unique request identifier for debugging.""" + + +class DescribeMetric(BaseModel): + """Description of a metric including its configuration and SQL.""" + + breakdownable_columns: List[str] + """Columns that can be used for breakdowns.""" + + debug: Optional[DescribeMetricDebug] = None + """Debug information.""" + + engine: str + """The query engine being used.""" + + filterable_columns: List[str] + """Columns that can be used for filtering.""" + + metric: str + """The metric name.""" + + node: str + """The node path this metric operates on.""" + + sql: Optional[str] = None + """The generated SQL query.""" + + supported_engines: List[str] + """Query engines that support this metric.""" + + timestamp_column: str + """The timestamp column used for time filtering.""" + + typename: Literal["DescribeMetric"] + """The typename of this object""" + + +class DescribeViewAssociation(BaseModel): + """An association or child path available for navigation.""" + + event_name: Optional[str] = None + """The event name (for event type).""" + + model: Optional[str] = None + """The associated model class name (for model associations).""" + + name: str + """The association name.""" + + path: Optional[str] = None + """The full path (for event associations).""" + + type: str + """The type (belongs_to, has_many, has_one, event, namespace).""" + + +class DescribeViewDebug(BaseModel): + """Debug information.""" + + request_id: Optional[str] = None + """Unique request identifier for debugging.""" + + +class DescribeViewMetric(BaseModel): + """A metric available for querying.""" + + name: str + """The metric name.""" + + node_path: str + """The node path this metric operates on.""" + + supported_engines: List[str] + """Query engines that support this metric.""" + + +class DescribeView(BaseModel): + """Description of an API resource view including its columns and associations.""" + + associations: List[DescribeViewAssociation] + """Available associations.""" + + columns: List[str] + """Available columns.""" + + debug: Optional[DescribeViewDebug] = None + """Debug information.""" + + engine: str + """The query engine being used.""" + + metrics: List[DescribeViewMetric] + """Available metrics.""" + + model: str + """The underlying model class.""" + + resource: str + """The API resource name.""" + + sample: Optional[List[Dict[str, object]]] = None + """Sample data rows.""" + + sortable_columns: List[str] + """Columns that can be used for sorting.""" + + typename: Literal["DescribeView"] + """The typename of this object""" + + view: str + """The view name being described.""" + + +StatDescribeResponse: TypeAlias = Annotated[ + Union[Optional[DescribeRoot], Optional[DescribeNode], Optional[DescribeMetric], Optional[DescribeView]], + PropertyInfo(discriminator="typename"), +] diff --git a/src/whop_sdk/types/stat_query_metric_params.py b/src/whop_sdk/types/stat_query_metric_params.py new file mode 100644 index 0000000..0e77b1a --- /dev/null +++ b/src/whop_sdk/types/stat_query_metric_params.py @@ -0,0 +1,47 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Union, Optional +from datetime import datetime +from typing_extensions import Required, Annotated, TypedDict + +from .._types import SequenceNotStr +from .._utils import PropertyInfo + +__all__ = ["StatQueryMetricParams"] + + +class StatQueryMetricParams(TypedDict, total=False): + resource: Required[str] + """ + Metric resource using : as separator (e.g., 'receipts:gross_revenue', + 'members:new_users'). + """ + + breakdowns: Optional[SequenceNotStr[str]] + """Columns to break down the metric by.""" + + company_id: Optional[str] + """Scope query to a specific company.""" + + filters: Optional[Dict[str, object]] + """Key-value pairs to filter the data.""" + + from_: Annotated[Union[str, datetime, None], PropertyInfo(alias="from", format="iso8601")] + """Start of time range (unix timestamp).""" + + granularity: Optional[str] + """Time granularity (daily, weekly, monthly).""" + + time_zone: Optional[str] + """IANA timezone for period bucketing (e.g. + + 'America/New_York'). Defaults to UTC. Only applies to ClickHouse metrics. + """ + + to: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """End of time range (unix timestamp).""" + + user_id: Optional[str] + """Scope query to a specific user.""" diff --git a/src/whop_sdk/types/stat_query_metric_response.py b/src/whop_sdk/types/stat_query_metric_response.py new file mode 100644 index 0000000..c987920 --- /dev/null +++ b/src/whop_sdk/types/stat_query_metric_response.py @@ -0,0 +1,53 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["StatQueryMetricResponse", "Debug", "Pagination"] + + +class Debug(BaseModel): + """Debug information including engine and SQL.""" + + engine: Optional[str] = None + """The query engine used.""" + + request_id: Optional[str] = None + """Unique request identifier for debugging.""" + + sql: Optional[str] = None + """The generated SQL query (with IDs sanitized).""" + + +class Pagination(BaseModel): + """Pagination information.""" + + next_cursor: Optional[str] = None + """Cursor for the next page of results.""" + + +class StatQueryMetricResponse(BaseModel): + """Result from a stats query (raw, metric, or SQL).""" + + columns: Optional[List[str]] = None + """Column names in the order they appear in each data row.""" + + data: Optional[List[Dict[str, object]]] = None + """ + Array of data rows, where each row is an array of values matching the columns + order. + """ + + debug: Optional[Debug] = None + """Debug information including engine and SQL.""" + + node: Optional[str] = None + """The node path that was queried.""" + + pagination: Optional[Pagination] = None + """Pagination information.""" + + typename: Literal["Result"] + """The typename of this object""" diff --git a/src/whop_sdk/types/stat_query_raw_params.py b/src/whop_sdk/types/stat_query_raw_params.py new file mode 100644 index 0000000..f9296c4 --- /dev/null +++ b/src/whop_sdk/types/stat_query_raw_params.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from datetime import datetime +from typing_extensions import Required, Annotated, TypedDict + +from .._utils import PropertyInfo +from .shared.direction import Direction + +__all__ = ["StatQueryRawParams"] + + +class StatQueryRawParams(TypedDict, total=False): + resource: Required[str] + """Resource path using : as separator (e.g., 'members', 'payments:membership').""" + + company_id: Optional[str] + """Scope query to a specific company.""" + + cursor: Optional[str] + """Pagination cursor for next page.""" + + from_: Annotated[Union[str, datetime, None], PropertyInfo(alias="from", format="iso8601")] + """Start of time range (unix timestamp).""" + + limit: Optional[int] + """Number of records to return (max 10000).""" + + sort: Optional[str] + """Column to sort by.""" + + sort_direction: Optional[Direction] + """The direction of the sort.""" + + to: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """End of time range (unix timestamp).""" + + user_id: Optional[str] + """Scope query to a specific user.""" diff --git a/src/whop_sdk/types/stat_query_raw_response.py b/src/whop_sdk/types/stat_query_raw_response.py new file mode 100644 index 0000000..1987953 --- /dev/null +++ b/src/whop_sdk/types/stat_query_raw_response.py @@ -0,0 +1,49 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional + +from .._models import BaseModel + +__all__ = ["StatQueryRawResponse", "Debug", "Pagination"] + + +class Debug(BaseModel): + """Debug information including engine and SQL.""" + + engine: Optional[str] = None + """The query engine used.""" + + request_id: Optional[str] = None + """Unique request identifier for debugging.""" + + sql: Optional[str] = None + """The generated SQL query (with IDs sanitized).""" + + +class Pagination(BaseModel): + """Pagination information.""" + + next_cursor: Optional[str] = None + """Cursor for the next page of results.""" + + +class StatQueryRawResponse(BaseModel): + """Result from a stats query (raw, metric, or SQL).""" + + columns: Optional[List[str]] = None + """Column names in the order they appear in each data row.""" + + data: Optional[List[Dict[str, object]]] = None + """ + Array of data rows, where each row is an array of values matching the columns + order. + """ + + debug: Optional[Debug] = None + """Debug information including engine and SQL.""" + + node: Optional[str] = None + """The node path that was queried.""" + + pagination: Optional[Pagination] = None + """Pagination information.""" diff --git a/src/whop_sdk/types/stat_run_sql_params.py b/src/whop_sdk/types/stat_run_sql_params.py new file mode 100644 index 0000000..f06046d --- /dev/null +++ b/src/whop_sdk/types/stat_run_sql_params.py @@ -0,0 +1,44 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from datetime import datetime +from typing_extensions import Required, Annotated, TypedDict + +from .._utils import PropertyInfo +from .shared.direction import Direction + +__all__ = ["StatRunSqlParams"] + + +class StatRunSqlParams(TypedDict, total=False): + resource: Required[str] + """Resource path using : as separator (e.g., 'receipts', 'payments:membership').""" + + sql: Required[str] + """SQL query. Use SCOPED_DATA as the table name.""" + + company_id: Optional[str] + """Scope query to a specific company.""" + + cursor: Optional[str] + """Pagination cursor for next page.""" + + from_: Annotated[Union[str, datetime, None], PropertyInfo(alias="from", format="iso8601")] + """Start of time range (unix timestamp).""" + + limit: Optional[int] + """Number of records to return (max 10000).""" + + sort: Optional[str] + """Column to sort by.""" + + sort_direction: Optional[Direction] + """The direction of the sort.""" + + to: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """End of time range (unix timestamp).""" + + user_id: Optional[str] + """Scope query to a specific user.""" diff --git a/src/whop_sdk/types/stat_run_sql_response.py b/src/whop_sdk/types/stat_run_sql_response.py new file mode 100644 index 0000000..d0f4668 --- /dev/null +++ b/src/whop_sdk/types/stat_run_sql_response.py @@ -0,0 +1,53 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["StatRunSqlResponse", "Debug", "Pagination"] + + +class Debug(BaseModel): + """Debug information including engine and SQL.""" + + engine: Optional[str] = None + """The query engine used.""" + + request_id: Optional[str] = None + """Unique request identifier for debugging.""" + + sql: Optional[str] = None + """The generated SQL query (with IDs sanitized).""" + + +class Pagination(BaseModel): + """Pagination information.""" + + next_cursor: Optional[str] = None + """Cursor for the next page of results.""" + + +class StatRunSqlResponse(BaseModel): + """Result from a stats query (raw, metric, or SQL).""" + + columns: Optional[List[str]] = None + """Column names in the order they appear in each data row.""" + + data: Optional[List[Dict[str, object]]] = None + """ + Array of data rows, where each row is an array of values matching the columns + order. + """ + + debug: Optional[Debug] = None + """Debug information including engine and SQL.""" + + node: Optional[str] = None + """The node path that was queried.""" + + pagination: Optional[Pagination] = None + """Pagination information.""" + + typename: Literal["Result"] + """The typename of this object""" diff --git a/tests/api_resources/test_ad_campaigns.py b/tests/api_resources/test_ad_campaigns.py new file mode 100644 index 0000000..d609a91 --- /dev/null +++ b/tests/api_resources/test_ad_campaigns.py @@ -0,0 +1,661 @@ +# 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 whop_sdk import Whop, AsyncWhop +from tests.utils import assert_matches_type +from whop_sdk.types import ( + AdCampaignListResponse, + AdCampaignPauseResponse, + AdCampaignCreateResponse, + AdCampaignUpdateResponse, + AdCampaignUnpauseResponse, + AdCampaignRetrieveResponse, +) +from whop_sdk._utils import parse_datetime +from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestAdCampaigns: + 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: Whop) -> None: + ad_campaign = client.ad_campaigns.create( + company_id="biz_xxxxxxxxxxxxxx", + config={}, + platform="meta", + title="title", + ) + assert_matches_type(AdCampaignCreateResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Whop) -> None: + ad_campaign = client.ad_campaigns.create( + company_id="biz_xxxxxxxxxxxxxx", + config={ + "bid_amount": 42, + "bid_strategy": "lowest_cost", + "budget_optimization": True, + "end_time": "end_time", + "lifetime_budget": 42, + "objective": "awareness", + "special_categories": ["string"], + "start_time": "start_time", + "status": "active", + }, + platform="meta", + title="title", + ad_creative_set_ids=["string"], + budget=6.9, + budget_type="daily", + daily_budget=6.9, + product_id="prod_xxxxxxxxxxxxx", + target_country_codes=["string"], + ) + assert_matches_type(AdCampaignCreateResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_create(self, client: Whop) -> None: + response = client.ad_campaigns.with_raw_response.create( + company_id="biz_xxxxxxxxxxxxxx", + config={}, + platform="meta", + title="title", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_campaign = response.parse() + assert_matches_type(AdCampaignCreateResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Whop) -> None: + with client.ad_campaigns.with_streaming_response.create( + company_id="biz_xxxxxxxxxxxxxx", + config={}, + platform="meta", + title="title", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_campaign = response.parse() + assert_matches_type(AdCampaignCreateResponse, ad_campaign, 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: Whop) -> None: + ad_campaign = client.ad_campaigns.retrieve( + "adcamp_xxxxxxxxxxx", + ) + assert_matches_type(AdCampaignRetrieveResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: Whop) -> None: + response = client.ad_campaigns.with_raw_response.retrieve( + "adcamp_xxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_campaign = response.parse() + assert_matches_type(AdCampaignRetrieveResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: Whop) -> None: + with client.ad_campaigns.with_streaming_response.retrieve( + "adcamp_xxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_campaign = response.parse() + assert_matches_type(AdCampaignRetrieveResponse, ad_campaign, 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: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.ad_campaigns.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_update(self, client: Whop) -> None: + ad_campaign = client.ad_campaigns.update( + id="adcamp_xxxxxxxxxxx", + ) + assert_matches_type(AdCampaignUpdateResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_update_with_all_params(self, client: Whop) -> None: + ad_campaign = client.ad_campaigns.update( + id="adcamp_xxxxxxxxxxx", + ad_creative_set_ids=["string"], + budget=6.9, + budget_type="daily", + config={ + "bid_amount": 42, + "bid_strategy": "lowest_cost", + "budget_optimization": True, + "end_time": "end_time", + "lifetime_budget": 42, + "objective": "awareness", + "special_categories": ["string"], + "start_time": "start_time", + "status": "active", + }, + daily_budget=6.9, + product_id="prod_xxxxxxxxxxxxx", + target_country_codes=["string"], + title="title", + ) + assert_matches_type(AdCampaignUpdateResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_update(self, client: Whop) -> None: + response = client.ad_campaigns.with_raw_response.update( + id="adcamp_xxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_campaign = response.parse() + assert_matches_type(AdCampaignUpdateResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_update(self, client: Whop) -> None: + with client.ad_campaigns.with_streaming_response.update( + id="adcamp_xxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_campaign = response.parse() + assert_matches_type(AdCampaignUpdateResponse, ad_campaign, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_update(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.ad_campaigns.with_raw_response.update( + id="", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: Whop) -> None: + ad_campaign = client.ad_campaigns.list( + company_id="biz_xxxxxxxxxxxxxx", + ) + assert_matches_type(SyncCursorPage[AdCampaignListResponse], ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: Whop) -> None: + ad_campaign = client.ad_campaigns.list( + company_id="biz_xxxxxxxxxxxxxx", + after="after", + before="before", + created_after=parse_datetime("2023-12-01T05:00:00.401Z"), + created_before=parse_datetime("2023-12-01T05:00:00.401Z"), + first=42, + last=42, + query="query", + status="active", + ) + assert_matches_type(SyncCursorPage[AdCampaignListResponse], ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: Whop) -> None: + response = client.ad_campaigns.with_raw_response.list( + company_id="biz_xxxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_campaign = response.parse() + assert_matches_type(SyncCursorPage[AdCampaignListResponse], ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Whop) -> None: + with client.ad_campaigns.with_streaming_response.list( + company_id="biz_xxxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_campaign = response.parse() + assert_matches_type(SyncCursorPage[AdCampaignListResponse], ad_campaign, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_pause(self, client: Whop) -> None: + ad_campaign = client.ad_campaigns.pause( + "adcamp_xxxxxxxxxxx", + ) + assert_matches_type(AdCampaignPauseResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_pause(self, client: Whop) -> None: + response = client.ad_campaigns.with_raw_response.pause( + "adcamp_xxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_campaign = response.parse() + assert_matches_type(AdCampaignPauseResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_pause(self, client: Whop) -> None: + with client.ad_campaigns.with_streaming_response.pause( + "adcamp_xxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_campaign = response.parse() + assert_matches_type(AdCampaignPauseResponse, ad_campaign, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_pause(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.ad_campaigns.with_raw_response.pause( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_unpause(self, client: Whop) -> None: + ad_campaign = client.ad_campaigns.unpause( + "adcamp_xxxxxxxxxxx", + ) + assert_matches_type(AdCampaignUnpauseResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_unpause(self, client: Whop) -> None: + response = client.ad_campaigns.with_raw_response.unpause( + "adcamp_xxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_campaign = response.parse() + assert_matches_type(AdCampaignUnpauseResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_unpause(self, client: Whop) -> None: + with client.ad_campaigns.with_streaming_response.unpause( + "adcamp_xxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_campaign = response.parse() + assert_matches_type(AdCampaignUnpauseResponse, ad_campaign, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_unpause(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.ad_campaigns.with_raw_response.unpause( + "", + ) + + +class TestAsyncAdCampaigns: + 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: AsyncWhop) -> None: + ad_campaign = await async_client.ad_campaigns.create( + company_id="biz_xxxxxxxxxxxxxx", + config={}, + platform="meta", + title="title", + ) + assert_matches_type(AdCampaignCreateResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None: + ad_campaign = await async_client.ad_campaigns.create( + company_id="biz_xxxxxxxxxxxxxx", + config={ + "bid_amount": 42, + "bid_strategy": "lowest_cost", + "budget_optimization": True, + "end_time": "end_time", + "lifetime_budget": 42, + "objective": "awareness", + "special_categories": ["string"], + "start_time": "start_time", + "status": "active", + }, + platform="meta", + title="title", + ad_creative_set_ids=["string"], + budget=6.9, + budget_type="daily", + daily_budget=6.9, + product_id="prod_xxxxxxxxxxxxx", + target_country_codes=["string"], + ) + assert_matches_type(AdCampaignCreateResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_create(self, async_client: AsyncWhop) -> None: + response = await async_client.ad_campaigns.with_raw_response.create( + company_id="biz_xxxxxxxxxxxxxx", + config={}, + platform="meta", + title="title", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_campaign = await response.parse() + assert_matches_type(AdCampaignCreateResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: + async with async_client.ad_campaigns.with_streaming_response.create( + company_id="biz_xxxxxxxxxxxxxx", + config={}, + platform="meta", + title="title", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_campaign = await response.parse() + assert_matches_type(AdCampaignCreateResponse, ad_campaign, 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: AsyncWhop) -> None: + ad_campaign = await async_client.ad_campaigns.retrieve( + "adcamp_xxxxxxxxxxx", + ) + assert_matches_type(AdCampaignRetrieveResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: + response = await async_client.ad_campaigns.with_raw_response.retrieve( + "adcamp_xxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_campaign = await response.parse() + assert_matches_type(AdCampaignRetrieveResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: + async with async_client.ad_campaigns.with_streaming_response.retrieve( + "adcamp_xxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_campaign = await response.parse() + assert_matches_type(AdCampaignRetrieveResponse, ad_campaign, 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: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.ad_campaigns.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_update(self, async_client: AsyncWhop) -> None: + ad_campaign = await async_client.ad_campaigns.update( + id="adcamp_xxxxxxxxxxx", + ) + assert_matches_type(AdCampaignUpdateResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> None: + ad_campaign = await async_client.ad_campaigns.update( + id="adcamp_xxxxxxxxxxx", + ad_creative_set_ids=["string"], + budget=6.9, + budget_type="daily", + config={ + "bid_amount": 42, + "bid_strategy": "lowest_cost", + "budget_optimization": True, + "end_time": "end_time", + "lifetime_budget": 42, + "objective": "awareness", + "special_categories": ["string"], + "start_time": "start_time", + "status": "active", + }, + daily_budget=6.9, + product_id="prod_xxxxxxxxxxxxx", + target_country_codes=["string"], + title="title", + ) + assert_matches_type(AdCampaignUpdateResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_update(self, async_client: AsyncWhop) -> None: + response = await async_client.ad_campaigns.with_raw_response.update( + id="adcamp_xxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_campaign = await response.parse() + assert_matches_type(AdCampaignUpdateResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_update(self, async_client: AsyncWhop) -> None: + async with async_client.ad_campaigns.with_streaming_response.update( + id="adcamp_xxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_campaign = await response.parse() + assert_matches_type(AdCampaignUpdateResponse, ad_campaign, 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_update(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.ad_campaigns.with_raw_response.update( + id="", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncWhop) -> None: + ad_campaign = await async_client.ad_campaigns.list( + company_id="biz_xxxxxxxxxxxxxx", + ) + assert_matches_type(AsyncCursorPage[AdCampaignListResponse], ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None: + ad_campaign = await async_client.ad_campaigns.list( + company_id="biz_xxxxxxxxxxxxxx", + after="after", + before="before", + created_after=parse_datetime("2023-12-01T05:00:00.401Z"), + created_before=parse_datetime("2023-12-01T05:00:00.401Z"), + first=42, + last=42, + query="query", + status="active", + ) + assert_matches_type(AsyncCursorPage[AdCampaignListResponse], ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncWhop) -> None: + response = await async_client.ad_campaigns.with_raw_response.list( + company_id="biz_xxxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_campaign = await response.parse() + assert_matches_type(AsyncCursorPage[AdCampaignListResponse], ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: + async with async_client.ad_campaigns.with_streaming_response.list( + company_id="biz_xxxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_campaign = await response.parse() + assert_matches_type(AsyncCursorPage[AdCampaignListResponse], ad_campaign, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_pause(self, async_client: AsyncWhop) -> None: + ad_campaign = await async_client.ad_campaigns.pause( + "adcamp_xxxxxxxxxxx", + ) + assert_matches_type(AdCampaignPauseResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_pause(self, async_client: AsyncWhop) -> None: + response = await async_client.ad_campaigns.with_raw_response.pause( + "adcamp_xxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_campaign = await response.parse() + assert_matches_type(AdCampaignPauseResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_pause(self, async_client: AsyncWhop) -> None: + async with async_client.ad_campaigns.with_streaming_response.pause( + "adcamp_xxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_campaign = await response.parse() + assert_matches_type(AdCampaignPauseResponse, ad_campaign, 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_pause(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.ad_campaigns.with_raw_response.pause( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_unpause(self, async_client: AsyncWhop) -> None: + ad_campaign = await async_client.ad_campaigns.unpause( + "adcamp_xxxxxxxxxxx", + ) + assert_matches_type(AdCampaignUnpauseResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_unpause(self, async_client: AsyncWhop) -> None: + response = await async_client.ad_campaigns.with_raw_response.unpause( + "adcamp_xxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_campaign = await response.parse() + assert_matches_type(AdCampaignUnpauseResponse, ad_campaign, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_unpause(self, async_client: AsyncWhop) -> None: + async with async_client.ad_campaigns.with_streaming_response.unpause( + "adcamp_xxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_campaign = await response.parse() + assert_matches_type(AdCampaignUnpauseResponse, ad_campaign, 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_unpause(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.ad_campaigns.with_raw_response.unpause( + "", + ) diff --git a/tests/api_resources/test_ad_groups.py b/tests/api_resources/test_ad_groups.py new file mode 100644 index 0000000..dbe0dcc --- /dev/null +++ b/tests/api_resources/test_ad_groups.py @@ -0,0 +1,1838 @@ +# 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 whop_sdk import Whop, AsyncWhop +from tests.utils import assert_matches_type +from whop_sdk.types import ( + AdGroupListResponse, + AdGroupCreateResponse, + AdGroupDeleteResponse, + AdGroupUpdateResponse, + AdGroupRetrieveResponse, +) +from whop_sdk._utils import parse_datetime +from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestAdGroups: + 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: Whop) -> None: + ad_group = client.ad_groups.create( + campaign_id="campaign_id", + ) + assert_matches_type(AdGroupCreateResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Whop) -> None: + ad_group = client.ad_groups.create( + campaign_id="campaign_id", + budget=6.9, + budget_type="daily", + config={ + "bid_amount": 42, + "bid_strategy": "lowest_cost", + "billing_event": "impressions", + "end_time": "end_time", + "frequency_cap": 42, + "frequency_cap_interval_days": 42, + "optimization_goal": "conversions", + "pacing": "standard", + "start_time": "start_time", + "targeting": { + "age_max": 42, + "age_min": 42, + "countries": ["string"], + "device_platforms": ["mobile"], + "exclude_audience_ids": ["string"], + "genders": ["male"], + "include_audience_ids": ["string"], + "interest_ids": ["string"], + "languages": ["string"], + "placement_type": "automatic", + }, + }, + daily_budget=6.9, + name="name", + platform_config={ + "meta": { + "android_devices": ["string"], + "attribution_setting": "attribution_setting", + "attribution_spec": [ + { + "event_type": "event_type", + "window_days": 42, + } + ], + "audience_network_positions": ["string"], + "audience_type": "audience_type", + "bid_amount": 42, + "bid_strategy": "LOWEST_COST_WITHOUT_CAP", + "billing_event": "APP_INSTALLS", + "brand_safety_content_filter_levels": ["string"], + "budget_remaining": "budget_remaining", + "cost_per_result_goal": 6.9, + "created_time": "created_time", + "daily_budget": 42, + "daily_min_spend_target": "daily_min_spend_target", + "daily_spend_cap": "daily_spend_cap", + "destination_type": "UNDEFINED", + "dsa_beneficiary": "dsa_beneficiary", + "dsa_payor": "dsa_payor", + "end_time": "end_time", + "excluded_geo_locations": { + "cities": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "countries": ["string"], + "location_types": ["string"], + "regions": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "zips": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + }, + "facebook_positions": ["string"], + "frequency_control_count": 42, + "frequency_control_days": 42, + "frequency_control_type": "frequency_control_type", + "geo_cities": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "geo_locations": { + "cities": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "countries": ["string"], + "location_types": ["string"], + "regions": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "zips": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + }, + "geo_regions": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "geo_zips": ["string"], + "instagram_actor_id": "instagram_actor_id", + "instagram_positions": ["string"], + "ios_devices": ["string"], + "is_dynamic_creative": True, + "lead_conversion_location": "website", + "lead_form_config": { + "name": "name", + "privacy_policy_url": "privacy_policy_url", + "questions": [ + { + "type": "type", + "conditional_questions_group_id": "conditional_questions_group_id", + "dependent_conditional_questions": [ + { + "type": "type", + "inline_context": "inline_context", + "key": "key", + "label": "label", + "options": [ + { + "key": "key", + "value": "value", + "logic": { + "type": "type", + "target_end_page_index": 42, + "target_question_index": 42, + }, + } + ], + } + ], + "inline_context": "inline_context", + "key": "key", + "label": "label", + "options": [ + { + "key": "key", + "value": "value", + "logic": { + "type": "type", + "target_end_page_index": 42, + "target_question_index": 42, + }, + } + ], + "question_format": "question_format", + } + ], + "background_image_source": "background_image_source", + "background_image_url": "background_image_url", + "conditional_logic_enabled": True, + "context_card_button_text": "context_card_button_text", + "context_card_content": ["string"], + "context_card_style": "context_card_style", + "context_card_title": "context_card_title", + "custom_disclaimer_body": "custom_disclaimer_body", + "custom_disclaimer_checkboxes": [ + { + "key": "key", + "text": "text", + "is_checked_by_default": True, + "is_required": True, + } + ], + "custom_disclaimer_title": "custom_disclaimer_title", + "form_type": "form_type", + "messenger_enabled": True, + "phone_verification_enabled": True, + "privacy_policy_link_text": "privacy_policy_link_text", + "question_page_custom_headline": "question_page_custom_headline", + "rich_creative_headline": "rich_creative_headline", + "rich_creative_overview": "rich_creative_overview", + "rich_creative_url": "rich_creative_url", + "thank_you_pages": [ + { + "body": "body", + "business_phone": "business_phone", + "button_text": "button_text", + "button_type": "button_type", + "conditional_question_group_id": "conditional_question_group_id", + "enable_messenger": True, + "gated_file_url": "gated_file_url", + "link": "link", + "name": "name", + "title": "title", + } + ], + }, + "lead_gen_form_id": "lead_gen_form_id", + "lifetime_budget": 42, + "lifetime_min_spend_target": "lifetime_min_spend_target", + "lifetime_spend_cap": "lifetime_spend_cap", + "location_types": ["string"], + "messenger_positions": ["string"], + "optimization_goal": "NONE", + "page_id": "page_id", + "pixel_id": "pixel_id", + "promoted_object": { + "custom_conversion_id": "custom_conversion_id", + "custom_event_str": "custom_event_str", + "custom_event_type": "custom_event_type", + "page_id": "page_id", + "pixel_id": "pixel_id", + "whatsapp_phone_number": "whatsapp_phone_number", + }, + "publisher_platforms": ["string"], + "source_adset_id": "source_adset_id", + "start_time": "start_time", + "status": "ACTIVE", + "targeting_automation": {"advantage_audience": 42}, + "threads_positions": ["string"], + "updated_time": "updated_time", + "user_device": ["string"], + "user_os": ["string"], + "whatsapp_phone_number": "whatsapp_phone_number", + "whatsapp_positions": ["string"], + }, + "tiktok": { + "actions": [ + { + "action_category_ids": ["string"], + "action_period": 42, + "action_scene": "VIDEO_RELATED", + "video_user_actions": ["WATCHED_TO_END"], + } + ], + "age_groups": ["AGE_13_17"], + "app_id": "app_xxxxxxxxxxxxxx", + "attribution_event_count": "UNSET", + "audience_ids": ["string"], + "audience_rule": {"foo": "bar"}, + "audience_type": "NORMAL", + "bid_price": 6.9, + "bid_type": "BID_TYPE_NO_BID", + "billing_event": "CPC", + "brand_safety_type": "NO_BRAND_SAFETY", + "budget_mode": "BUDGET_MODE_DAY", + "carrier_ids": ["string"], + "category_exclusion_ids": ["string"], + "click_attribution_window": "OFF", + "comment_disabled": True, + "contextual_tag_ids": ["string"], + "conversion_bid_price": 6.9, + "creative_material_mode": "creative_material_mode", + "dayparting": "dayparting", + "deep_funnel_event_source": "deep_funnel_event_source", + "deep_funnel_event_source_id": "deep_funnel_event_source_id", + "deep_funnel_optimization_status": "ON", + "device_model_ids": ["string"], + "device_price_ranges": ["string"], + "engaged_view_attribution_window": "OFF", + "excluded_audience_ids": ["string"], + "excluded_location_ids": ["string"], + "frequency": 42, + "frequency_schedule": 42, + "gender": "GENDER_UNLIMITED", + "identity_authorized_bc_id": "identity_authorized_bc_id", + "identity_id": "identity_id", + "identity_type": "identity_type", + "instant_form_config": { + "privacy_policy_url": "privacy_policy_url", + "questions": [ + { + "field_type": "field_type", + "label": "label", + } + ], + "button_text": "button_text", + "greeting": "greeting", + "name": "name", + }, + "instant_form_id": "instant_form_id", + "interest_category_ids": ["string"], + "interest_keyword_ids": ["string"], + "inventory_filter_enabled": True, + "ios14_targeting": "UNSET", + "isp_ids": ["string"], + "languages": ["string"], + "location_ids": ["string"], + "min_android_version": "min_android_version", + "min_ios_version": "min_ios_version", + "network_types": ["string"], + "operating_systems": ["ANDROID"], + "operation_status": "ENABLE", + "optimization_event": "optimization_event", + "optimization_goal": "CLICK", + "pacing": "PACING_MODE_SMOOTH", + "pangle_audience_package_exclude_ids": ["string"], + "pangle_audience_package_include_ids": ["string"], + "pangle_block_app_ids": ["string"], + "pixel_id": "pixel_id", + "placement_type": "PLACEMENT_TYPE_AUTOMATIC", + "placements": ["string"], + "product_set_id": "product_set_id", + "product_source": "CATALOG", + "promotion_type": "promotion_type", + "schedule_end_time": "schedule_end_time", + "schedule_start_time": "schedule_start_time", + "schedule_type": "SCHEDULE_START_END", + "secondary_optimization_event": "secondary_optimization_event", + "shopping_ads_retargeting_actions_days": 42, + "shopping_ads_retargeting_type": "OFF", + "spending_power": "ALL", + "tiktok_subplacements": ["string"], + "vertical_sensitivity_id": "vertical_sensitivity_id", + "video_download_disabled": True, + "video_user_actions": ["string"], + "view_attribution_window": "OFF", + }, + }, + status="active", + ) + assert_matches_type(AdGroupCreateResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_create(self, client: Whop) -> None: + response = client.ad_groups.with_raw_response.create( + campaign_id="campaign_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_group = response.parse() + assert_matches_type(AdGroupCreateResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Whop) -> None: + with client.ad_groups.with_streaming_response.create( + campaign_id="campaign_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_group = response.parse() + assert_matches_type(AdGroupCreateResponse, ad_group, 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: Whop) -> None: + ad_group = client.ad_groups.retrieve( + "adgrp_xxxxxxxxxxxx", + ) + assert_matches_type(AdGroupRetrieveResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: Whop) -> None: + response = client.ad_groups.with_raw_response.retrieve( + "adgrp_xxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_group = response.parse() + assert_matches_type(AdGroupRetrieveResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: Whop) -> None: + with client.ad_groups.with_streaming_response.retrieve( + "adgrp_xxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_group = response.parse() + assert_matches_type(AdGroupRetrieveResponse, ad_group, 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: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.ad_groups.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_update(self, client: Whop) -> None: + ad_group = client.ad_groups.update( + id="adgrp_xxxxxxxxxxxx", + ) + assert_matches_type(AdGroupUpdateResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_update_with_all_params(self, client: Whop) -> None: + ad_group = client.ad_groups.update( + id="adgrp_xxxxxxxxxxxx", + budget=6.9, + budget_type="daily", + config={ + "bid_amount": 42, + "bid_strategy": "lowest_cost", + "billing_event": "impressions", + "end_time": "end_time", + "frequency_cap": 42, + "frequency_cap_interval_days": 42, + "optimization_goal": "conversions", + "pacing": "standard", + "start_time": "start_time", + "targeting": { + "age_max": 42, + "age_min": 42, + "countries": ["string"], + "device_platforms": ["mobile"], + "exclude_audience_ids": ["string"], + "genders": ["male"], + "include_audience_ids": ["string"], + "interest_ids": ["string"], + "languages": ["string"], + "placement_type": "automatic", + }, + }, + daily_budget=6.9, + name="name", + platform_config={ + "meta": { + "android_devices": ["string"], + "attribution_setting": "attribution_setting", + "attribution_spec": [ + { + "event_type": "event_type", + "window_days": 42, + } + ], + "audience_network_positions": ["string"], + "audience_type": "audience_type", + "bid_amount": 42, + "bid_strategy": "LOWEST_COST_WITHOUT_CAP", + "billing_event": "APP_INSTALLS", + "brand_safety_content_filter_levels": ["string"], + "budget_remaining": "budget_remaining", + "cost_per_result_goal": 6.9, + "created_time": "created_time", + "daily_budget": 42, + "daily_min_spend_target": "daily_min_spend_target", + "daily_spend_cap": "daily_spend_cap", + "destination_type": "UNDEFINED", + "dsa_beneficiary": "dsa_beneficiary", + "dsa_payor": "dsa_payor", + "end_time": "end_time", + "excluded_geo_locations": { + "cities": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "countries": ["string"], + "location_types": ["string"], + "regions": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "zips": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + }, + "facebook_positions": ["string"], + "frequency_control_count": 42, + "frequency_control_days": 42, + "frequency_control_type": "frequency_control_type", + "geo_cities": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "geo_locations": { + "cities": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "countries": ["string"], + "location_types": ["string"], + "regions": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "zips": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + }, + "geo_regions": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "geo_zips": ["string"], + "instagram_actor_id": "instagram_actor_id", + "instagram_positions": ["string"], + "ios_devices": ["string"], + "is_dynamic_creative": True, + "lead_conversion_location": "website", + "lead_form_config": { + "name": "name", + "privacy_policy_url": "privacy_policy_url", + "questions": [ + { + "type": "type", + "conditional_questions_group_id": "conditional_questions_group_id", + "dependent_conditional_questions": [ + { + "type": "type", + "inline_context": "inline_context", + "key": "key", + "label": "label", + "options": [ + { + "key": "key", + "value": "value", + "logic": { + "type": "type", + "target_end_page_index": 42, + "target_question_index": 42, + }, + } + ], + } + ], + "inline_context": "inline_context", + "key": "key", + "label": "label", + "options": [ + { + "key": "key", + "value": "value", + "logic": { + "type": "type", + "target_end_page_index": 42, + "target_question_index": 42, + }, + } + ], + "question_format": "question_format", + } + ], + "background_image_source": "background_image_source", + "background_image_url": "background_image_url", + "conditional_logic_enabled": True, + "context_card_button_text": "context_card_button_text", + "context_card_content": ["string"], + "context_card_style": "context_card_style", + "context_card_title": "context_card_title", + "custom_disclaimer_body": "custom_disclaimer_body", + "custom_disclaimer_checkboxes": [ + { + "key": "key", + "text": "text", + "is_checked_by_default": True, + "is_required": True, + } + ], + "custom_disclaimer_title": "custom_disclaimer_title", + "form_type": "form_type", + "messenger_enabled": True, + "phone_verification_enabled": True, + "privacy_policy_link_text": "privacy_policy_link_text", + "question_page_custom_headline": "question_page_custom_headline", + "rich_creative_headline": "rich_creative_headline", + "rich_creative_overview": "rich_creative_overview", + "rich_creative_url": "rich_creative_url", + "thank_you_pages": [ + { + "body": "body", + "business_phone": "business_phone", + "button_text": "button_text", + "button_type": "button_type", + "conditional_question_group_id": "conditional_question_group_id", + "enable_messenger": True, + "gated_file_url": "gated_file_url", + "link": "link", + "name": "name", + "title": "title", + } + ], + }, + "lead_gen_form_id": "lead_gen_form_id", + "lifetime_budget": 42, + "lifetime_min_spend_target": "lifetime_min_spend_target", + "lifetime_spend_cap": "lifetime_spend_cap", + "location_types": ["string"], + "messenger_positions": ["string"], + "optimization_goal": "NONE", + "page_id": "page_id", + "pixel_id": "pixel_id", + "promoted_object": { + "custom_conversion_id": "custom_conversion_id", + "custom_event_str": "custom_event_str", + "custom_event_type": "custom_event_type", + "page_id": "page_id", + "pixel_id": "pixel_id", + "whatsapp_phone_number": "whatsapp_phone_number", + }, + "publisher_platforms": ["string"], + "source_adset_id": "source_adset_id", + "start_time": "start_time", + "status": "ACTIVE", + "targeting_automation": {"advantage_audience": 42}, + "threads_positions": ["string"], + "updated_time": "updated_time", + "user_device": ["string"], + "user_os": ["string"], + "whatsapp_phone_number": "whatsapp_phone_number", + "whatsapp_positions": ["string"], + }, + "tiktok": { + "actions": [ + { + "action_category_ids": ["string"], + "action_period": 42, + "action_scene": "VIDEO_RELATED", + "video_user_actions": ["WATCHED_TO_END"], + } + ], + "age_groups": ["AGE_13_17"], + "app_id": "app_xxxxxxxxxxxxxx", + "attribution_event_count": "UNSET", + "audience_ids": ["string"], + "audience_rule": {"foo": "bar"}, + "audience_type": "NORMAL", + "bid_price": 6.9, + "bid_type": "BID_TYPE_NO_BID", + "billing_event": "CPC", + "brand_safety_type": "NO_BRAND_SAFETY", + "budget_mode": "BUDGET_MODE_DAY", + "carrier_ids": ["string"], + "category_exclusion_ids": ["string"], + "click_attribution_window": "OFF", + "comment_disabled": True, + "contextual_tag_ids": ["string"], + "conversion_bid_price": 6.9, + "creative_material_mode": "creative_material_mode", + "dayparting": "dayparting", + "deep_funnel_event_source": "deep_funnel_event_source", + "deep_funnel_event_source_id": "deep_funnel_event_source_id", + "deep_funnel_optimization_status": "ON", + "device_model_ids": ["string"], + "device_price_ranges": ["string"], + "engaged_view_attribution_window": "OFF", + "excluded_audience_ids": ["string"], + "excluded_location_ids": ["string"], + "frequency": 42, + "frequency_schedule": 42, + "gender": "GENDER_UNLIMITED", + "identity_authorized_bc_id": "identity_authorized_bc_id", + "identity_id": "identity_id", + "identity_type": "identity_type", + "instant_form_config": { + "privacy_policy_url": "privacy_policy_url", + "questions": [ + { + "field_type": "field_type", + "label": "label", + } + ], + "button_text": "button_text", + "greeting": "greeting", + "name": "name", + }, + "instant_form_id": "instant_form_id", + "interest_category_ids": ["string"], + "interest_keyword_ids": ["string"], + "inventory_filter_enabled": True, + "ios14_targeting": "UNSET", + "isp_ids": ["string"], + "languages": ["string"], + "location_ids": ["string"], + "min_android_version": "min_android_version", + "min_ios_version": "min_ios_version", + "network_types": ["string"], + "operating_systems": ["ANDROID"], + "operation_status": "ENABLE", + "optimization_event": "optimization_event", + "optimization_goal": "CLICK", + "pacing": "PACING_MODE_SMOOTH", + "pangle_audience_package_exclude_ids": ["string"], + "pangle_audience_package_include_ids": ["string"], + "pangle_block_app_ids": ["string"], + "pixel_id": "pixel_id", + "placement_type": "PLACEMENT_TYPE_AUTOMATIC", + "placements": ["string"], + "product_set_id": "product_set_id", + "product_source": "CATALOG", + "promotion_type": "promotion_type", + "schedule_end_time": "schedule_end_time", + "schedule_start_time": "schedule_start_time", + "schedule_type": "SCHEDULE_START_END", + "secondary_optimization_event": "secondary_optimization_event", + "shopping_ads_retargeting_actions_days": 42, + "shopping_ads_retargeting_type": "OFF", + "spending_power": "ALL", + "tiktok_subplacements": ["string"], + "vertical_sensitivity_id": "vertical_sensitivity_id", + "video_download_disabled": True, + "video_user_actions": ["string"], + "view_attribution_window": "OFF", + }, + }, + status="active", + ) + assert_matches_type(AdGroupUpdateResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_update(self, client: Whop) -> None: + response = client.ad_groups.with_raw_response.update( + id="adgrp_xxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_group = response.parse() + assert_matches_type(AdGroupUpdateResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_update(self, client: Whop) -> None: + with client.ad_groups.with_streaming_response.update( + id="adgrp_xxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_group = response.parse() + assert_matches_type(AdGroupUpdateResponse, ad_group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_update(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.ad_groups.with_raw_response.update( + id="", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: Whop) -> None: + ad_group = client.ad_groups.list() + assert_matches_type(SyncCursorPage[AdGroupListResponse], ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: Whop) -> None: + ad_group = client.ad_groups.list( + after="after", + before="before", + campaign_id="campaign_id", + company_id="biz_xxxxxxxxxxxxxx", + created_after=parse_datetime("2023-12-01T05:00:00.401Z"), + created_before=parse_datetime("2023-12-01T05:00:00.401Z"), + first=42, + last=42, + query="query", + status="active", + ) + assert_matches_type(SyncCursorPage[AdGroupListResponse], ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: Whop) -> None: + response = client.ad_groups.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_group = response.parse() + assert_matches_type(SyncCursorPage[AdGroupListResponse], ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Whop) -> None: + with client.ad_groups.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_group = response.parse() + assert_matches_type(SyncCursorPage[AdGroupListResponse], ad_group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_delete(self, client: Whop) -> None: + ad_group = client.ad_groups.delete( + "adgrp_xxxxxxxxxxxx", + ) + assert_matches_type(AdGroupDeleteResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_delete(self, client: Whop) -> None: + response = client.ad_groups.with_raw_response.delete( + "adgrp_xxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_group = response.parse() + assert_matches_type(AdGroupDeleteResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_delete(self, client: Whop) -> None: + with client.ad_groups.with_streaming_response.delete( + "adgrp_xxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_group = response.parse() + assert_matches_type(AdGroupDeleteResponse, ad_group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_delete(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.ad_groups.with_raw_response.delete( + "", + ) + + +class TestAsyncAdGroups: + 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: AsyncWhop) -> None: + ad_group = await async_client.ad_groups.create( + campaign_id="campaign_id", + ) + assert_matches_type(AdGroupCreateResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None: + ad_group = await async_client.ad_groups.create( + campaign_id="campaign_id", + budget=6.9, + budget_type="daily", + config={ + "bid_amount": 42, + "bid_strategy": "lowest_cost", + "billing_event": "impressions", + "end_time": "end_time", + "frequency_cap": 42, + "frequency_cap_interval_days": 42, + "optimization_goal": "conversions", + "pacing": "standard", + "start_time": "start_time", + "targeting": { + "age_max": 42, + "age_min": 42, + "countries": ["string"], + "device_platforms": ["mobile"], + "exclude_audience_ids": ["string"], + "genders": ["male"], + "include_audience_ids": ["string"], + "interest_ids": ["string"], + "languages": ["string"], + "placement_type": "automatic", + }, + }, + daily_budget=6.9, + name="name", + platform_config={ + "meta": { + "android_devices": ["string"], + "attribution_setting": "attribution_setting", + "attribution_spec": [ + { + "event_type": "event_type", + "window_days": 42, + } + ], + "audience_network_positions": ["string"], + "audience_type": "audience_type", + "bid_amount": 42, + "bid_strategy": "LOWEST_COST_WITHOUT_CAP", + "billing_event": "APP_INSTALLS", + "brand_safety_content_filter_levels": ["string"], + "budget_remaining": "budget_remaining", + "cost_per_result_goal": 6.9, + "created_time": "created_time", + "daily_budget": 42, + "daily_min_spend_target": "daily_min_spend_target", + "daily_spend_cap": "daily_spend_cap", + "destination_type": "UNDEFINED", + "dsa_beneficiary": "dsa_beneficiary", + "dsa_payor": "dsa_payor", + "end_time": "end_time", + "excluded_geo_locations": { + "cities": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "countries": ["string"], + "location_types": ["string"], + "regions": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "zips": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + }, + "facebook_positions": ["string"], + "frequency_control_count": 42, + "frequency_control_days": 42, + "frequency_control_type": "frequency_control_type", + "geo_cities": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "geo_locations": { + "cities": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "countries": ["string"], + "location_types": ["string"], + "regions": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "zips": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + }, + "geo_regions": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "geo_zips": ["string"], + "instagram_actor_id": "instagram_actor_id", + "instagram_positions": ["string"], + "ios_devices": ["string"], + "is_dynamic_creative": True, + "lead_conversion_location": "website", + "lead_form_config": { + "name": "name", + "privacy_policy_url": "privacy_policy_url", + "questions": [ + { + "type": "type", + "conditional_questions_group_id": "conditional_questions_group_id", + "dependent_conditional_questions": [ + { + "type": "type", + "inline_context": "inline_context", + "key": "key", + "label": "label", + "options": [ + { + "key": "key", + "value": "value", + "logic": { + "type": "type", + "target_end_page_index": 42, + "target_question_index": 42, + }, + } + ], + } + ], + "inline_context": "inline_context", + "key": "key", + "label": "label", + "options": [ + { + "key": "key", + "value": "value", + "logic": { + "type": "type", + "target_end_page_index": 42, + "target_question_index": 42, + }, + } + ], + "question_format": "question_format", + } + ], + "background_image_source": "background_image_source", + "background_image_url": "background_image_url", + "conditional_logic_enabled": True, + "context_card_button_text": "context_card_button_text", + "context_card_content": ["string"], + "context_card_style": "context_card_style", + "context_card_title": "context_card_title", + "custom_disclaimer_body": "custom_disclaimer_body", + "custom_disclaimer_checkboxes": [ + { + "key": "key", + "text": "text", + "is_checked_by_default": True, + "is_required": True, + } + ], + "custom_disclaimer_title": "custom_disclaimer_title", + "form_type": "form_type", + "messenger_enabled": True, + "phone_verification_enabled": True, + "privacy_policy_link_text": "privacy_policy_link_text", + "question_page_custom_headline": "question_page_custom_headline", + "rich_creative_headline": "rich_creative_headline", + "rich_creative_overview": "rich_creative_overview", + "rich_creative_url": "rich_creative_url", + "thank_you_pages": [ + { + "body": "body", + "business_phone": "business_phone", + "button_text": "button_text", + "button_type": "button_type", + "conditional_question_group_id": "conditional_question_group_id", + "enable_messenger": True, + "gated_file_url": "gated_file_url", + "link": "link", + "name": "name", + "title": "title", + } + ], + }, + "lead_gen_form_id": "lead_gen_form_id", + "lifetime_budget": 42, + "lifetime_min_spend_target": "lifetime_min_spend_target", + "lifetime_spend_cap": "lifetime_spend_cap", + "location_types": ["string"], + "messenger_positions": ["string"], + "optimization_goal": "NONE", + "page_id": "page_id", + "pixel_id": "pixel_id", + "promoted_object": { + "custom_conversion_id": "custom_conversion_id", + "custom_event_str": "custom_event_str", + "custom_event_type": "custom_event_type", + "page_id": "page_id", + "pixel_id": "pixel_id", + "whatsapp_phone_number": "whatsapp_phone_number", + }, + "publisher_platforms": ["string"], + "source_adset_id": "source_adset_id", + "start_time": "start_time", + "status": "ACTIVE", + "targeting_automation": {"advantage_audience": 42}, + "threads_positions": ["string"], + "updated_time": "updated_time", + "user_device": ["string"], + "user_os": ["string"], + "whatsapp_phone_number": "whatsapp_phone_number", + "whatsapp_positions": ["string"], + }, + "tiktok": { + "actions": [ + { + "action_category_ids": ["string"], + "action_period": 42, + "action_scene": "VIDEO_RELATED", + "video_user_actions": ["WATCHED_TO_END"], + } + ], + "age_groups": ["AGE_13_17"], + "app_id": "app_xxxxxxxxxxxxxx", + "attribution_event_count": "UNSET", + "audience_ids": ["string"], + "audience_rule": {"foo": "bar"}, + "audience_type": "NORMAL", + "bid_price": 6.9, + "bid_type": "BID_TYPE_NO_BID", + "billing_event": "CPC", + "brand_safety_type": "NO_BRAND_SAFETY", + "budget_mode": "BUDGET_MODE_DAY", + "carrier_ids": ["string"], + "category_exclusion_ids": ["string"], + "click_attribution_window": "OFF", + "comment_disabled": True, + "contextual_tag_ids": ["string"], + "conversion_bid_price": 6.9, + "creative_material_mode": "creative_material_mode", + "dayparting": "dayparting", + "deep_funnel_event_source": "deep_funnel_event_source", + "deep_funnel_event_source_id": "deep_funnel_event_source_id", + "deep_funnel_optimization_status": "ON", + "device_model_ids": ["string"], + "device_price_ranges": ["string"], + "engaged_view_attribution_window": "OFF", + "excluded_audience_ids": ["string"], + "excluded_location_ids": ["string"], + "frequency": 42, + "frequency_schedule": 42, + "gender": "GENDER_UNLIMITED", + "identity_authorized_bc_id": "identity_authorized_bc_id", + "identity_id": "identity_id", + "identity_type": "identity_type", + "instant_form_config": { + "privacy_policy_url": "privacy_policy_url", + "questions": [ + { + "field_type": "field_type", + "label": "label", + } + ], + "button_text": "button_text", + "greeting": "greeting", + "name": "name", + }, + "instant_form_id": "instant_form_id", + "interest_category_ids": ["string"], + "interest_keyword_ids": ["string"], + "inventory_filter_enabled": True, + "ios14_targeting": "UNSET", + "isp_ids": ["string"], + "languages": ["string"], + "location_ids": ["string"], + "min_android_version": "min_android_version", + "min_ios_version": "min_ios_version", + "network_types": ["string"], + "operating_systems": ["ANDROID"], + "operation_status": "ENABLE", + "optimization_event": "optimization_event", + "optimization_goal": "CLICK", + "pacing": "PACING_MODE_SMOOTH", + "pangle_audience_package_exclude_ids": ["string"], + "pangle_audience_package_include_ids": ["string"], + "pangle_block_app_ids": ["string"], + "pixel_id": "pixel_id", + "placement_type": "PLACEMENT_TYPE_AUTOMATIC", + "placements": ["string"], + "product_set_id": "product_set_id", + "product_source": "CATALOG", + "promotion_type": "promotion_type", + "schedule_end_time": "schedule_end_time", + "schedule_start_time": "schedule_start_time", + "schedule_type": "SCHEDULE_START_END", + "secondary_optimization_event": "secondary_optimization_event", + "shopping_ads_retargeting_actions_days": 42, + "shopping_ads_retargeting_type": "OFF", + "spending_power": "ALL", + "tiktok_subplacements": ["string"], + "vertical_sensitivity_id": "vertical_sensitivity_id", + "video_download_disabled": True, + "video_user_actions": ["string"], + "view_attribution_window": "OFF", + }, + }, + status="active", + ) + assert_matches_type(AdGroupCreateResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_create(self, async_client: AsyncWhop) -> None: + response = await async_client.ad_groups.with_raw_response.create( + campaign_id="campaign_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_group = await response.parse() + assert_matches_type(AdGroupCreateResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: + async with async_client.ad_groups.with_streaming_response.create( + campaign_id="campaign_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_group = await response.parse() + assert_matches_type(AdGroupCreateResponse, ad_group, 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: AsyncWhop) -> None: + ad_group = await async_client.ad_groups.retrieve( + "adgrp_xxxxxxxxxxxx", + ) + assert_matches_type(AdGroupRetrieveResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: + response = await async_client.ad_groups.with_raw_response.retrieve( + "adgrp_xxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_group = await response.parse() + assert_matches_type(AdGroupRetrieveResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: + async with async_client.ad_groups.with_streaming_response.retrieve( + "adgrp_xxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_group = await response.parse() + assert_matches_type(AdGroupRetrieveResponse, ad_group, 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: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.ad_groups.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_update(self, async_client: AsyncWhop) -> None: + ad_group = await async_client.ad_groups.update( + id="adgrp_xxxxxxxxxxxx", + ) + assert_matches_type(AdGroupUpdateResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> None: + ad_group = await async_client.ad_groups.update( + id="adgrp_xxxxxxxxxxxx", + budget=6.9, + budget_type="daily", + config={ + "bid_amount": 42, + "bid_strategy": "lowest_cost", + "billing_event": "impressions", + "end_time": "end_time", + "frequency_cap": 42, + "frequency_cap_interval_days": 42, + "optimization_goal": "conversions", + "pacing": "standard", + "start_time": "start_time", + "targeting": { + "age_max": 42, + "age_min": 42, + "countries": ["string"], + "device_platforms": ["mobile"], + "exclude_audience_ids": ["string"], + "genders": ["male"], + "include_audience_ids": ["string"], + "interest_ids": ["string"], + "languages": ["string"], + "placement_type": "automatic", + }, + }, + daily_budget=6.9, + name="name", + platform_config={ + "meta": { + "android_devices": ["string"], + "attribution_setting": "attribution_setting", + "attribution_spec": [ + { + "event_type": "event_type", + "window_days": 42, + } + ], + "audience_network_positions": ["string"], + "audience_type": "audience_type", + "bid_amount": 42, + "bid_strategy": "LOWEST_COST_WITHOUT_CAP", + "billing_event": "APP_INSTALLS", + "brand_safety_content_filter_levels": ["string"], + "budget_remaining": "budget_remaining", + "cost_per_result_goal": 6.9, + "created_time": "created_time", + "daily_budget": 42, + "daily_min_spend_target": "daily_min_spend_target", + "daily_spend_cap": "daily_spend_cap", + "destination_type": "UNDEFINED", + "dsa_beneficiary": "dsa_beneficiary", + "dsa_payor": "dsa_payor", + "end_time": "end_time", + "excluded_geo_locations": { + "cities": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "countries": ["string"], + "location_types": ["string"], + "regions": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "zips": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + }, + "facebook_positions": ["string"], + "frequency_control_count": 42, + "frequency_control_days": 42, + "frequency_control_type": "frequency_control_type", + "geo_cities": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "geo_locations": { + "cities": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "countries": ["string"], + "location_types": ["string"], + "regions": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "zips": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + }, + "geo_regions": [ + { + "key": "key", + "country": "country", + "name": "name", + "radius": 42, + } + ], + "geo_zips": ["string"], + "instagram_actor_id": "instagram_actor_id", + "instagram_positions": ["string"], + "ios_devices": ["string"], + "is_dynamic_creative": True, + "lead_conversion_location": "website", + "lead_form_config": { + "name": "name", + "privacy_policy_url": "privacy_policy_url", + "questions": [ + { + "type": "type", + "conditional_questions_group_id": "conditional_questions_group_id", + "dependent_conditional_questions": [ + { + "type": "type", + "inline_context": "inline_context", + "key": "key", + "label": "label", + "options": [ + { + "key": "key", + "value": "value", + "logic": { + "type": "type", + "target_end_page_index": 42, + "target_question_index": 42, + }, + } + ], + } + ], + "inline_context": "inline_context", + "key": "key", + "label": "label", + "options": [ + { + "key": "key", + "value": "value", + "logic": { + "type": "type", + "target_end_page_index": 42, + "target_question_index": 42, + }, + } + ], + "question_format": "question_format", + } + ], + "background_image_source": "background_image_source", + "background_image_url": "background_image_url", + "conditional_logic_enabled": True, + "context_card_button_text": "context_card_button_text", + "context_card_content": ["string"], + "context_card_style": "context_card_style", + "context_card_title": "context_card_title", + "custom_disclaimer_body": "custom_disclaimer_body", + "custom_disclaimer_checkboxes": [ + { + "key": "key", + "text": "text", + "is_checked_by_default": True, + "is_required": True, + } + ], + "custom_disclaimer_title": "custom_disclaimer_title", + "form_type": "form_type", + "messenger_enabled": True, + "phone_verification_enabled": True, + "privacy_policy_link_text": "privacy_policy_link_text", + "question_page_custom_headline": "question_page_custom_headline", + "rich_creative_headline": "rich_creative_headline", + "rich_creative_overview": "rich_creative_overview", + "rich_creative_url": "rich_creative_url", + "thank_you_pages": [ + { + "body": "body", + "business_phone": "business_phone", + "button_text": "button_text", + "button_type": "button_type", + "conditional_question_group_id": "conditional_question_group_id", + "enable_messenger": True, + "gated_file_url": "gated_file_url", + "link": "link", + "name": "name", + "title": "title", + } + ], + }, + "lead_gen_form_id": "lead_gen_form_id", + "lifetime_budget": 42, + "lifetime_min_spend_target": "lifetime_min_spend_target", + "lifetime_spend_cap": "lifetime_spend_cap", + "location_types": ["string"], + "messenger_positions": ["string"], + "optimization_goal": "NONE", + "page_id": "page_id", + "pixel_id": "pixel_id", + "promoted_object": { + "custom_conversion_id": "custom_conversion_id", + "custom_event_str": "custom_event_str", + "custom_event_type": "custom_event_type", + "page_id": "page_id", + "pixel_id": "pixel_id", + "whatsapp_phone_number": "whatsapp_phone_number", + }, + "publisher_platforms": ["string"], + "source_adset_id": "source_adset_id", + "start_time": "start_time", + "status": "ACTIVE", + "targeting_automation": {"advantage_audience": 42}, + "threads_positions": ["string"], + "updated_time": "updated_time", + "user_device": ["string"], + "user_os": ["string"], + "whatsapp_phone_number": "whatsapp_phone_number", + "whatsapp_positions": ["string"], + }, + "tiktok": { + "actions": [ + { + "action_category_ids": ["string"], + "action_period": 42, + "action_scene": "VIDEO_RELATED", + "video_user_actions": ["WATCHED_TO_END"], + } + ], + "age_groups": ["AGE_13_17"], + "app_id": "app_xxxxxxxxxxxxxx", + "attribution_event_count": "UNSET", + "audience_ids": ["string"], + "audience_rule": {"foo": "bar"}, + "audience_type": "NORMAL", + "bid_price": 6.9, + "bid_type": "BID_TYPE_NO_BID", + "billing_event": "CPC", + "brand_safety_type": "NO_BRAND_SAFETY", + "budget_mode": "BUDGET_MODE_DAY", + "carrier_ids": ["string"], + "category_exclusion_ids": ["string"], + "click_attribution_window": "OFF", + "comment_disabled": True, + "contextual_tag_ids": ["string"], + "conversion_bid_price": 6.9, + "creative_material_mode": "creative_material_mode", + "dayparting": "dayparting", + "deep_funnel_event_source": "deep_funnel_event_source", + "deep_funnel_event_source_id": "deep_funnel_event_source_id", + "deep_funnel_optimization_status": "ON", + "device_model_ids": ["string"], + "device_price_ranges": ["string"], + "engaged_view_attribution_window": "OFF", + "excluded_audience_ids": ["string"], + "excluded_location_ids": ["string"], + "frequency": 42, + "frequency_schedule": 42, + "gender": "GENDER_UNLIMITED", + "identity_authorized_bc_id": "identity_authorized_bc_id", + "identity_id": "identity_id", + "identity_type": "identity_type", + "instant_form_config": { + "privacy_policy_url": "privacy_policy_url", + "questions": [ + { + "field_type": "field_type", + "label": "label", + } + ], + "button_text": "button_text", + "greeting": "greeting", + "name": "name", + }, + "instant_form_id": "instant_form_id", + "interest_category_ids": ["string"], + "interest_keyword_ids": ["string"], + "inventory_filter_enabled": True, + "ios14_targeting": "UNSET", + "isp_ids": ["string"], + "languages": ["string"], + "location_ids": ["string"], + "min_android_version": "min_android_version", + "min_ios_version": "min_ios_version", + "network_types": ["string"], + "operating_systems": ["ANDROID"], + "operation_status": "ENABLE", + "optimization_event": "optimization_event", + "optimization_goal": "CLICK", + "pacing": "PACING_MODE_SMOOTH", + "pangle_audience_package_exclude_ids": ["string"], + "pangle_audience_package_include_ids": ["string"], + "pangle_block_app_ids": ["string"], + "pixel_id": "pixel_id", + "placement_type": "PLACEMENT_TYPE_AUTOMATIC", + "placements": ["string"], + "product_set_id": "product_set_id", + "product_source": "CATALOG", + "promotion_type": "promotion_type", + "schedule_end_time": "schedule_end_time", + "schedule_start_time": "schedule_start_time", + "schedule_type": "SCHEDULE_START_END", + "secondary_optimization_event": "secondary_optimization_event", + "shopping_ads_retargeting_actions_days": 42, + "shopping_ads_retargeting_type": "OFF", + "spending_power": "ALL", + "tiktok_subplacements": ["string"], + "vertical_sensitivity_id": "vertical_sensitivity_id", + "video_download_disabled": True, + "video_user_actions": ["string"], + "view_attribution_window": "OFF", + }, + }, + status="active", + ) + assert_matches_type(AdGroupUpdateResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_update(self, async_client: AsyncWhop) -> None: + response = await async_client.ad_groups.with_raw_response.update( + id="adgrp_xxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_group = await response.parse() + assert_matches_type(AdGroupUpdateResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_update(self, async_client: AsyncWhop) -> None: + async with async_client.ad_groups.with_streaming_response.update( + id="adgrp_xxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_group = await response.parse() + assert_matches_type(AdGroupUpdateResponse, ad_group, 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_update(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.ad_groups.with_raw_response.update( + id="", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncWhop) -> None: + ad_group = await async_client.ad_groups.list() + assert_matches_type(AsyncCursorPage[AdGroupListResponse], ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None: + ad_group = await async_client.ad_groups.list( + after="after", + before="before", + campaign_id="campaign_id", + company_id="biz_xxxxxxxxxxxxxx", + created_after=parse_datetime("2023-12-01T05:00:00.401Z"), + created_before=parse_datetime("2023-12-01T05:00:00.401Z"), + first=42, + last=42, + query="query", + status="active", + ) + assert_matches_type(AsyncCursorPage[AdGroupListResponse], ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncWhop) -> None: + response = await async_client.ad_groups.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_group = await response.parse() + assert_matches_type(AsyncCursorPage[AdGroupListResponse], ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: + async with async_client.ad_groups.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_group = await response.parse() + assert_matches_type(AsyncCursorPage[AdGroupListResponse], ad_group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_delete(self, async_client: AsyncWhop) -> None: + ad_group = await async_client.ad_groups.delete( + "adgrp_xxxxxxxxxxxx", + ) + assert_matches_type(AdGroupDeleteResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_delete(self, async_client: AsyncWhop) -> None: + response = await async_client.ad_groups.with_raw_response.delete( + "adgrp_xxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad_group = await response.parse() + assert_matches_type(AdGroupDeleteResponse, ad_group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncWhop) -> None: + async with async_client.ad_groups.with_streaming_response.delete( + "adgrp_xxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad_group = await response.parse() + assert_matches_type(AdGroupDeleteResponse, ad_group, 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_delete(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.ad_groups.with_raw_response.delete( + "", + ) diff --git a/tests/api_resources/test_ads.py b/tests/api_resources/test_ads.py new file mode 100644 index 0000000..4d3e8a0 --- /dev/null +++ b/tests/api_resources/test_ads.py @@ -0,0 +1,482 @@ +# 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 whop_sdk import Whop, AsyncWhop +from tests.utils import assert_matches_type +from whop_sdk.types import AdListResponse, AdCreateResponse, AdRetrieveResponse +from whop_sdk._utils import parse_datetime +from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestAds: + 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: Whop) -> None: + ad = client.ads.create( + ad_group_id="ad_group_id", + ) + assert_matches_type(AdCreateResponse, ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Whop) -> None: + ad = client.ads.create( + ad_group_id="ad_group_id", + creative_set_id="creative_set_id", + existing_instagram_media_id="existing_instagram_media_id", + existing_post_id="existing_post_id", + platform_config={ + "meta": { + "call_to_action_type": "LEARN_MORE", + "carousel_cards": [ + { + "call_to_action_type": "call_to_action_type", + "description": "description", + "link": "link", + "name": "name", + } + ], + "description": "description", + "descriptions": ["string"], + "existing_instagram_media_id": "existing_instagram_media_id", + "existing_post_id": "existing_post_id", + "headline": "headline", + "headlines": ["string"], + "instagram_actor_id": "instagram_actor_id", + "lead_form_config": {"foo": "bar"}, + "link_url": "link_url", + "multi_advertiser_enrollment": "OPT_IN", + "name": "name", + "page_id": "page_id", + "page_welcome_message": {"foo": "bar"}, + "primary_text": "primary_text", + "primary_texts": ["string"], + "url_tags": "url_tags", + }, + "tiktok": { + "access_pass_tag": "access_pass_tag", + "ad_format": "SINGLE_IMAGE", + "ad_name": "ad_name", + "ad_text": "ad_text", + "ad_texts": ["string"], + "aigc_disclosure_type": "UNSET", + "auto_disclaimer_types": ["string"], + "automate_creative_enabled": True, + "brand_safety_postbid_partner": "UNSET", + "brand_safety_vast_url": "brand_safety_vast_url", + "call_to_action": "LEARN_MORE", + "call_to_action_enabled": True, + "call_to_action_id": "call_to_action_id", + "call_to_action_mode": "STANDARD", + "card_id": "card_id", + "carousel_image_index": 42, + "catalog_id": "catalog_id", + "click_tracking_url": "click_tracking_url", + "cpp_url": "cpp_url", + "creative_authorized": True, + "creative_auto_enhancement_strategy_list": ["string"], + "dark_post_status": "ON", + "deeplink": "deeplink", + "deeplink_format_type": "UNSET", + "deeplink_type": "deeplink_type", + "deeplink_utm_params": [{"foo": "bar"}], + "disclaimer_clickable_texts": [{"foo": "bar"}], + "disclaimer_text": "disclaimer_text", + "disclaimer_type": "NONE", + "dynamic_destination": "dynamic_destination", + "dynamic_format": "dynamic_format", + "end_card_cta": "end_card_cta", + "fallback_type": "UNSET", + "identity_authorized_bc_id": "identity_authorized_bc_id", + "identity_id": "identity_id", + "identity_type": "CUSTOMIZED_USER", + "image_ids": ["string"], + "impression_tracking_url": "impression_tracking_url", + "item_duet_status": "ENABLE", + "item_group_ids": ["string"], + "item_stitch_status": "ENABLE", + "landing_page_url": "landing_page_url", + "link_url": "link_url", + "music_id": "music_id", + "page_id": "page_id", + "product_display_field_list": ["string"], + "product_set_id": "product_set_id", + "product_specific_type": "product_specific_type", + "promotional_music_disabled": True, + "shopping_ads_fallback_type": "UNSET", + "shopping_ads_video_package_id": "shopping_ads_video_package_id", + "showcase_products": [{"foo": "bar"}], + "sku_ids": ["string"], + "tiktok_item_id": "tiktok_item_id", + "tracking_app_id": "tracking_app_id", + "tracking_message_event_set_id": "tracking_message_event_set_id", + "tracking_offline_event_set_ids": ["string"], + "tracking_pixel_id": "trpx_xxxxxxxxxxxxx", + "utm_params": [{"foo": "bar"}], + "vertical_video_strategy": "vertical_video_strategy", + "video_id": "video_id", + "video_view_tracking_url": "video_view_tracking_url", + "viewability_postbid_partner": "UNSET", + "viewability_vast_url": "viewability_vast_url", + }, + }, + status="active", + ) + assert_matches_type(AdCreateResponse, ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_create(self, client: Whop) -> None: + response = client.ads.with_raw_response.create( + ad_group_id="ad_group_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad = response.parse() + assert_matches_type(AdCreateResponse, ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Whop) -> None: + with client.ads.with_streaming_response.create( + ad_group_id="ad_group_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad = response.parse() + assert_matches_type(AdCreateResponse, ad, 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: Whop) -> None: + ad = client.ads.retrieve( + "xad_xxxxxxxxxxxxxx", + ) + assert_matches_type(AdRetrieveResponse, ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: Whop) -> None: + response = client.ads.with_raw_response.retrieve( + "xad_xxxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad = response.parse() + assert_matches_type(AdRetrieveResponse, ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: Whop) -> None: + with client.ads.with_streaming_response.retrieve( + "xad_xxxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad = response.parse() + assert_matches_type(AdRetrieveResponse, ad, 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: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.ads.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: Whop) -> None: + ad = client.ads.list() + assert_matches_type(SyncCursorPage[AdListResponse], ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: Whop) -> None: + ad = client.ads.list( + ad_group_id="ad_group_id", + after="after", + before="before", + campaign_id="campaign_id", + company_id="biz_xxxxxxxxxxxxxx", + created_after=parse_datetime("2023-12-01T05:00:00.401Z"), + created_before=parse_datetime("2023-12-01T05:00:00.401Z"), + first=42, + last=42, + status="active", + ) + assert_matches_type(SyncCursorPage[AdListResponse], ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: Whop) -> None: + response = client.ads.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad = response.parse() + assert_matches_type(SyncCursorPage[AdListResponse], ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Whop) -> None: + with client.ads.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad = response.parse() + assert_matches_type(SyncCursorPage[AdListResponse], ad, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncAds: + 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: AsyncWhop) -> None: + ad = await async_client.ads.create( + ad_group_id="ad_group_id", + ) + assert_matches_type(AdCreateResponse, ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None: + ad = await async_client.ads.create( + ad_group_id="ad_group_id", + creative_set_id="creative_set_id", + existing_instagram_media_id="existing_instagram_media_id", + existing_post_id="existing_post_id", + platform_config={ + "meta": { + "call_to_action_type": "LEARN_MORE", + "carousel_cards": [ + { + "call_to_action_type": "call_to_action_type", + "description": "description", + "link": "link", + "name": "name", + } + ], + "description": "description", + "descriptions": ["string"], + "existing_instagram_media_id": "existing_instagram_media_id", + "existing_post_id": "existing_post_id", + "headline": "headline", + "headlines": ["string"], + "instagram_actor_id": "instagram_actor_id", + "lead_form_config": {"foo": "bar"}, + "link_url": "link_url", + "multi_advertiser_enrollment": "OPT_IN", + "name": "name", + "page_id": "page_id", + "page_welcome_message": {"foo": "bar"}, + "primary_text": "primary_text", + "primary_texts": ["string"], + "url_tags": "url_tags", + }, + "tiktok": { + "access_pass_tag": "access_pass_tag", + "ad_format": "SINGLE_IMAGE", + "ad_name": "ad_name", + "ad_text": "ad_text", + "ad_texts": ["string"], + "aigc_disclosure_type": "UNSET", + "auto_disclaimer_types": ["string"], + "automate_creative_enabled": True, + "brand_safety_postbid_partner": "UNSET", + "brand_safety_vast_url": "brand_safety_vast_url", + "call_to_action": "LEARN_MORE", + "call_to_action_enabled": True, + "call_to_action_id": "call_to_action_id", + "call_to_action_mode": "STANDARD", + "card_id": "card_id", + "carousel_image_index": 42, + "catalog_id": "catalog_id", + "click_tracking_url": "click_tracking_url", + "cpp_url": "cpp_url", + "creative_authorized": True, + "creative_auto_enhancement_strategy_list": ["string"], + "dark_post_status": "ON", + "deeplink": "deeplink", + "deeplink_format_type": "UNSET", + "deeplink_type": "deeplink_type", + "deeplink_utm_params": [{"foo": "bar"}], + "disclaimer_clickable_texts": [{"foo": "bar"}], + "disclaimer_text": "disclaimer_text", + "disclaimer_type": "NONE", + "dynamic_destination": "dynamic_destination", + "dynamic_format": "dynamic_format", + "end_card_cta": "end_card_cta", + "fallback_type": "UNSET", + "identity_authorized_bc_id": "identity_authorized_bc_id", + "identity_id": "identity_id", + "identity_type": "CUSTOMIZED_USER", + "image_ids": ["string"], + "impression_tracking_url": "impression_tracking_url", + "item_duet_status": "ENABLE", + "item_group_ids": ["string"], + "item_stitch_status": "ENABLE", + "landing_page_url": "landing_page_url", + "link_url": "link_url", + "music_id": "music_id", + "page_id": "page_id", + "product_display_field_list": ["string"], + "product_set_id": "product_set_id", + "product_specific_type": "product_specific_type", + "promotional_music_disabled": True, + "shopping_ads_fallback_type": "UNSET", + "shopping_ads_video_package_id": "shopping_ads_video_package_id", + "showcase_products": [{"foo": "bar"}], + "sku_ids": ["string"], + "tiktok_item_id": "tiktok_item_id", + "tracking_app_id": "tracking_app_id", + "tracking_message_event_set_id": "tracking_message_event_set_id", + "tracking_offline_event_set_ids": ["string"], + "tracking_pixel_id": "trpx_xxxxxxxxxxxxx", + "utm_params": [{"foo": "bar"}], + "vertical_video_strategy": "vertical_video_strategy", + "video_id": "video_id", + "video_view_tracking_url": "video_view_tracking_url", + "viewability_postbid_partner": "UNSET", + "viewability_vast_url": "viewability_vast_url", + }, + }, + status="active", + ) + assert_matches_type(AdCreateResponse, ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_create(self, async_client: AsyncWhop) -> None: + response = await async_client.ads.with_raw_response.create( + ad_group_id="ad_group_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad = await response.parse() + assert_matches_type(AdCreateResponse, ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: + async with async_client.ads.with_streaming_response.create( + ad_group_id="ad_group_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad = await response.parse() + assert_matches_type(AdCreateResponse, ad, 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: AsyncWhop) -> None: + ad = await async_client.ads.retrieve( + "xad_xxxxxxxxxxxxxx", + ) + assert_matches_type(AdRetrieveResponse, ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: + response = await async_client.ads.with_raw_response.retrieve( + "xad_xxxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad = await response.parse() + assert_matches_type(AdRetrieveResponse, ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: + async with async_client.ads.with_streaming_response.retrieve( + "xad_xxxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad = await response.parse() + assert_matches_type(AdRetrieveResponse, ad, 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: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.ads.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncWhop) -> None: + ad = await async_client.ads.list() + assert_matches_type(AsyncCursorPage[AdListResponse], ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None: + ad = await async_client.ads.list( + ad_group_id="ad_group_id", + after="after", + before="before", + campaign_id="campaign_id", + company_id="biz_xxxxxxxxxxxxxx", + created_after=parse_datetime("2023-12-01T05:00:00.401Z"), + created_before=parse_datetime("2023-12-01T05:00:00.401Z"), + first=42, + last=42, + status="active", + ) + assert_matches_type(AsyncCursorPage[AdListResponse], ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncWhop) -> None: + response = await async_client.ads.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + ad = await response.parse() + assert_matches_type(AsyncCursorPage[AdListResponse], ad, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: + async with async_client.ads.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + ad = await response.parse() + assert_matches_type(AsyncCursorPage[AdListResponse], ad, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_bounties.py b/tests/api_resources/test_bounties.py new file mode 100644 index 0000000..ccec6b6 --- /dev/null +++ b/tests/api_resources/test_bounties.py @@ -0,0 +1,313 @@ +# 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 whop_sdk import Whop, AsyncWhop +from tests.utils import assert_matches_type +from whop_sdk.types import ( + BountyListResponse, + BountyCreateResponse, + BountyRetrieveResponse, +) +from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestBounties: + 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: Whop) -> None: + bounty = client.bounties.create( + base_unit_amount=6.9, + currency="usd", + description="description", + title="title", + ) + assert_matches_type(BountyCreateResponse, bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Whop) -> None: + bounty = client.bounties.create( + base_unit_amount=6.9, + currency="usd", + description="description", + title="title", + accepted_submissions_limit=42, + allowed_country_codes=["string"], + experience_id="exp_xxxxxxxxxxxxxx", + origin_account_id="origin_account_id", + ) + assert_matches_type(BountyCreateResponse, bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_create(self, client: Whop) -> None: + response = client.bounties.with_raw_response.create( + base_unit_amount=6.9, + currency="usd", + description="description", + title="title", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + bounty = response.parse() + assert_matches_type(BountyCreateResponse, bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Whop) -> None: + with client.bounties.with_streaming_response.create( + base_unit_amount=6.9, + currency="usd", + description="description", + title="title", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + bounty = response.parse() + assert_matches_type(BountyCreateResponse, bounty, 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: Whop) -> None: + bounty = client.bounties.retrieve( + "bnty_xxxxxxxxxxxxx", + ) + assert_matches_type(BountyRetrieveResponse, bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_retrieve(self, client: Whop) -> None: + response = client.bounties.with_raw_response.retrieve( + "bnty_xxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + bounty = response.parse() + assert_matches_type(BountyRetrieveResponse, bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_retrieve(self, client: Whop) -> None: + with client.bounties.with_streaming_response.retrieve( + "bnty_xxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + bounty = response.parse() + assert_matches_type(BountyRetrieveResponse, bounty, 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: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.bounties.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list(self, client: Whop) -> None: + bounty = client.bounties.list() + assert_matches_type(SyncCursorPage[BountyListResponse], bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_list_with_all_params(self, client: Whop) -> None: + bounty = client.bounties.list( + after="after", + before="before", + direction="asc", + experience_id="exp_xxxxxxxxxxxxxx", + first=42, + last=42, + status="published", + ) + assert_matches_type(SyncCursorPage[BountyListResponse], bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_list(self, client: Whop) -> None: + response = client.bounties.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + bounty = response.parse() + assert_matches_type(SyncCursorPage[BountyListResponse], bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_list(self, client: Whop) -> None: + with client.bounties.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + bounty = response.parse() + assert_matches_type(SyncCursorPage[BountyListResponse], bounty, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncBounties: + 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: AsyncWhop) -> None: + bounty = await async_client.bounties.create( + base_unit_amount=6.9, + currency="usd", + description="description", + title="title", + ) + assert_matches_type(BountyCreateResponse, bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None: + bounty = await async_client.bounties.create( + base_unit_amount=6.9, + currency="usd", + description="description", + title="title", + accepted_submissions_limit=42, + allowed_country_codes=["string"], + experience_id="exp_xxxxxxxxxxxxxx", + origin_account_id="origin_account_id", + ) + assert_matches_type(BountyCreateResponse, bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_create(self, async_client: AsyncWhop) -> None: + response = await async_client.bounties.with_raw_response.create( + base_unit_amount=6.9, + currency="usd", + description="description", + title="title", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + bounty = await response.parse() + assert_matches_type(BountyCreateResponse, bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: + async with async_client.bounties.with_streaming_response.create( + base_unit_amount=6.9, + currency="usd", + description="description", + title="title", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + bounty = await response.parse() + assert_matches_type(BountyCreateResponse, bounty, 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: AsyncWhop) -> None: + bounty = await async_client.bounties.retrieve( + "bnty_xxxxxxxxxxxxx", + ) + assert_matches_type(BountyRetrieveResponse, bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_retrieve(self, async_client: AsyncWhop) -> None: + response = await async_client.bounties.with_raw_response.retrieve( + "bnty_xxxxxxxxxxxxx", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + bounty = await response.parse() + assert_matches_type(BountyRetrieveResponse, bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_retrieve(self, async_client: AsyncWhop) -> None: + async with async_client.bounties.with_streaming_response.retrieve( + "bnty_xxxxxxxxxxxxx", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + bounty = await response.parse() + assert_matches_type(BountyRetrieveResponse, bounty, 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: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.bounties.with_raw_response.retrieve( + "", + ) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list(self, async_client: AsyncWhop) -> None: + bounty = await async_client.bounties.list() + assert_matches_type(AsyncCursorPage[BountyListResponse], bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncWhop) -> None: + bounty = await async_client.bounties.list( + after="after", + before="before", + direction="asc", + experience_id="exp_xxxxxxxxxxxxxx", + first=42, + last=42, + status="published", + ) + assert_matches_type(AsyncCursorPage[BountyListResponse], bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_list(self, async_client: AsyncWhop) -> None: + response = await async_client.bounties.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + bounty = await response.parse() + assert_matches_type(AsyncCursorPage[BountyListResponse], bounty, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: + async with async_client.bounties.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + bounty = await response.parse() + assert_matches_type(AsyncCursorPage[BountyListResponse], bounty, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_companies.py b/tests/api_resources/test_companies.py index 8b3e6f8..4051c78 100644 --- a/tests/api_resources/test_companies.py +++ b/tests/api_resources/test_companies.py @@ -11,6 +11,7 @@ from tests.utils import assert_matches_type from whop_sdk.types import ( CompanyListResponse, + CompanyCreateAPIKeyResponse, ) from whop_sdk._utils import parse_datetime from whop_sdk.pagination import SyncCursorPage, AsyncCursorPage @@ -215,6 +216,70 @@ def test_streaming_response_list(self, client: Whop) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_api_key(self, client: Whop) -> None: + company = client.companies.create_api_key( + parent_company_id="parent_company_id", + child_company_id="child_company_id", + ) + assert_matches_type(CompanyCreateAPIKeyResponse, company, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_api_key_with_all_params(self, client: Whop) -> None: + company = client.companies.create_api_key( + parent_company_id="parent_company_id", + child_company_id="child_company_id", + name="name", + permissions=[ + { + "actions": ["string"], + "grant": True, + "resources": ["string"], + } + ], + role="owner", + ) + assert_matches_type(CompanyCreateAPIKeyResponse, company, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_create_api_key(self, client: Whop) -> None: + response = client.companies.with_raw_response.create_api_key( + parent_company_id="parent_company_id", + child_company_id="child_company_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + company = response.parse() + assert_matches_type(CompanyCreateAPIKeyResponse, company, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_create_api_key(self, client: Whop) -> None: + with client.companies.with_streaming_response.create_api_key( + parent_company_id="parent_company_id", + child_company_id="child_company_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + company = response.parse() + assert_matches_type(CompanyCreateAPIKeyResponse, company, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_path_params_create_api_key(self, client: Whop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `parent_company_id` but received ''"): + client.companies.with_raw_response.create_api_key( + parent_company_id="", + child_company_id="child_company_id", + ) + class TestAsyncCompanies: parametrize = pytest.mark.parametrize( @@ -413,3 +478,67 @@ async def test_streaming_response_list(self, async_client: AsyncWhop) -> None: assert_matches_type(AsyncCursorPage[CompanyListResponse], company, path=["response"]) assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create_api_key(self, async_client: AsyncWhop) -> None: + company = await async_client.companies.create_api_key( + parent_company_id="parent_company_id", + child_company_id="child_company_id", + ) + assert_matches_type(CompanyCreateAPIKeyResponse, company, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create_api_key_with_all_params(self, async_client: AsyncWhop) -> None: + company = await async_client.companies.create_api_key( + parent_company_id="parent_company_id", + child_company_id="child_company_id", + name="name", + permissions=[ + { + "actions": ["string"], + "grant": True, + "resources": ["string"], + } + ], + role="owner", + ) + assert_matches_type(CompanyCreateAPIKeyResponse, company, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_create_api_key(self, async_client: AsyncWhop) -> None: + response = await async_client.companies.with_raw_response.create_api_key( + parent_company_id="parent_company_id", + child_company_id="child_company_id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + company = await response.parse() + assert_matches_type(CompanyCreateAPIKeyResponse, company, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_create_api_key(self, async_client: AsyncWhop) -> None: + async with async_client.companies.with_streaming_response.create_api_key( + parent_company_id="parent_company_id", + child_company_id="child_company_id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + company = await response.parse() + assert_matches_type(CompanyCreateAPIKeyResponse, company, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_path_params_create_api_key(self, async_client: AsyncWhop) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `parent_company_id` but received ''"): + await async_client.companies.with_raw_response.create_api_key( + parent_company_id="", + child_company_id="child_company_id", + ) diff --git a/tests/api_resources/test_conversions.py b/tests/api_resources/test_conversions.py new file mode 100644 index 0000000..3b63db1 --- /dev/null +++ b/tests/api_resources/test_conversions.py @@ -0,0 +1,215 @@ +# 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 whop_sdk import Whop, AsyncWhop +from tests.utils import assert_matches_type +from whop_sdk.types import ConversionCreateResponse +from whop_sdk._utils import parse_datetime + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestConversions: + 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: Whop) -> None: + conversion = client.conversions.create( + company_id="biz_xxxxxxxxxxxxxx", + event_name="lead", + ) + assert_matches_type(ConversionCreateResponse, conversion, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Whop) -> None: + conversion = client.conversions.create( + company_id="biz_xxxxxxxxxxxxxx", + event_name="lead", + action_source="email", + context={ + "ad_campaign_id": "ad_campaign_id", + "ad_id": "ad_id", + "ad_set_id": "ad_set_id", + "fbclid": "fbclid", + "fbp": "fbp", + "ga": "ga", + "gclid": "gclid", + "ig_sid": "ig_sid", + "ip_address": "ip_address", + "ttclid": "ttclid", + "ttp": "ttp", + "user_agent": "user_agent", + "utm_campaign": "utm_campaign", + "utm_content": "utm_content", + "utm_id": "utm_id", + "utm_medium": "utm_medium", + "utm_source": "utm_source", + "utm_term": "utm_term", + }, + currency="usd", + custom_name="custom_name", + event_id="evnt_xxxxxxxxxxxxx", + event_time=parse_datetime("2023-12-01T05:00:00.401Z"), + plan_id="plan_xxxxxxxxxxxxx", + product_id="prod_xxxxxxxxxxxxx", + referrer_url="referrer_url", + url="url", + user={ + "anonymous_id": "anonymous_id", + "birthdate": "1990-01-15", + "city": "city", + "country": "country", + "email": "email", + "external_id": "external_id", + "first_name": "first_name", + "gender": "male", + "last_name": "last_name", + "member_id": "mber_xxxxxxxxxxxxx", + "membership_id": "mem_xxxxxxxxxxxxxx", + "name": "name", + "phone": "phone", + "postal_code": "postal_code", + "state": "state", + "user_id": "user_xxxxxxxxxxxxx", + "username": "username", + }, + value=6.9, + ) + assert_matches_type(ConversionCreateResponse, conversion, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_create(self, client: Whop) -> None: + response = client.conversions.with_raw_response.create( + company_id="biz_xxxxxxxxxxxxxx", + event_name="lead", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + conversion = response.parse() + assert_matches_type(ConversionCreateResponse, conversion, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Whop) -> None: + with client.conversions.with_streaming_response.create( + company_id="biz_xxxxxxxxxxxxxx", + event_name="lead", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + conversion = response.parse() + assert_matches_type(ConversionCreateResponse, conversion, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncConversions: + 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: AsyncWhop) -> None: + conversion = await async_client.conversions.create( + company_id="biz_xxxxxxxxxxxxxx", + event_name="lead", + ) + assert_matches_type(ConversionCreateResponse, conversion, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncWhop) -> None: + conversion = await async_client.conversions.create( + company_id="biz_xxxxxxxxxxxxxx", + event_name="lead", + action_source="email", + context={ + "ad_campaign_id": "ad_campaign_id", + "ad_id": "ad_id", + "ad_set_id": "ad_set_id", + "fbclid": "fbclid", + "fbp": "fbp", + "ga": "ga", + "gclid": "gclid", + "ig_sid": "ig_sid", + "ip_address": "ip_address", + "ttclid": "ttclid", + "ttp": "ttp", + "user_agent": "user_agent", + "utm_campaign": "utm_campaign", + "utm_content": "utm_content", + "utm_id": "utm_id", + "utm_medium": "utm_medium", + "utm_source": "utm_source", + "utm_term": "utm_term", + }, + currency="usd", + custom_name="custom_name", + event_id="evnt_xxxxxxxxxxxxx", + event_time=parse_datetime("2023-12-01T05:00:00.401Z"), + plan_id="plan_xxxxxxxxxxxxx", + product_id="prod_xxxxxxxxxxxxx", + referrer_url="referrer_url", + url="url", + user={ + "anonymous_id": "anonymous_id", + "birthdate": "1990-01-15", + "city": "city", + "country": "country", + "email": "email", + "external_id": "external_id", + "first_name": "first_name", + "gender": "male", + "last_name": "last_name", + "member_id": "mber_xxxxxxxxxxxxx", + "membership_id": "mem_xxxxxxxxxxxxxx", + "name": "name", + "phone": "phone", + "postal_code": "postal_code", + "state": "state", + "user_id": "user_xxxxxxxxxxxxx", + "username": "username", + }, + value=6.9, + ) + assert_matches_type(ConversionCreateResponse, conversion, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_create(self, async_client: AsyncWhop) -> None: + response = await async_client.conversions.with_raw_response.create( + company_id="biz_xxxxxxxxxxxxxx", + event_name="lead", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + conversion = await response.parse() + assert_matches_type(ConversionCreateResponse, conversion, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_create(self, async_client: AsyncWhop) -> None: + async with async_client.conversions.with_streaming_response.create( + company_id="biz_xxxxxxxxxxxxxx", + event_name="lead", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + conversion = await response.parse() + assert_matches_type(ConversionCreateResponse, conversion, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_stats.py b/tests/api_resources/test_stats.py new file mode 100644 index 0000000..064af46 --- /dev/null +++ b/tests/api_resources/test_stats.py @@ -0,0 +1,414 @@ +# 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 whop_sdk import Whop, AsyncWhop +from tests.utils import assert_matches_type +from whop_sdk.types import ( + StatRunSqlResponse, + StatDescribeResponse, + StatQueryRawResponse, + StatQueryMetricResponse, +) +from whop_sdk._utils import parse_datetime + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestStats: + 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_describe(self, client: Whop) -> None: + stat = client.stats.describe() + assert_matches_type(StatDescribeResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_describe_with_all_params(self, client: Whop) -> None: + stat = client.stats.describe( + company_id="biz_xxxxxxxxxxxxxx", + resource="resource", + user_id="user_xxxxxxxxxxxxx", + ) + assert_matches_type(StatDescribeResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_describe(self, client: Whop) -> None: + response = client.stats.with_raw_response.describe() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stat = response.parse() + assert_matches_type(StatDescribeResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_describe(self, client: Whop) -> None: + with client.stats.with_streaming_response.describe() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stat = response.parse() + assert_matches_type(StatDescribeResponse, stat, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_query_metric(self, client: Whop) -> None: + stat = client.stats.query_metric( + resource="resource", + ) + assert_matches_type(StatQueryMetricResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_query_metric_with_all_params(self, client: Whop) -> None: + stat = client.stats.query_metric( + resource="resource", + breakdowns=["string"], + company_id="biz_xxxxxxxxxxxxxx", + filters={"foo": "bar"}, + from_=parse_datetime("2023-12-01T05:00:00.401Z"), + granularity="granularity", + time_zone="time_zone", + to=parse_datetime("2023-12-01T05:00:00.401Z"), + user_id="user_xxxxxxxxxxxxx", + ) + assert_matches_type(StatQueryMetricResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_query_metric(self, client: Whop) -> None: + response = client.stats.with_raw_response.query_metric( + resource="resource", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stat = response.parse() + assert_matches_type(StatQueryMetricResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_query_metric(self, client: Whop) -> None: + with client.stats.with_streaming_response.query_metric( + resource="resource", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stat = response.parse() + assert_matches_type(StatQueryMetricResponse, stat, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_query_raw(self, client: Whop) -> None: + stat = client.stats.query_raw( + resource="resource", + ) + assert_matches_type(StatQueryRawResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_query_raw_with_all_params(self, client: Whop) -> None: + stat = client.stats.query_raw( + resource="resource", + company_id="biz_xxxxxxxxxxxxxx", + cursor="cursor", + from_=parse_datetime("2023-12-01T05:00:00.401Z"), + limit=42, + sort="sort", + sort_direction="asc", + to=parse_datetime("2023-12-01T05:00:00.401Z"), + user_id="user_xxxxxxxxxxxxx", + ) + assert_matches_type(StatQueryRawResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_query_raw(self, client: Whop) -> None: + response = client.stats.with_raw_response.query_raw( + resource="resource", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stat = response.parse() + assert_matches_type(StatQueryRawResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_query_raw(self, client: Whop) -> None: + with client.stats.with_streaming_response.query_raw( + resource="resource", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stat = response.parse() + assert_matches_type(StatQueryRawResponse, stat, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_run_sql(self, client: Whop) -> None: + stat = client.stats.run_sql( + resource="resource", + sql="sql", + ) + assert_matches_type(StatRunSqlResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_run_sql_with_all_params(self, client: Whop) -> None: + stat = client.stats.run_sql( + resource="resource", + sql="sql", + company_id="biz_xxxxxxxxxxxxxx", + cursor="cursor", + from_=parse_datetime("2023-12-01T05:00:00.401Z"), + limit=42, + sort="sort", + sort_direction="asc", + to=parse_datetime("2023-12-01T05:00:00.401Z"), + user_id="user_xxxxxxxxxxxxx", + ) + assert_matches_type(StatRunSqlResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_run_sql(self, client: Whop) -> None: + response = client.stats.with_raw_response.run_sql( + resource="resource", + sql="sql", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stat = response.parse() + assert_matches_type(StatRunSqlResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_run_sql(self, client: Whop) -> None: + with client.stats.with_streaming_response.run_sql( + resource="resource", + sql="sql", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stat = response.parse() + assert_matches_type(StatRunSqlResponse, stat, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncStats: + 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_describe(self, async_client: AsyncWhop) -> None: + stat = await async_client.stats.describe() + assert_matches_type(StatDescribeResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_describe_with_all_params(self, async_client: AsyncWhop) -> None: + stat = await async_client.stats.describe( + company_id="biz_xxxxxxxxxxxxxx", + resource="resource", + user_id="user_xxxxxxxxxxxxx", + ) + assert_matches_type(StatDescribeResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_describe(self, async_client: AsyncWhop) -> None: + response = await async_client.stats.with_raw_response.describe() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stat = await response.parse() + assert_matches_type(StatDescribeResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_describe(self, async_client: AsyncWhop) -> None: + async with async_client.stats.with_streaming_response.describe() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stat = await response.parse() + assert_matches_type(StatDescribeResponse, stat, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_query_metric(self, async_client: AsyncWhop) -> None: + stat = await async_client.stats.query_metric( + resource="resource", + ) + assert_matches_type(StatQueryMetricResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_query_metric_with_all_params(self, async_client: AsyncWhop) -> None: + stat = await async_client.stats.query_metric( + resource="resource", + breakdowns=["string"], + company_id="biz_xxxxxxxxxxxxxx", + filters={"foo": "bar"}, + from_=parse_datetime("2023-12-01T05:00:00.401Z"), + granularity="granularity", + time_zone="time_zone", + to=parse_datetime("2023-12-01T05:00:00.401Z"), + user_id="user_xxxxxxxxxxxxx", + ) + assert_matches_type(StatQueryMetricResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_query_metric(self, async_client: AsyncWhop) -> None: + response = await async_client.stats.with_raw_response.query_metric( + resource="resource", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stat = await response.parse() + assert_matches_type(StatQueryMetricResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_query_metric(self, async_client: AsyncWhop) -> None: + async with async_client.stats.with_streaming_response.query_metric( + resource="resource", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stat = await response.parse() + assert_matches_type(StatQueryMetricResponse, stat, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_query_raw(self, async_client: AsyncWhop) -> None: + stat = await async_client.stats.query_raw( + resource="resource", + ) + assert_matches_type(StatQueryRawResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_query_raw_with_all_params(self, async_client: AsyncWhop) -> None: + stat = await async_client.stats.query_raw( + resource="resource", + company_id="biz_xxxxxxxxxxxxxx", + cursor="cursor", + from_=parse_datetime("2023-12-01T05:00:00.401Z"), + limit=42, + sort="sort", + sort_direction="asc", + to=parse_datetime("2023-12-01T05:00:00.401Z"), + user_id="user_xxxxxxxxxxxxx", + ) + assert_matches_type(StatQueryRawResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_query_raw(self, async_client: AsyncWhop) -> None: + response = await async_client.stats.with_raw_response.query_raw( + resource="resource", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stat = await response.parse() + assert_matches_type(StatQueryRawResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_query_raw(self, async_client: AsyncWhop) -> None: + async with async_client.stats.with_streaming_response.query_raw( + resource="resource", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stat = await response.parse() + assert_matches_type(StatQueryRawResponse, stat, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_run_sql(self, async_client: AsyncWhop) -> None: + stat = await async_client.stats.run_sql( + resource="resource", + sql="sql", + ) + assert_matches_type(StatRunSqlResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_run_sql_with_all_params(self, async_client: AsyncWhop) -> None: + stat = await async_client.stats.run_sql( + resource="resource", + sql="sql", + company_id="biz_xxxxxxxxxxxxxx", + cursor="cursor", + from_=parse_datetime("2023-12-01T05:00:00.401Z"), + limit=42, + sort="sort", + sort_direction="asc", + to=parse_datetime("2023-12-01T05:00:00.401Z"), + user_id="user_xxxxxxxxxxxxx", + ) + assert_matches_type(StatRunSqlResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_run_sql(self, async_client: AsyncWhop) -> None: + response = await async_client.stats.with_raw_response.run_sql( + resource="resource", + sql="sql", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + stat = await response.parse() + assert_matches_type(StatRunSqlResponse, stat, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_run_sql(self, async_client: AsyncWhop) -> None: + async with async_client.stats.with_streaming_response.run_sql( + resource="resource", + sql="sql", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + stat = await response.parse() + assert_matches_type(StatRunSqlResponse, stat, path=["response"]) + + assert cast(Any, response.is_closed) is True From 0f7ed96e61417a26bf2075b687e9c0f60cf0f1ba Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 06:16:57 +0000 Subject: [PATCH 31/35] feat(api): api update --- .stats.yml | 4 ++-- src/whop_sdk/types/ad_create_params.py | 1 + src/whop_sdk/types/ad_create_response.py | 1 + src/whop_sdk/types/ad_list_response.py | 1 + src/whop_sdk/types/ad_retrieve_response.py | 1 + 5 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 75c2f68..04ea60b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 218 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-dcbc5afc2351d027e101397493a5ec8352a81a727258ea1bbdfc15059c953772.yml -openapi_spec_hash: a4c166d585688a123caa9a5abb575406 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-a3f7272c868273bdba6b569c05961b3dcfeaf602c44b2e82c04097b9abb13af5.yml +openapi_spec_hash: e5b4b97d17ab288a38c218fc909a07c5 config_hash: 82b4c2cb9b7d1707feb0f11c7c2426e9 diff --git a/src/whop_sdk/types/ad_create_params.py b/src/whop_sdk/types/ad_create_params.py index 26dc747..0ef67c8 100644 --- a/src/whop_sdk/types/ad_create_params.py +++ b/src/whop_sdk/types/ad_create_params.py @@ -92,6 +92,7 @@ class PlatformConfigMeta(TypedDict, total=False): "SEE_MENU", "REQUEST_TIME", "EVENT_RSVP", + "SEE_DETAILS", ] ] """Call-to-action button type.""" diff --git a/src/whop_sdk/types/ad_create_response.py b/src/whop_sdk/types/ad_create_response.py index de11a1b..3f9d6ed 100644 --- a/src/whop_sdk/types/ad_create_response.py +++ b/src/whop_sdk/types/ad_create_response.py @@ -71,6 +71,7 @@ class PlatformConfigMetaAdPlatformConfigType(BaseModel): "SEE_MENU", "REQUEST_TIME", "EVENT_RSVP", + "SEE_DETAILS", ] ] = None diff --git a/src/whop_sdk/types/ad_list_response.py b/src/whop_sdk/types/ad_list_response.py index c5254c5..3dce9b7 100644 --- a/src/whop_sdk/types/ad_list_response.py +++ b/src/whop_sdk/types/ad_list_response.py @@ -49,6 +49,7 @@ class PlatformConfigMetaAdPlatformConfigType(BaseModel): "SEE_MENU", "REQUEST_TIME", "EVENT_RSVP", + "SEE_DETAILS", ] ] = None diff --git a/src/whop_sdk/types/ad_retrieve_response.py b/src/whop_sdk/types/ad_retrieve_response.py index 4b49b9b..ba185ed 100644 --- a/src/whop_sdk/types/ad_retrieve_response.py +++ b/src/whop_sdk/types/ad_retrieve_response.py @@ -71,6 +71,7 @@ class PlatformConfigMetaAdPlatformConfigType(BaseModel): "SEE_MENU", "REQUEST_TIME", "EVENT_RSVP", + "SEE_DETAILS", ] ] = None From 07e70b5f3043647dd06dd54a435ac5db55eae06d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 02:17:47 +0000 Subject: [PATCH 32/35] feat(api): api update --- .stats.yml | 4 ++-- src/whop_sdk/types/ad_create_params.py | 1 + src/whop_sdk/types/ad_create_response.py | 1 + src/whop_sdk/types/ad_list_response.py | 1 + src/whop_sdk/types/ad_retrieve_response.py | 1 + 5 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 04ea60b..7dd81fd 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 218 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-a3f7272c868273bdba6b569c05961b3dcfeaf602c44b2e82c04097b9abb13af5.yml -openapi_spec_hash: e5b4b97d17ab288a38c218fc909a07c5 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-a41094741fe2e462e8344ebcc5fd06a0b7e1c32f87c7e088ed3830689b3abf79.yml +openapi_spec_hash: af859c10ee54c5289aac2b239628e31a config_hash: 82b4c2cb9b7d1707feb0f11c7c2426e9 diff --git a/src/whop_sdk/types/ad_create_params.py b/src/whop_sdk/types/ad_create_params.py index 0ef67c8..269d922 100644 --- a/src/whop_sdk/types/ad_create_params.py +++ b/src/whop_sdk/types/ad_create_params.py @@ -93,6 +93,7 @@ class PlatformConfigMeta(TypedDict, total=False): "REQUEST_TIME", "EVENT_RSVP", "SEE_DETAILS", + "VIEW_INSTAGRAM_PROFILE", ] ] """Call-to-action button type.""" diff --git a/src/whop_sdk/types/ad_create_response.py b/src/whop_sdk/types/ad_create_response.py index 3f9d6ed..13494ae 100644 --- a/src/whop_sdk/types/ad_create_response.py +++ b/src/whop_sdk/types/ad_create_response.py @@ -72,6 +72,7 @@ class PlatformConfigMetaAdPlatformConfigType(BaseModel): "REQUEST_TIME", "EVENT_RSVP", "SEE_DETAILS", + "VIEW_INSTAGRAM_PROFILE", ] ] = None diff --git a/src/whop_sdk/types/ad_list_response.py b/src/whop_sdk/types/ad_list_response.py index 3dce9b7..5711b6d 100644 --- a/src/whop_sdk/types/ad_list_response.py +++ b/src/whop_sdk/types/ad_list_response.py @@ -50,6 +50,7 @@ class PlatformConfigMetaAdPlatformConfigType(BaseModel): "REQUEST_TIME", "EVENT_RSVP", "SEE_DETAILS", + "VIEW_INSTAGRAM_PROFILE", ] ] = None diff --git a/src/whop_sdk/types/ad_retrieve_response.py b/src/whop_sdk/types/ad_retrieve_response.py index ba185ed..ba0008e 100644 --- a/src/whop_sdk/types/ad_retrieve_response.py +++ b/src/whop_sdk/types/ad_retrieve_response.py @@ -72,6 +72,7 @@ class PlatformConfigMetaAdPlatformConfigType(BaseModel): "REQUEST_TIME", "EVENT_RSVP", "SEE_DETAILS", + "VIEW_INSTAGRAM_PROFILE", ] ] = None From 95abdfe89b150735daa9d0caaed4834d47cc3762 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 03:16:50 +0000 Subject: [PATCH 33/35] feat(api): api update --- .stats.yml | 4 ++-- src/whop_sdk/types/payment_method_types.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 7dd81fd..a2ab163 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 218 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-a41094741fe2e462e8344ebcc5fd06a0b7e1c32f87c7e088ed3830689b3abf79.yml -openapi_spec_hash: af859c10ee54c5289aac2b239628e31a +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-99356f078396ad58a18ad6aa5e68ae7b6b0bce38d810f09f944fcd4054654bf5.yml +openapi_spec_hash: 8142944a7535449738c0f495c2e82d0c config_hash: 82b4c2cb9b7d1707feb0f11c7c2426e9 diff --git a/src/whop_sdk/types/payment_method_types.py b/src/whop_sdk/types/payment_method_types.py index 38bd959..859feeb 100644 --- a/src/whop_sdk/types/payment_method_types.py +++ b/src/whop_sdk/types/payment_method_types.py @@ -22,6 +22,7 @@ "bizum", "blik", "boleto", + "ca_bank_transfer", "capchase_pay", "card", "cashapp", From cb4b06e28716bf53ed67ca7ac411f5e1d17496e9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 04:16:48 +0000 Subject: [PATCH 34/35] feat(api): api update --- .stats.yml | 4 ++-- src/whop_sdk/resources/invoices.py | 10 ++++++++++ src/whop_sdk/types/invoice_update_params.py | 6 ++++++ tests/api_resources/test_invoices.py | 2 ++ 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index a2ab163..5bb99ec 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 218 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-99356f078396ad58a18ad6aa5e68ae7b6b0bce38d810f09f944fcd4054654bf5.yml -openapi_spec_hash: 8142944a7535449738c0f495c2e82d0c +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/frostedinc/whopsdk-3baab19728698b8f16db370c217ff197d7c46c74fcc80a6e7df160e35204e1d7.yml +openapi_spec_hash: c7b665366889f267a0f10c6389c36cf3 config_hash: 82b4c2cb9b7d1707feb0f11c7c2426e9 diff --git a/src/whop_sdk/resources/invoices.py b/src/whop_sdk/resources/invoices.py index 5f9b090..053064c 100644 --- a/src/whop_sdk/resources/invoices.py +++ b/src/whop_sdk/resources/invoices.py @@ -371,6 +371,7 @@ def update( member_id: Optional[str] | Omit = omit, payment_method_id: Optional[str] | Omit = omit, plan: Optional[invoice_update_params.Plan] | Omit = omit, + product_id: Optional[str] | Omit = omit, subscription_billing_anchor_at: Union[str, datetime, None] | 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. @@ -413,6 +414,9 @@ def update( plan: Updated plan attributes. + product_id: The unique identifier of an existing product to attach to this invoice. Only + allowed while the invoice is still a draft. + subscription_billing_anchor_at: The date that defines when the subscription billing cycle should start. extra_headers: Send extra headers @@ -441,6 +445,7 @@ def update( "member_id": member_id, "payment_method_id": payment_method_id, "plan": plan, + "product_id": product_id, "subscription_billing_anchor_at": subscription_billing_anchor_at, }, invoice_update_params.InvoiceUpdateParams, @@ -1033,6 +1038,7 @@ async def update( member_id: Optional[str] | Omit = omit, payment_method_id: Optional[str] | Omit = omit, plan: Optional[invoice_update_params.Plan] | Omit = omit, + product_id: Optional[str] | Omit = omit, subscription_billing_anchor_at: Union[str, datetime, None] | 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. @@ -1075,6 +1081,9 @@ async def update( plan: Updated plan attributes. + product_id: The unique identifier of an existing product to attach to this invoice. Only + allowed while the invoice is still a draft. + subscription_billing_anchor_at: The date that defines when the subscription billing cycle should start. extra_headers: Send extra headers @@ -1103,6 +1112,7 @@ async def update( "member_id": member_id, "payment_method_id": payment_method_id, "plan": plan, + "product_id": product_id, "subscription_billing_anchor_at": subscription_billing_anchor_at, }, invoice_update_params.InvoiceUpdateParams, diff --git a/src/whop_sdk/types/invoice_update_params.py b/src/whop_sdk/types/invoice_update_params.py index 69f0c8f..ba46184 100644 --- a/src/whop_sdk/types/invoice_update_params.py +++ b/src/whop_sdk/types/invoice_update_params.py @@ -65,6 +65,12 @@ class InvoiceUpdateParams(TypedDict, total=False): plan: Optional[Plan] """Updated plan attributes.""" + product_id: Optional[str] + """The unique identifier of an existing product to attach to this invoice. + + Only allowed while the invoice is still a draft. + """ + subscription_billing_anchor_at: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] """The date that defines when the subscription billing cycle should start.""" diff --git a/tests/api_resources/test_invoices.py b/tests/api_resources/test_invoices.py index 96ec3e2..847de39 100644 --- a/tests/api_resources/test_invoices.py +++ b/tests/api_resources/test_invoices.py @@ -367,6 +367,7 @@ def test_method_update_with_all_params(self, client: Whop) -> None: "unlimited_stock": True, "visibility": "visible", }, + product_id="prod_xxxxxxxxxxxxx", subscription_billing_anchor_at=parse_datetime("2023-12-01T05:00:00.401Z"), ) assert_matches_type(Invoice, invoice, path=["response"]) @@ -974,6 +975,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncWhop) -> N "unlimited_stock": True, "visibility": "visible", }, + product_id="prod_xxxxxxxxxxxxx", subscription_billing_anchor_at=parse_datetime("2023-12-01T05:00:00.401Z"), ) assert_matches_type(Invoice, invoice, path=["response"]) From 8bdc235835dcd0be23ca4f7a810ba4eb6711b2d8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 5 May 2026 04:17:26 +0000 Subject: [PATCH 35/35] release: 0.1.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 35 +++++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- src/whop_sdk/_version.py | 2 +- 4 files changed, 38 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index e3fea21..3d2ac0b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.0.38" + ".": "0.1.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index d61b0a1..6df9d32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,40 @@ # Changelog +## 0.1.0 (2026-05-05) + +Full Changelog: [v0.0.38...v0.1.0](https://github.com/whopio/whopsdk-python/compare/v0.0.38...v0.1.0) + +### Features + +* **api:** api update ([cb4b06e](https://github.com/whopio/whopsdk-python/commit/cb4b06e28716bf53ed67ca7ac411f5e1d17496e9)) +* **api:** api update ([95abdfe](https://github.com/whopio/whopsdk-python/commit/95abdfe89b150735daa9d0caaed4834d47cc3762)) +* **api:** api update ([07e70b5](https://github.com/whopio/whopsdk-python/commit/07e70b5f3043647dd06dd54a435ac5db55eae06d)) +* **api:** api update ([0f7ed96](https://github.com/whopio/whopsdk-python/commit/0f7ed96e61417a26bf2075b687e9c0f60cf0f1ba)) +* **api:** api update ([b61b0ee](https://github.com/whopio/whopsdk-python/commit/b61b0ee58364761c8c904d82e8d195b2d5f73efb)) +* **api:** api update ([da275a3](https://github.com/whopio/whopsdk-python/commit/da275a3907434c24f3a3433d38e4c883bfa1207d)) +* **api:** api update ([b671eaa](https://github.com/whopio/whopsdk-python/commit/b671eaa7a5fd7c67ee29af64eaad905b690d9f0f)) +* **api:** api update ([44f542f](https://github.com/whopio/whopsdk-python/commit/44f542f1c1083fda63e974e60177a4bbc992b29e)) +* **api:** api update ([3189146](https://github.com/whopio/whopsdk-python/commit/3189146ef827f1e6db20cbcb38e08ff0632fc031)) +* **api:** api update ([b3f3ebf](https://github.com/whopio/whopsdk-python/commit/b3f3ebf63f5400817e222df485260eb1e6d972ff)) +* **api:** api update ([b5be3d5](https://github.com/whopio/whopsdk-python/commit/b5be3d54f1884ecdaacaa2ec0de13d961e5e3ab6)) +* **api:** api update ([1e5a3f2](https://github.com/whopio/whopsdk-python/commit/1e5a3f2d0314c3905ce099b004f4855794e05082)) +* **api:** api update ([f1d06ff](https://github.com/whopio/whopsdk-python/commit/f1d06ff881c5d7667edde20d8bd2ee7246807173)) +* **api:** api update ([eea02e9](https://github.com/whopio/whopsdk-python/commit/eea02e91bfcb142e308c88d7cdc5232b3a2c419e)) +* **api:** api update ([0449c0b](https://github.com/whopio/whopsdk-python/commit/0449c0b3e63ac52b829408c1e92d1c9495c235c4)) +* **api:** manual updates ([69d6c5c](https://github.com/whopio/whopsdk-python/commit/69d6c5cadc4d7a1bb7f2bb44b06e8f424d8e7bd0)) +* support setting headers via env ([c943353](https://github.com/whopio/whopsdk-python/commit/c943353561b164a19d00b96933fe17ecee3a1601)) + + +### Bug Fixes + +* use correct field name format for multipart file arrays ([f30e380](https://github.com/whopio/whopsdk-python/commit/f30e380baa7acbf33ccdf33e5d14d477018e3428)) + + +### Chores + +* **internal:** more robust bootstrap script ([6b442f0](https://github.com/whopio/whopsdk-python/commit/6b442f0c46d096523edfe775f8bb0c602b8f9bd3)) +* **internal:** reformat pyproject.toml ([eb865b0](https://github.com/whopio/whopsdk-python/commit/eb865b00e455c264ee6d9a3ccc99b68372e868da)) + ## 0.0.38 (2026-04-21) Full Changelog: [v0.0.37...v0.0.38](https://github.com/whopio/whopsdk-python/compare/v0.0.37...v0.0.38) diff --git a/pyproject.toml b/pyproject.toml index 82c0844..9212ce8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "whop-sdk" -version = "0.0.38" +version = "0.1.0" description = "The official Python library for the Whop API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/whop_sdk/_version.py b/src/whop_sdk/_version.py index dececef..162053b 100644 --- a/src/whop_sdk/_version.py +++ b/src/whop_sdk/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "whop_sdk" -__version__ = "0.0.38" # x-release-please-version +__version__ = "0.1.0" # x-release-please-version